diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 00000000..81e9e855 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,254 @@ +# Pascal Editor - Setup Guide + +This guide will help you set up the Pascal Editor with authentication and database integration. + +## Prerequisites + +- Node.js 18+ or Bun 1.3+ +- Docker Desktop (for running Supabase locally) + +## Quick Start + +### 1. Install Dependencies + +```bash +bun install +``` + +This installs the Supabase CLI as a dev dependency - no need for global installation! + +### 2. Start Supabase Local Development + +```bash +bun db:start +``` + +This will start a local Supabase instance. You'll see output like: + +``` +API URL: http://127.0.0.1:54321 +DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres +Studio URL: http://127.0.0.1:54323 +Anon key: eyJh... +Service role key: eyJh... +``` + +### 4. Configure Environment Variables + +Create `apps/editor/.env.local` with the following variables: + +```bash +# Database Connection (Supabase local) +POSTGRES_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres + +# Supabase Configuration +NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 +SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU + +# Better Auth (generate your own secret with: openssl rand -base64 32) +BETTER_AUTH_SECRET= +BETTER_AUTH_URL=http://localhost:3000 + +# Google Maps (optional, for address search) +NEXT_PUBLIC_GOOGLE_MAPS_API_KEY= +``` + +Generate a secret for `BETTER_AUTH_SECRET`: + +```bash +openssl rand -base64 32 +``` + +### 5. Run Database Migrations + +```bash +bun db:reset +``` + +This will create all necessary tables for authentication and properties. + +### 6. Start the Development Server + +```bash +bun dev +``` + +The editor will be available at http://localhost:3000 + +## Monorepo Structure + +``` +. +├── apps/ +│ └── editor/ # Next.js editor application +│ ├── app/ +│ │ └── api/auth/ # Better Auth API routes +│ ├── components/ # UI components +│ └── features/ +│ └── cloud-sync/ # Cloud sync feature +├── packages/ +│ ├── auth/ # @pascal-app/auth - Authentication package +│ │ ├── src/ +│ │ │ ├── server.ts # Better Auth server config +│ │ │ └── client.ts # Better Auth client +│ │ └── README.md +│ ├── db/ # @pascal-app/db - Database package +│ │ ├── src/ +│ │ │ ├── client.ts # Supabase client (with RLS) +│ │ │ ├── server.ts # Supabase admin client +│ │ │ └── types.ts # Database types +│ │ ├── supabase/ +│ │ │ ├── config.toml +│ │ │ └── migrations/ # SQL migrations +│ │ └── README.md +│ ├── core/ # @pascal-app/core - Core editor logic +│ ├── viewer/ # @pascal-app/viewer - 3D viewer +│ └── ui/ # @repo/ui - Shared UI components +└── turbo.json +``` + +## Database Schema + +### Auth Tables (Better Auth) + +- **users** - User accounts with email and profile +- **sessions** - Active authentication sessions +- **accounts** - OAuth provider accounts (for future use) +- **verification_tokens** - Magic link tokens + +### Application Tables + +- **properties** - User properties + - `id`: Property ID + - `name`: Property name + - `owner_id`: User ID (foreign key to users) + +- **properties_addresses** - Property addresses with Google Maps data + - `id`: Address ID + - `property_id`: Property ID (foreign key) + - `formatted_address`: Full address + - `latitude`, `longitude`: GPS coordinates + - Plus detailed address components (street, city, state, etc.) + +- **properties_models** - Scene graph models (versions) + - `id`: Model ID + - `property_id`: Property ID (foreign key) + - `name`: Model name + - `version`: Version number + - `draft`: Draft status + - `scene_graph`: JSONB scene graph data + +## Features + +### Authentication + +- **Magic Link Sign-In**: Passwordless authentication via email +- **Session Management**: 7-day sessions with automatic refresh +- **Cookie-based**: Secure httpOnly cookies + +### Property Management + +- **Create Properties**: Add properties with real-world addresses +- **Google Maps Integration**: Address autocomplete and geocoding +- **Switch Properties**: Seamlessly switch between properties + +### Scene Management + +- **Auto-Save**: Changes saved every 2 seconds +- **Scene Loading**: Automatic scene loading when switching properties +- **Version Control**: Models are versioned for future rollback support + +## Development Workflow + +### Making Database Changes + +1. Create a new migration: + ```bash + cd packages/db + supabase migration new your_migration_name + ``` + +2. Edit the migration file in `supabase/migrations/` + +3. Apply the migration: + ```bash + supabase db reset + ``` + +### Updating Database Types + +After changing the database schema, regenerate TypeScript types: + +1. Update `packages/db/src/types.ts` to match your new schema +2. Run `bun install` to update type checking + +### Testing Authentication + +1. Start the editor: `bun dev` +2. Click "Save to cloud" button +3. Enter your email +4. Check console for magic link (not sent via email in development) +5. Click the link to authenticate + +## Supabase Studio + +Access the local Supabase Studio at: http://127.0.0.1:54323 + +Use this to: +- Browse and edit tables +- Run SQL queries +- View logs +- Manage RLS policies +- Test database functions + +## Production Deployment + +For production deployment: + +1. Create a Supabase project at https://supabase.com +2. Get your production database connection string +3. Update environment variables in your hosting platform +4. Link and push migrations: + ```bash + cd packages/db + bunx supabase link --project-ref your-project-ref + bunx supabase db push + ``` +5. Configure email provider in `packages/auth/src/server.ts` + +## Troubleshooting + +### "Missing POSTGRES_URL" error + +Make sure you've set `POSTGRES_URL` in `apps/editor/.env.local` to your Supabase connection string. + +### Supabase not starting + +Try stopping and restarting: +```bash +bun db:stop +bun db:start +``` + +### Migration errors + +Reset the database: +```bash +cd packages/db +supabase db reset +``` + +### Auth not working + +1. Check that Better Auth API route exists at `apps/editor/app/api/auth/[...all]/route.ts` +2. Verify `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` are set +3. Check console for magic link URLs in development + +## Next Steps + +- Configure email provider for magic links +- Add OAuth providers (Google, GitHub, etc.) +- Set up production Supabase project +- Configure RLS policies for additional security +- Add more property features (sharing, collaboration, etc.) diff --git a/apps/editor/app/api/auth/[...all]/route.ts b/apps/editor/app/api/auth/[...all]/route.ts new file mode 100644 index 00000000..4dbec88a --- /dev/null +++ b/apps/editor/app/api/auth/[...all]/route.ts @@ -0,0 +1,9 @@ +/** + * Better Auth API route handler + * Handles all /api/auth/* routes for authentication + */ + +import { auth } from '@pascal-app/auth/server' +import { toNextJsHandler } from 'better-auth/next-js' + +export const { GET, POST } = toNextJsHandler(auth) diff --git a/apps/editor/components/editor/index.tsx b/apps/editor/components/editor/index.tsx index 4a39bc15..07bc825c 100644 --- a/apps/editor/components/editor/index.tsx +++ b/apps/editor/components/editor/index.tsx @@ -4,11 +4,11 @@ import { initSpaceDetectionSync, initSpatialGridSync, useScene } from '@pascal-a import { Viewer } from '@pascal-app/viewer' import { useKeyboard } from '@/hooks/use-keyboard' import useEditor from '@/store/use-editor' -import { usePropertyScene } from '@/features/cloud-sync/lib/models/hooks' +import { usePropertyScene } from '@/features/community/lib/models/hooks' import { ZoneSystem } from '../systems/zone/zone-system' import { ToolManager } from '../tools/tool-manager' import { ActionMenu } from '../ui/action-menu' -import { CloudSaveButton } from '@/features/cloud-sync/components/cloud-save-button' +import { CloudSaveButton } from '@/features/community/components/cloud-save-button' import { PanelManager } from '../ui/panels/panel-manager' import { SidebarProvider } from '../ui/primitives/sidebar' import { AppSidebar } from '../ui/sidebar/app-sidebar' diff --git a/apps/editor/features/cloud-sync/lib/auth/client.ts b/apps/editor/features/cloud-sync/lib/auth/client.ts deleted file mode 100644 index 515dac5b..00000000 --- a/apps/editor/features/cloud-sync/lib/auth/client.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Auth client for the editor using better-auth - * Connects to the Pascal monorepo backend - */ - -import { createAuthClient } from 'better-auth/react' -import { - customSessionClient, - magicLinkClient, - organizationClient, -} from 'better-auth/client/plugins' - -/** - * Get the backend API URL - * Default: http://localhost:3000 (monorepo backend) - */ -function getBackendURL(): string { - // Check if we have an env variable for the backend URL - if (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_API_URL) { - return process.env.NEXT_PUBLIC_API_URL - } - - // In browser, try to use the current origin if it's the same host - if (typeof window !== 'undefined') { - // For local development, always use localhost:3000 (monorepo backend) - if (window.location.hostname === 'localhost') { - return 'http://localhost:3000' - } - // For production, use the same origin - return window.location.origin - } - - // SSR fallback - return 'http://localhost:3000' -} - -/** - * Auth client instance with better-auth - * Configured to work with the Pascal monorepo backend - */ -export const authClient = createAuthClient({ - baseURL: getBackendURL(), - plugins: [ - magicLinkClient(), - organizationClient(), - customSessionClient<{ - session: { - activePropertyId: string | null - activeOrganizationId: string | null - } - }>(), - ], -}) - -/** - * Export types for use in components - */ -export type AuthState = { - user: (typeof authClient)['$Infer']['Session']['user'] | null - session: (typeof authClient)['$Infer']['Session']['session'] | null - isLoading: boolean -} - -export type User = NonNullable -export type Session = NonNullable diff --git a/apps/editor/features/cloud-sync/lib/database/server.ts b/apps/editor/features/cloud-sync/lib/database/server.ts deleted file mode 100644 index 77e0ae2d..00000000 --- a/apps/editor/features/cloud-sync/lib/database/server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createClient } from '@supabase/supabase-js' - -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL -const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY - -if (!supabaseUrl || !supabaseServiceRoleKey) { - throw new Error( - 'Missing Supabase environment variables. Add NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY to your .env.local file.', - ) -} - -/** - * Create a Supabase client for server-side use with service role key - * This bypasses RLS and allows server actions to query the database directly - * Authentication is handled by Better Auth, permissions enforced by filtering on user_id - */ -export async function createServerSupabaseClient() { - const client = createClient(supabaseUrl, supabaseServiceRoleKey, { - auth: { - persistSession: false, - autoRefreshToken: false, - }, - }) - - return client -} diff --git a/apps/editor/features/cloud-sync/README.md b/apps/editor/features/community/README.md similarity index 61% rename from apps/editor/features/cloud-sync/README.md rename to apps/editor/features/community/README.md index f2324c44..4de77f66 100644 --- a/apps/editor/features/cloud-sync/README.md +++ b/apps/editor/features/community/README.md @@ -1,10 +1,10 @@ -# Cloud Sync Feature +# Community Feature -This directory contains the **optional** cloud synchronization and authentication features for the Pascal Editor. This feature is specific to the Pascal platform and can be safely removed if you're using the editor standalone. +This directory contains the **optional** community features (cloud synchronization and authentication) for the Pascal Editor. This feature is specific to the Pascal platform and can be safely removed if you're using the editor standalone. ## What This Does -The cloud sync feature provides: +The community feature provides: - **Authentication** - Sign in with magic link via Better Auth - **Property Management** - Create and manage properties with Google Maps address search @@ -15,20 +15,24 @@ The cloud sync feature provides: ## Architecture ``` -features/cloud-sync/ +features/community/ ├── lib/ │ ├── auth/ -│ │ ├── client.ts # Better Auth client configuration +│ │ ├── client.ts # Re-exports from @pascal-app/auth │ │ ├── server.ts # Server-side session handling │ │ └── hooks.ts # useAuth React hook │ ├── properties/ │ │ ├── actions.ts # Server actions for CRUD operations │ │ ├── types.ts # TypeScript types for properties -│ │ └── hooks.ts # useProperties and useActiveProperty hooks +│ │ ├── hooks.ts # Property React hooks +│ │ └── store.ts # Zustand store for property state +│ ├── models/ +│ │ ├── actions.ts # Scene model CRUD operations +│ │ └── hooks.ts # Scene loading and auto-save hooks │ ├── database/ -│ │ └── server.ts # Supabase server client with service role +│ │ └── server.ts # Re-exports from @pascal-app/db │ └── utils/ -│ └── id-generator.ts # nanoid-based ID generation (matches backend) +│ └── id-generator.ts # nanoid-based ID generation ├── components/ │ ├── cloud-save-button.tsx # Main UI entry point (top-right button) │ ├── sign-in-dialog.tsx # Magic link sign-in dialog @@ -45,7 +49,7 @@ features/cloud-sync/ 1. User clicks "Save to cloud" button 2. Signs in with magic link (email-based, no password) 3. Better Auth session is stored in cookies -4. Server actions validate session by calling the monorepo backend +4. Server actions validate session using Better Auth API ### Property Management 1. User creates a property with a real-world address (Google Maps) @@ -62,28 +66,38 @@ features/cloud-sync/ ### Database Integration - Uses Supabase (PostgreSQL) for database access +- Better Auth manages authentication tables directly - Server actions use service role key to bypass RLS - Permissions enforced by filtering on `owner_id` -- Table names: `properties`, `properties_addresses`, `auth_sessions` +- Tables: `users`, `sessions`, `properties`, `properties_addresses`, `properties_models` ## Required Environment Variables ```bash -# Backend API URL (Pascal monorepo - for better-auth only) -NEXT_PUBLIC_API_URL=http://localhost:3000 +# Database Connection +DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres # Supabase Configuration NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key_here SUPABASE_SERVICE_ROLE_KEY=your_service_role_key_here +# Better Auth +BETTER_AUTH_SECRET= +BETTER_AUTH_URL=http://localhost:3000 + # Google Maps API Key (for address search) NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_google_maps_key_here ``` +Generate `BETTER_AUTH_SECRET`: +```bash +openssl rand -base64 32 +``` + ## Dependencies -The cloud sync feature requires these packages: +The community feature requires these packages: ```json { @@ -96,17 +110,17 @@ The cloud sync feature requires these packages: ## How to Remove (For Open Source Users) -If you want to use the editor without cloud sync: +If you want to use the editor without community features: ### 1. Delete this directory ```bash -rm -rf features/cloud-sync +rm -rf features/community ``` ### 2. Remove the CloudSaveButton from the editor Edit `components/editor/index.tsx`: ```diff -- import { CloudSaveButton } from '@/features/cloud-sync/components/cloud-save-button' +- import { CloudSaveButton } from '@/features/community/components/cloud-save-button' export default function Editor() { return ( @@ -139,30 +153,46 @@ That's it! The editor will work as a standalone application without any cloud fe ## Backend Requirements -This feature requires the Pascal monorepo backend running with: -- Better Auth configured with magic link support +This feature requires: +- Supabase local development instance - PostgreSQL database with the following tables: + - `users` - User accounts (Better Auth) + - `sessions` - Authentication sessions (Better Auth) + - `verification_tokens` - Magic link tokens (Better Auth) - `properties` - Property records - `properties_addresses` - Property addresses - - `auth_sessions` - Better Auth sessions - - `auth_users` - Better Auth users -- Supabase local instance for database access + - `properties_models` - Scene graph models +- Database migrations are managed in `packages/db/supabase/migrations/` ## Development To work on this feature: -1. Ensure the monorepo backend is running on port 3000 -2. Ensure Supabase local is running on port 54321 -3. Configure all environment variables -4. Run the editor: `bun dev` +1. Install dependencies: + ```bash + bun install + ``` +2. Start Supabase local development: + ```bash + bun db:start + ``` +3. Run database migrations: + ```bash + bun db:reset + ``` +4. Configure all environment variables in `apps/editor/.env.local` +5. Run the editor: `bun dev` -The editor will be available at `http://localhost:3002` (different port to avoid conflicts with the monorepo). +The editor will be available at `http://localhost:3000`. + +For detailed setup instructions, see [SETUP.md](../../../SETUP.md) in the root directory. ## Notes - This feature uses **server actions** (Next.js App Router) for all database operations -- Authentication is handled by the monorepo backend via Better Auth +- Authentication is handled by **Better Auth** with magic link support +- Better Auth server is configured in `packages/auth` and mounted at `/api/auth/*` - The editor queries the database directly using Supabase with service role key -- IDs are generated using nanoid with custom alphabet to match the backend schema -- All table names match the monorepo's database schema exactly +- IDs are generated using nanoid with custom alphabet +- Scene state is managed with a **Zustand store** for reliable property switching +- Scene changes are auto-saved with 2-second debouncing to the currently selected property diff --git a/apps/editor/features/cloud-sync/components/cloud-save-button.tsx b/apps/editor/features/community/components/cloud-save-button.tsx similarity index 100% rename from apps/editor/features/cloud-sync/components/cloud-save-button.tsx rename to apps/editor/features/community/components/cloud-save-button.tsx diff --git a/apps/editor/features/cloud-sync/components/google-address-search.tsx b/apps/editor/features/community/components/google-address-search.tsx similarity index 100% rename from apps/editor/features/cloud-sync/components/google-address-search.tsx rename to apps/editor/features/community/components/google-address-search.tsx diff --git a/apps/editor/features/cloud-sync/components/new-property-dialog.tsx b/apps/editor/features/community/components/new-property-dialog.tsx similarity index 100% rename from apps/editor/features/cloud-sync/components/new-property-dialog.tsx rename to apps/editor/features/community/components/new-property-dialog.tsx diff --git a/apps/editor/features/cloud-sync/components/profile-dropdown.tsx b/apps/editor/features/community/components/profile-dropdown.tsx similarity index 100% rename from apps/editor/features/cloud-sync/components/profile-dropdown.tsx rename to apps/editor/features/community/components/profile-dropdown.tsx diff --git a/apps/editor/features/cloud-sync/components/property-dropdown.tsx b/apps/editor/features/community/components/property-dropdown.tsx similarity index 100% rename from apps/editor/features/cloud-sync/components/property-dropdown.tsx rename to apps/editor/features/community/components/property-dropdown.tsx diff --git a/apps/editor/features/cloud-sync/components/sign-in-dialog.tsx b/apps/editor/features/community/components/sign-in-dialog.tsx similarity index 100% rename from apps/editor/features/cloud-sync/components/sign-in-dialog.tsx rename to apps/editor/features/community/components/sign-in-dialog.tsx diff --git a/apps/editor/features/community/lib/auth/client.ts b/apps/editor/features/community/lib/auth/client.ts new file mode 100644 index 00000000..47907379 --- /dev/null +++ b/apps/editor/features/community/lib/auth/client.ts @@ -0,0 +1,7 @@ +/** + * Auth client for the editor using better-auth + * Re-exports from @pascal-app/auth package + */ + +export { authClient } from '@pascal-app/auth/client' +export type { AuthState, User, Session } from '@pascal-app/auth/client' diff --git a/apps/editor/features/cloud-sync/lib/auth/hooks.ts b/apps/editor/features/community/lib/auth/hooks.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/auth/hooks.ts rename to apps/editor/features/community/lib/auth/hooks.ts diff --git a/apps/editor/features/cloud-sync/lib/auth/server.ts b/apps/editor/features/community/lib/auth/server.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/auth/server.ts rename to apps/editor/features/community/lib/auth/server.ts diff --git a/apps/editor/features/community/lib/database/server.ts b/apps/editor/features/community/lib/database/server.ts new file mode 100644 index 00000000..56e50201 --- /dev/null +++ b/apps/editor/features/community/lib/database/server.ts @@ -0,0 +1,15 @@ +/** + * Supabase server client for database access + * Re-exports from @pascal-app/db package + */ + +import { supabaseAdmin } from '@pascal-app/db/server' + +/** + * Create a Supabase client for server-side use with service role key + * This bypasses RLS and allows server actions to query the database directly + * Authentication is handled by Better Auth, permissions enforced by filtering on user_id + */ +export async function createServerSupabaseClient() { + return supabaseAdmin +} diff --git a/apps/editor/features/cloud-sync/lib/models/actions.ts b/apps/editor/features/community/lib/models/actions.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/models/actions.ts rename to apps/editor/features/community/lib/models/actions.ts diff --git a/apps/editor/features/cloud-sync/lib/models/hooks.ts b/apps/editor/features/community/lib/models/hooks.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/models/hooks.ts rename to apps/editor/features/community/lib/models/hooks.ts diff --git a/apps/editor/features/cloud-sync/lib/properties/actions.ts b/apps/editor/features/community/lib/properties/actions.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/properties/actions.ts rename to apps/editor/features/community/lib/properties/actions.ts diff --git a/apps/editor/features/cloud-sync/lib/properties/hooks.ts b/apps/editor/features/community/lib/properties/hooks.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/properties/hooks.ts rename to apps/editor/features/community/lib/properties/hooks.ts diff --git a/apps/editor/features/cloud-sync/lib/properties/store.ts b/apps/editor/features/community/lib/properties/store.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/properties/store.ts rename to apps/editor/features/community/lib/properties/store.ts diff --git a/apps/editor/features/cloud-sync/lib/properties/types.ts b/apps/editor/features/community/lib/properties/types.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/properties/types.ts rename to apps/editor/features/community/lib/properties/types.ts diff --git a/apps/editor/features/cloud-sync/lib/utils/id-generator.ts b/apps/editor/features/community/lib/utils/id-generator.ts similarity index 100% rename from apps/editor/features/cloud-sync/lib/utils/id-generator.ts rename to apps/editor/features/community/lib/utils/id-generator.ts diff --git a/apps/editor/package.json b/apps/editor/package.json index f2d83f9b..41132f8c 100644 --- a/apps/editor/package.json +++ b/apps/editor/package.json @@ -4,14 +4,16 @@ "type": "module", "private": true, "scripts": { - "dev": "next dev --port 3002", + "dev": "next dev --port 3000", "build": "next build", "start": "next start", "lint": "biome lint", "check-types": "next typegen && tsc --noEmit" }, "dependencies": { + "@pascal-app/auth": "*", "@pascal-app/core": "*", + "@pascal-app/db": "*", "@pascal-app/viewer": "*", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-context-menu": "^2.2.16", diff --git a/apps/editor/public/audios/radios/classic/Ballroom in Miniature.mp3 b/apps/editor/public/audios/radios/classic/Ballroom in Miniature.mp3 new file mode 100644 index 00000000..ebcf4dd7 Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Ballroom in Miniature.mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Blueprints in Springtime.mp3 b/apps/editor/public/audios/radios/classic/Blueprints in Springtime.mp3 new file mode 100644 index 00000000..973955ba Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Blueprints in Springtime.mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Clockwork Tea Party (Alternate).mp3 b/apps/editor/public/audios/radios/classic/Clockwork Tea Party (Alternate).mp3 new file mode 100644 index 00000000..d4ec67d7 Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Clockwork Tea Party (Alternate).mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Clockwork Tea Party.mp3 b/apps/editor/public/audios/radios/classic/Clockwork Tea Party.mp3 new file mode 100644 index 00000000..88915bad Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Clockwork Tea Party.mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Clockwork Teacups.mp3 b/apps/editor/public/audios/radios/classic/Clockwork Teacups.mp3 new file mode 100644 index 00000000..a8967b20 Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Clockwork Teacups.mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Evening in the Parlor.mp3 b/apps/editor/public/audios/radios/classic/Evening in the Parlor.mp3 new file mode 100644 index 00000000..ed7b590f Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Evening in the Parlor.mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Glass Atrium.mp3 b/apps/editor/public/audios/radios/classic/Glass Atrium.mp3 new file mode 100644 index 00000000..460f72f9 Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Glass Atrium.mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Moonlight On The Drafting Table.mp3 b/apps/editor/public/audios/radios/classic/Moonlight On The Drafting Table.mp3 new file mode 100644 index 00000000..9b4e5275 Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Moonlight On The Drafting Table.mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Sunlit Garden Reverie.mp3 b/apps/editor/public/audios/radios/classic/Sunlit Garden Reverie.mp3 new file mode 100644 index 00000000..120d20ab Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Sunlit Garden Reverie.mp3 differ diff --git a/apps/editor/public/audios/radios/classic/Sunlit Waltz in Pastel Hues.mp3 b/apps/editor/public/audios/radios/classic/Sunlit Waltz in Pastel Hues.mp3 new file mode 100644 index 00000000..037cd889 Binary files /dev/null and b/apps/editor/public/audios/radios/classic/Sunlit Waltz in Pastel Hues.mp3 differ diff --git a/apps/editor/public/audios/sfx/grid_snap.mp3 b/apps/editor/public/audios/sfx/grid_snap.mp3 new file mode 100644 index 00000000..f3c853c0 Binary files /dev/null and b/apps/editor/public/audios/sfx/grid_snap.mp3 differ diff --git a/apps/editor/public/audios/sfx/item_delete.mp3 b/apps/editor/public/audios/sfx/item_delete.mp3 new file mode 100644 index 00000000..06f759d1 Binary files /dev/null and b/apps/editor/public/audios/sfx/item_delete.mp3 differ diff --git a/apps/editor/public/audios/sfx/item_pick.mp3 b/apps/editor/public/audios/sfx/item_pick.mp3 new file mode 100644 index 00000000..f65770ca Binary files /dev/null and b/apps/editor/public/audios/sfx/item_pick.mp3 differ diff --git a/apps/editor/public/audios/sfx/item_place.mp3 b/apps/editor/public/audios/sfx/item_place.mp3 new file mode 100644 index 00000000..5a176fe9 Binary files /dev/null and b/apps/editor/public/audios/sfx/item_place.mp3 differ diff --git a/apps/editor/public/audios/sfx/structure_build.mp3 b/apps/editor/public/audios/sfx/structure_build.mp3 new file mode 100644 index 00000000..028b3f2e Binary files /dev/null and b/apps/editor/public/audios/sfx/structure_build.mp3 differ diff --git a/apps/editor/public/audios/sfx/structure_delete.mp3 b/apps/editor/public/audios/sfx/structure_delete.mp3 new file mode 100644 index 00000000..6895e26a Binary files /dev/null and b/apps/editor/public/audios/sfx/structure_delete.mp3 differ diff --git a/bun.lock b/bun.lock index e0019e90..4d6eb984 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "editor", @@ -13,6 +14,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.3.13", + "supabase": "2.75.3", "turbo": "^2.8.1", "typescript": "5.9.2", "ultracite": "^7.1.1", @@ -22,7 +24,9 @@ "name": "web", "version": "0.1.0", "dependencies": { + "@pascal-app/auth": "*", "@pascal-app/core": "*", + "@pascal-app/db": "*", "@pascal-app/viewer": "*", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-context-menu": "^2.2.16", @@ -64,6 +68,19 @@ "typescript": "5.9.2", }, }, + "packages/auth": { + "name": "@pascal-app/auth", + "version": "0.0.0", + "dependencies": { + "@pascal-app/db": "*", + "better-auth": "^1.4.18", + "resend": "^4.0.1", + }, + "devDependencies": { + "@repo/typescript-config": "*", + "typescript": "5.9.2", + }, + }, "packages/core": { "name": "@pascal-app/core", "version": "0.1.11", @@ -91,6 +108,22 @@ "three": "^0.182", }, }, + "packages/db": { + "name": "@pascal-app/db", + "version": "0.0.0", + "dependencies": { + "@supabase/supabase-js": "^2.95.3", + "drizzle-orm": "^0.39.0", + "drizzle-zod": "^0.5.1", + "nanoid": "^5.0.9", + "postgres": "^3.4.5", + }, + "devDependencies": { + "@repo/typescript-config": "*", + "drizzle-kit": "^0.30.0", + "typescript": "5.9.2", + }, + }, "packages/eslint-config": { "name": "@repo/eslint-config", "version": "0.0.0", @@ -187,8 +220,60 @@ "@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], @@ -281,6 +366,8 @@ "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -325,10 +412,16 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@pascal-app/auth": ["@pascal-app/auth@workspace:packages/auth"], + "@pascal-app/core": ["@pascal-app/core@workspace:packages/core"], + "@pascal-app/db": ["@pascal-app/db@workspace:packages/db"], + "@pascal-app/viewer": ["@pascal-app/viewer@workspace:packages/viewer"], + "@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="], + "@pmndrs/msdfonts": ["@pmndrs/msdfonts@1.0.61", "", {}, "sha512-Nxk+3dAdsXFdx5pq1IHNe1j4zxogkAfhmBKUIHS+aJUs45q/XMtlADVrHLooiQ/Amlp9tXAvbvFYUOGd897POw=="], "@pmndrs/uikit": ["@pmndrs/uikit@1.0.61", "", { "dependencies": { "@pmndrs/msdfonts": "^1.0.61", "@pmndrs/uikit-pub-sub": "^1.0.61", "@preact/signals-core": "^1.5.1", "@zappar/msdf-generator": "^1.2.4", "yoga-layout": "^3.2.1" }, "peerDependencies": { "three": ">=0.162" } }, "sha512-xoNp2jKRoHa7INC3QaAL2ddpGmKy+eORMh4zoNds17EndqyBrqYzfWJK+PVmei/5MQNBTi0KrY2qxBGj6hB0cg=="], @@ -417,6 +510,8 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@react-email/render": ["@react-email/render@1.1.2", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3", "react-promise-suspense": "^0.3.4" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw=="], + "@react-google-maps/api": ["@react-google-maps/api@2.20.8", "", { "dependencies": { "@googlemaps/js-api-loader": "1.16.8", "@googlemaps/markerclusterer": "2.5.3", "@react-google-maps/infobox": "2.20.0", "@react-google-maps/marker-clusterer": "2.20.0", "@types/google.maps": "3.58.1", "invariant": "2.2.4" }, "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19", "react-dom": "^16.8 || ^17 || ^18 || ^19" } }, "sha512-wtLYFtCGXK3qbIz1H5to3JxbosPnKsvjDKhqGylXUb859EskhzR7OpuNt0LqdLarXUtZCJTKzPn3BNaekNIahg=="], "@react-google-maps/infobox": ["@react-google-maps/infobox@2.20.0", "", {}, "sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ=="], @@ -437,6 +532,8 @@ "@repo/ui": ["@repo/ui@workspace:packages/ui"], + "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@supabase/auth-js": ["@supabase/auth-js@2.95.3", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vD2YoS8E2iKIX0F7EwXTmqhUpaNsmbU6X2R0/NdFcs02oEfnHyNP/3M716f3wVJ2E5XHGiTFXki6lRckhJ0Thg=="], @@ -545,6 +642,8 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -583,12 +682,16 @@ "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + "bin-links": ["bin-links@6.0.0", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -603,6 +706,8 @@ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "citty": ["citty@0.2.0", "", {}, "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA=="], "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], @@ -611,6 +716,8 @@ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], @@ -627,6 +734,8 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], @@ -655,14 +764,32 @@ "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + "dotenv": ["dotenv@16.0.3", "", {}, "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="], "draco3d": ["draco3d@1.5.7", "", {}, "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ=="], + "drizzle-kit": ["drizzle-kit@0.30.6", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g=="], + + "drizzle-orm": ["drizzle-orm@0.39.3", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-EZ8ZpYvDIvKU9C56JYLOmUskazhad+uXZCTCRN4OnRMsL+xAJ05dv1eCpAG5xzhsm1hqiuC5kAZUCS924u2DTw=="], + + "drizzle-zod": ["drizzle-zod@0.5.1", "", { "peerDependencies": { "drizzle-orm": ">=0.23.13", "zod": "*" } }, "sha512-C/8bvzUH/zSnVfwdSibOgFjLhtDtbKYmkbPbUCq46QZyZCH6kODIMSOgZ8R7rVjoI+tCj3k06MRJMDqsIeoS4A=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + "es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -679,6 +806,10 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], @@ -719,6 +850,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], @@ -733,6 +866,8 @@ "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + "framer-motion": ["framer-motion@12.26.2", "", { "dependencies": { "motion-dom": "^12.26.2", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -741,6 +876,8 @@ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + "gel": ["gel@2.2.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ=="], + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -751,6 +888,8 @@ "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], + "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], + "glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -781,6 +920,12 @@ "hls.js": ["hls.js@1.6.15", "", {}, "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA=="], + "html-to-text": ["html-to-text@9.0.5", "", { "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", "dom-serializer": "^2.0.0", "htmlparser2": "^8.0.2", "selderee": "^0.11.0" } }, "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg=="], + + "htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "iceberg-js": ["iceberg-js@0.8.1", "", {}, "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA=="], "idb-keyval": ["idb-keyval@6.2.2", "", {}, "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg=="], @@ -853,7 +998,7 @@ "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], @@ -883,6 +1028,8 @@ "kysely": ["kysely@0.28.11", "", {}, "sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg=="], + "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], @@ -939,6 +1086,8 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], "motion": ["motion@12.26.2", "", { "dependencies": { "framer-motion": "^12.26.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-2Q6g0zK1gUJKhGT742DAe42LgietcdiJ3L3OcYAHCQaC1UkLnn6aC8S/obe4CxYTLAgid2asS1QdQ/blYfo5dw=="], @@ -957,6 +1106,12 @@ "next": ["next@16.1.0", "", { "dependencies": { "@next/env": "16.1.0", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.0", "@next/swc-darwin-x64": "16.1.0", "@next/swc-linux-arm64-gnu": "16.1.0", "@next/swc-linux-arm64-musl": "16.1.0", "@next/swc-linux-x64-gnu": "16.1.0", "@next/swc-linux-x64-musl": "16.1.0", "@next/swc-win32-arm64-msvc": "16.1.0", "@next/swc-win32-x64-msvc": "16.1.0", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Y+KbmDbefYtHDDQKLNrmzE/YYzG2msqo2VXhzh5yrJ54tx/6TmGdkR5+kP9ma7i7LwZpZMfoY3m/AoPPPKxtVw=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "npm-normalize-package-bin": ["npm-normalize-package-bin@5.0.0", "", {}, "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag=="], + "nypm": ["nypm@0.6.4", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -983,6 +1138,8 @@ "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -993,6 +1150,24 @@ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="], + + "pg": ["pg@8.18.0", "", { "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ=="], + + "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="], + + "pg-connection-string": ["pg-connection-string@2.11.0", "", {}, "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.11.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w=="], + + "pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -1001,10 +1176,24 @@ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="], + + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + "potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], + "promise-worker-transferable": ["promise-worker-transferable@1.0.4", "", { "dependencies": { "is-promise": "^2.1.0", "lie": "^3.0.2" } }, "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw=="], "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], @@ -1019,6 +1208,8 @@ "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="], + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], @@ -1027,16 +1218,22 @@ "react-use-measure": ["react-use-measure@2.1.7", "", { "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" }, "optionalPeers": ["react-dom"] }, "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg=="], + "read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "resend": ["resend@4.8.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-R8eBOFQDO6dzRTDmaMEdpqrkmgSjPpVXt4nGfWsZdYOet0kqra0xgbvTES6HmCriZEXbmGk3e0DiGIaLFTFSHA=="], + "resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], @@ -1051,6 +1248,8 @@ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + "selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="], + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], @@ -1067,6 +1266,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], @@ -1075,10 +1276,18 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "stats-gl": ["stats-gl@2.4.2", "", { "dependencies": { "@types/three": "*", "three": "^0.170.0" } }, "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ=="], "stats.js": ["stats.js@0.17.0", "", {}, "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="], @@ -1099,6 +1308,8 @@ "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + "supabase": ["supabase@2.75.3", "", { "dependencies": { "bin-links": "^6.0.0", "https-proxy-agent": "^7.0.2", "node-fetch": "^3.3.2", "tar": "7.5.7" }, "bin": { "supabase": "bin/supabase" } }, "sha512-j2uHJfK8TE7SxL6cd+kQixwkl8H5ASBkQfOEVjf3d3XS1Wixy4UO8WudDYNVriu1KuKzsNnkkSC/jTyHNXNrcQ=="], + "supercluster": ["supercluster@8.0.1", "", { "dependencies": { "kdbush": "^4.0.2" } }, "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -1113,6 +1324,8 @@ "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tar": ["tar@7.5.7", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ=="], + "three": ["three@0.182.0", "", {}, "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ=="], "three-bvh-csg": ["three-bvh-csg@0.0.17", "", { "peerDependencies": { "three": ">=0.151.0", "three-mesh-bvh": ">=0.6.6" } }, "sha512-iEkHDF8GRfGM6593Cuw8SnF1vfENCp46gIAtRzuL4nGXGWPcR1sbTBwM9ptDONb5twqlZp5WkAKya5aBKe2qcA=="], @@ -1187,11 +1400,13 @@ "web": ["web@workspace:apps/editor"], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "webgl-constants": ["webgl-constants@1.1.1", "", {}, "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="], "webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], @@ -1203,8 +1418,14 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "write-file-atomic": ["write-file-atomic@7.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg=="], + "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], @@ -1215,6 +1436,8 @@ "zustand": ["zustand@5.0.10", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], @@ -1259,14 +1482,20 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "gel/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="], + "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "stats-gl/three": ["three@0.170.0", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="], @@ -1279,8 +1508,54 @@ "web/@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], } } diff --git a/package.json b/package.json index fbc5bb3d..276a913f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "format:check": "biome format", "check": "biome check", "check:fix": "biome check --write", - "check-types": "turbo run check-types" + "check-types": "turbo run check-types", + "db:start": "cd packages/db && supabase start", + "db:stop": "cd packages/db && supabase stop", + "db:reset": "cd packages/db && supabase db reset", + "db:status": "cd packages/db && supabase status" }, "dependencies": { "@react-three/drei": "^10.7.7", @@ -22,6 +26,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.3.13", + "supabase": "2.75.3", "turbo": "^2.8.1", "typescript": "5.9.2", "ultracite": "^7.1.1" diff --git a/packages/auth/README.md b/packages/auth/README.md new file mode 100644 index 00000000..bc7ffd7c --- /dev/null +++ b/packages/auth/README.md @@ -0,0 +1,142 @@ +# @pascal-app/auth + +Authentication package for Pascal Editor using Better Auth. + +## Features + +- **Magic Link Authentication** - Passwordless email-based authentication +- **Session Management** - Secure cookie-based sessions +- **Supabase Integration** - Uses Supabase as the database adapter +- **Type-safe** - Full TypeScript support with type inference + +## Setup + +### 1. Configure environment variables + +Add these to `apps/editor/.env.local`: + +```bash +# Better Auth +BETTER_AUTH_SECRET= +BETTER_AUTH_URL=http://localhost:3000 +``` + +Generate a secret for `BETTER_AUTH_SECRET`: + +```bash +openssl rand -base64 32 +``` + +### 2. Ensure database is running + +Make sure you have Supabase running with the auth tables created. See `@pascal-app/db` package for setup. + +## Usage + +### Server-side (API routes, server actions) + +```typescript +import { auth } from '@pascal-app/auth/server' + +// Get session in server component or action +const session = await auth.api.getSession({ headers: request.headers }) + +if (!session) { + return { error: 'Unauthorized' } +} + +// Access user data +const userId = session.user.id +const email = session.user.email +``` + +### Client-side (React components) + +```typescript +'use client' + +import { authClient } from '@pascal-app/auth/client' + +function SignInButton() { + const { signIn } = authClient + + const handleSignIn = async (email: string) => { + await signIn.magicLink({ + email, + callbackURL: '/dashboard', + }) + } + + return +} +``` + +### Using the auth hook + +```typescript +'use client' + +import { authClient } from '@pascal-app/auth/client' + +function Profile() { + const { data: session, isPending } = authClient.useSession() + + if (isPending) return
Loading...
+ if (!session) return
Not signed in
+ + return
Signed in as {session.user.email}
+} +``` + +## API Routes + +The auth package requires an API route handler in your Next.js app: + +```typescript +// app/api/auth/[...all]/route.ts +import { auth } from '@pascal-app/auth/server' +import { toNextJsHandler } from 'better-auth/next-js' + +export const { GET, POST } = toNextJsHandler(auth) +``` + +This handles all Better Auth endpoints: +- `/api/auth/sign-in/magic-link` - Send magic link +- `/api/auth/sign-in/magic-link/verify` - Verify magic link +- `/api/auth/sign-out` - Sign out +- `/api/auth/session` - Get session +- And more... + +## Email Configuration + +By default, magic links are logged to the console. To send actual emails, you'll need to configure an email provider in `packages/auth/src/server.ts`: + +```typescript +magicLink({ + sendMagicLink: async ({ email, url }) => { + // Use Resend, SendGrid, or your preferred email service + await sendEmail({ + to: email, + subject: 'Sign in to Pascal Editor', + html: `Click here to sign in: ${url}`, + }) + }, +}) +``` + +## Database Schema + +The auth package requires these tables (created by `@pascal-app/db` migrations): + +- `users` - User accounts +- `sessions` - Active sessions +- `accounts` - OAuth provider accounts (for future use) +- `verification_tokens` - Magic link tokens + +## Security + +- Session cookies are httpOnly and secure (in production) +- Sessions expire after 7 days +- Session cache is enabled for 5 minutes to reduce database queries +- Magic link tokens expire after 15 minutes +- All sensitive operations require valid session tokens diff --git a/packages/auth/package.json b/packages/auth/package.json new file mode 100644 index 00000000..c354edfc --- /dev/null +++ b/packages/auth/package.json @@ -0,0 +1,24 @@ +{ + "name": "@pascal-app/auth", + "version": "0.0.0", + "type": "module", + "exports": { + "./server": { + "types": "./src/server.ts", + "default": "./src/server.ts" + }, + "./client": { + "types": "./src/client.ts", + "default": "./src/client.ts" + } + }, + "dependencies": { + "@pascal-app/db": "*", + "better-auth": "^1.4.18", + "resend": "^4.0.1" + }, + "devDependencies": { + "@repo/typescript-config": "*", + "typescript": "5.9.2" + } +} diff --git a/packages/auth/src/client.ts b/packages/auth/src/client.ts new file mode 100644 index 00000000..7994f42d --- /dev/null +++ b/packages/auth/src/client.ts @@ -0,0 +1,37 @@ +import { magicLinkClient } from 'better-auth/client/plugins' +import { createAuthClient } from 'better-auth/react' + +/** + * Get the auth base URL + * In development: use the editor URL (localhost:3000) + * In production: use the same origin + */ +function getAuthURL(): string { + if (typeof window !== 'undefined') { + return window.location.origin + } + + // SSR fallback + return process.env.BETTER_AUTH_URL || 'http://localhost:3000' +} + +/** + * Auth client instance + * Configured for magic link authentication + */ +export const authClient = createAuthClient({ + baseURL: getAuthURL(), + plugins: [magicLinkClient()], +}) + +/** + * Export types for use in components + */ +export type AuthState = { + user: (typeof authClient)['$Infer']['Session']['user'] | null + session: (typeof authClient)['$Infer']['Session']['session'] | null + isLoading: boolean +} + +export type User = NonNullable +export type Session = NonNullable diff --git a/packages/auth/src/server.ts b/packages/auth/src/server.ts new file mode 100644 index 00000000..73988b7b --- /dev/null +++ b/packages/auth/src/server.ts @@ -0,0 +1,91 @@ +import { db, schema } from '@pascal-app/db' +import { betterAuth } from 'better-auth' +import { drizzleAdapter } from 'better-auth/adapters/drizzle' +import { magicLink } from 'better-auth/plugins' +import { Resend } from 'resend' + +if (!process.env.BETTER_AUTH_SECRET) { + throw new Error( + 'Missing BETTER_AUTH_SECRET environment variable. Generate one with: openssl rand -base64 32', + ) +} + +if (!process.env.BETTER_AUTH_URL) { + throw new Error( + 'Missing BETTER_AUTH_URL environment variable. Set it to your app URL (e.g., http://localhost:3000)', + ) +} + +// Initialize Resend for email sending +const resend = process.env.RESEND_API_KEY ? new Resend(process.env.RESEND_API_KEY) : null + +/** + * Better Auth server instance + * Configured with PostgreSQL database (Supabase) and magic link authentication + */ +export const auth = betterAuth({ + database: drizzleAdapter(db, { + provider: 'pg', + usePlural: true, + schema, + }), + advanced: { + database: { + generateId: false, // Use our prefixed nanoid IDs from schema + }, + }, + secret: process.env.BETTER_AUTH_SECRET, + baseURL: process.env.BETTER_AUTH_URL, + plugins: [ + magicLink({ + sendMagicLink: async ({ email, url }) => { + if (!resend) { + console.log(`[DEV] Magic link for ${email}: ${url}`) + return + } + + try { + await resend.emails.send({ + from: 'Pascal ', + to: email, + subject: 'Sign in to Pascal Editor', + html: ` +
+

Sign in to Pascal Editor

+

Click the button below to sign in to your account:

+ + Sign In + +

This link will expire in 5 minutes.

+

If you didn't request this email, you can safely ignore it.

+
+ `, + }) + console.log(`✓ Magic link email sent to ${email}`) + } catch (error) { + console.error('Failed to send magic link email:', error) + throw error + } + }, + }), + ], + session: { + expiresIn: 60 * 60 * 24 * 7, // 7 days + updateAge: 60 * 60 * 24, // 1 day + cookieCache: { + enabled: true, + maxAge: 5 * 60, // 5 minutes + }, + additionalFields: { + activePropertyId: { + type: 'string', + }, + }, + }, +}) + +/** + * Type helpers for better-auth session + */ +export type Session = typeof auth.$Infer.Session.session +export type User = typeof auth.$Infer.Session.user diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json new file mode 100644 index 00000000..c6daff18 --- /dev/null +++ b/packages/auth/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@repo/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/db/README.md b/packages/db/README.md new file mode 100644 index 00000000..34bad2f9 --- /dev/null +++ b/packages/db/README.md @@ -0,0 +1,127 @@ +# @pascal-app/db + +Database package for Pascal Editor with Supabase. + +## Setup + +### 1. Install Dependencies + +From the monorepo root: + +```bash +bun install +``` + +This installs Supabase CLI as a dev dependency. + +### 2. Start Supabase locally + +From the monorepo root: + +```bash +bun db:start +``` + +This will start a local Supabase instance with PostgreSQL, PostgREST, and Studio. + +### 3. Check Supabase status + +```bash +bun db:status +``` + +You'll see output like: + +``` +API URL: http://127.0.0.1:54321 +DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres +Studio URL: http://127.0.0.1:54323 +Anon key: eyJh... +Service role key: eyJh... +``` + +### 4. Configure environment variables + +Add these to `apps/editor/.env.local`: + +```bash +# Supabase +NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 +NEXT_PUBLIC_SUPABASE_ANON_KEY= +SUPABASE_SERVICE_ROLE_KEY= + +# Better Auth +BETTER_AUTH_SECRET= +BETTER_AUTH_URL=http://localhost:3000 +``` + +Generate a secret for `BETTER_AUTH_SECRET`: + +```bash +openssl rand -base64 32 +``` + +## Migrations + +Migrations are located in `supabase/migrations/`. + +### Apply migrations + +```bash +bun db:reset # Resets and applies all migrations +``` + +### Create a new migration + +```bash +cd packages/db +bunx supabase migration new +``` + +## Database Schema + +### Auth Tables (Better Auth) + +- `users` - User accounts +- `sessions` - Active sessions +- `accounts` - OAuth provider accounts +- `verification_tokens` - Magic link tokens + +### Application Tables + +- `properties` - User properties +- `properties_addresses` - Property addresses with Google Maps data +- `properties_models` - Scene graph models (versions) + +## Usage + +### Client-side (with RLS) + +```typescript +import { supabase } from '@pascal-app/db/client' + +// RLS policies automatically filter by authenticated user +const { data } = await supabase.from('properties').select('*') +``` + +### Server-side (service role) + +```typescript +import { supabaseAdmin } from '@pascal-app/db/server' + +// Bypasses RLS - you must manually filter by user_id +const { data } = await supabaseAdmin + .from('properties') + .select('*') + .eq('owner_id', userId) +``` + +## Supabase Studio + +Access the local Supabase Studio at: http://127.0.0.1:54323 + +Use this to: +- Browse tables and data +- Run SQL queries +- View logs +- Manage RLS policies diff --git a/packages/db/package.json b/packages/db/package.json new file mode 100644 index 00000000..b9a79c7f --- /dev/null +++ b/packages/db/package.json @@ -0,0 +1,35 @@ +{ + "name": "@pascal-app/db", + "version": "0.0.0", + "type": "module", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + }, + "./client": { + "types": "./src/client.ts", + "default": "./src/client.ts" + }, + "./server": { + "types": "./src/server.ts", + "default": "./src/server.ts" + }, + "./types": { + "types": "./src/types.ts", + "default": "./src/types.ts" + } + }, + "dependencies": { + "@supabase/supabase-js": "^2.95.3", + "drizzle-orm": "^0.39.0", + "drizzle-zod": "^0.5.1", + "nanoid": "^5.0.9", + "postgres": "^3.4.5" + }, + "devDependencies": { + "@repo/typescript-config": "*", + "drizzle-kit": "^0.30.0", + "typescript": "5.9.2" + } +} diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts new file mode 100644 index 00000000..6e328bca --- /dev/null +++ b/packages/db/src/client.ts @@ -0,0 +1,17 @@ +import { createClient } from '@supabase/supabase-js' +import type { Database } from './types' + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY + +if (!supabaseUrl || !supabaseAnonKey) { + throw new Error( + 'Missing Supabase environment variables. Add NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY to your .env.local file.' + ) +} + +/** + * Supabase client for client-side use with anon key + * Uses Row Level Security (RLS) policies + */ +export const supabase = createClient(supabaseUrl, supabaseAnonKey) diff --git a/packages/db/src/drizzle.ts b/packages/db/src/drizzle.ts new file mode 100644 index 00000000..0daa4d63 --- /dev/null +++ b/packages/db/src/drizzle.ts @@ -0,0 +1,17 @@ +import { drizzle } from 'drizzle-orm/postgres-js' +import postgres from 'postgres' +import * as schema from './schema' + +if (!process.env.POSTGRES_URL) { + throw new Error( + 'Missing POSTGRES_URL environment variable. Add your Supabase database connection string.', + ) +} + +// Create postgres connection +const client = postgres(process.env.POSTGRES_URL) + +// Create drizzle instance +export const db = drizzle(client, { schema }) + +export type Database = typeof db diff --git a/packages/db/src/helpers.ts b/packages/db/src/helpers.ts new file mode 100644 index 00000000..58967300 --- /dev/null +++ b/packages/db/src/helpers.ts @@ -0,0 +1,56 @@ +import { type AnyColumn, type SQL, sql } from 'drizzle-orm' +import { text, timestamp } from 'drizzle-orm/pg-core' +import { customAlphabet } from 'nanoid' + +const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +const nanoid = customAlphabet(alphabet, 16) + +/** + * Generate a unique ID with optional prefix + * @example createId('user') => 'user_Abc123...' + */ +export const createId = (prefix?: string) => { + const id = nanoid() + return prefix ? `${prefix}_${id}` : id +} + +/** + * Primary key column with auto-generated prefixed ID + * @example id('user') => text('id').notNull().primaryKey().$defaultFn(() => createId('user')) + */ +export const id = (prefix?: string) => + text('id') + .notNull() + .primaryKey() + .$defaultFn(() => createId(prefix)) + .$type() + +export const createdAt = timestamp('created_at', { withTimezone: true }).notNull().defaultNow() + +export const updatedAt = timestamp('updated_at', { withTimezone: true }) + .notNull() + .defaultNow() + .$onUpdate(() => new Date()) + +export const deletedAt = timestamp('deleted_at', { withTimezone: true }) + +/** + * Standard timestamp columns for created_at and updated_at + */ +export const timestamps = { + createdAt, + updatedAt, +} + +// Alias for backwards compatibility with existing code +export const timestampsColumns = timestamps + +export const timestampsColumnsSoftDelete = { + ...timestampsColumns, + deletedAt, +} + +/** + * SQL helper for case-insensitive comparison + */ +export const lower = (column: AnyColumn): SQL => sql`lower(${column})` diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts new file mode 100644 index 00000000..bef886e0 --- /dev/null +++ b/packages/db/src/index.ts @@ -0,0 +1,24 @@ +/** + * Database package + * Exports Supabase clients, Drizzle ORM, and types + */ + +export { supabase } from './client' +export { supabaseAdmin } from './server' +export type { Database as SupabaseDatabase } from './types' + +// Drizzle exports +export { type Database, db } from './drizzle' +export * from './schema' + +import * as dbSchema from './schema' +export const schema = dbSchema +export { + createId, + deletedAt, + id, + lower, + timestamps, + timestampsColumns, + timestampsColumnsSoftDelete, +} from './helpers' diff --git a/packages/db/src/schema/auth/accounts.ts b/packages/db/src/schema/auth/accounts.ts new file mode 100644 index 00000000..c3650555 --- /dev/null +++ b/packages/db/src/schema/auth/accounts.ts @@ -0,0 +1,31 @@ +import { pgTable } from 'drizzle-orm/pg-core' +import { createInsertSchema, createSelectSchema } from 'drizzle-zod' +import { id, timestamps } from '../../helpers' +import { users } from './users' + +export const accounts = pgTable('auth_accounts', (t) => ({ + id: id('account'), + userId: t + .text('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + providerId: t.text('provider_id').notNull(), + accountId: t.text('account_id').notNull(), + password: t.text('password'), + accessToken: t.text('access_token'), + refreshToken: t.text('refresh_token'), + idToken: t.text('id_token'), + accessTokenExpiresAt: t.timestamp('access_token_expires_at', { + withTimezone: true, + }), + refreshTokenExpiresAt: t.timestamp('refresh_token_expires_at', { + withTimezone: true, + }), + scope: t.text('scope'), + ...timestamps, +})).enableRLS() + +export type Account = typeof accounts.$inferSelect +export type NewAccount = typeof accounts.$inferInsert +export const insertAccountSchema = createInsertSchema(accounts) +export const selectAccountSchema = createSelectSchema(accounts) diff --git a/packages/db/src/schema/auth/jwks.ts b/packages/db/src/schema/auth/jwks.ts new file mode 100644 index 00000000..9f5cb2a0 --- /dev/null +++ b/packages/db/src/schema/auth/jwks.ts @@ -0,0 +1,15 @@ +import { pgTable } from 'drizzle-orm/pg-core' +import { createInsertSchema, createSelectSchema } from 'drizzle-zod' +import { createdAt, id } from '../../helpers' + +export const jwks = pgTable('auth_jwks', (t) => ({ + id: id('jwks'), + publicKey: t.text('public_key').notNull(), + privateKey: t.text('private_key').notNull(), + createdAt, +})).enableRLS() + +export type Jwks = typeof jwks.$inferSelect +export type NewJwks = typeof jwks.$inferInsert +export const insertJwksSchema = createInsertSchema(jwks) +export const selectJwksSchema = createSelectSchema(jwks) diff --git a/packages/db/src/schema/auth/sessions.ts b/packages/db/src/schema/auth/sessions.ts new file mode 100644 index 00000000..bfd83511 --- /dev/null +++ b/packages/db/src/schema/auth/sessions.ts @@ -0,0 +1,28 @@ +import { pgTable } from 'drizzle-orm/pg-core' +import { createInsertSchema, createSelectSchema } from 'drizzle-zod' +import { id, timestamps } from '../../helpers' +import { users } from './users' + +export const sessions = pgTable('auth_sessions', (t) => ({ + id: id('session'), + userId: t + .text('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + expiresAt: t.timestamp('expires_at', { withTimezone: true }), + token: t.text('token').notNull(), + ipAddress: t.text('ip_address'), + userAgent: t.text('user_agent'), + // Custom: active property for the session context + activePropertyId: t.text('active_property_id'), + // Admin plugin support: tracks who is impersonating this session + impersonatedBy: t + .text('impersonated_by') + .references(() => users.id, { onDelete: 'set null' }), + ...timestamps, +})).enableRLS() + +export type Session = typeof sessions.$inferSelect +export type NewSession = typeof sessions.$inferInsert +export const insertSessionSchema = createInsertSchema(sessions) +export const selectSessionSchema = createSelectSchema(sessions) diff --git a/packages/db/src/schema/auth/users.ts b/packages/db/src/schema/auth/users.ts new file mode 100644 index 00000000..e01799f9 --- /dev/null +++ b/packages/db/src/schema/auth/users.ts @@ -0,0 +1,28 @@ +import { pgEnum, pgTable, uniqueIndex } from 'drizzle-orm/pg-core' +import { createInsertSchema, createSelectSchema } from 'drizzle-zod' +import { id, lower, timestampsColumns } from '../../helpers' + +export const USER_ROLES = ['user', 'admin'] as const +export const userRoles = pgEnum('auth_user_roles', USER_ROLES) + +export const users = pgTable( + 'auth_users', + (t) => ({ + id: id('user'), + email: t.text('email').notNull(), + emailVerified: t.boolean('email_verified').notNull().default(false), + name: t.text('name').notNull(), + image: t.text('image'), + role: userRoles('role').notNull().default('user'), + banned: t.boolean('banned').notNull().default(false), + banReason: t.text('ban_reason'), + banExpires: t.timestamp('ban_expires', { withTimezone: true }), + ...timestampsColumns, + }), + (t) => [uniqueIndex('email_unique_index').on(lower(t.email))], +).enableRLS() + +export type User = typeof users.$inferSelect +export type NewUser = typeof users.$inferInsert +export const insertUserSchema = createInsertSchema(users) +export const selectUserSchema = createSelectSchema(users) diff --git a/packages/db/src/schema/auth/verifications.ts b/packages/db/src/schema/auth/verifications.ts new file mode 100644 index 00000000..a360f355 --- /dev/null +++ b/packages/db/src/schema/auth/verifications.ts @@ -0,0 +1,22 @@ +import { index, pgTable } from 'drizzle-orm/pg-core' +import { createInsertSchema, createSelectSchema } from 'drizzle-zod' +import { id, timestamps } from '../../helpers' + +export const verifications = pgTable( + 'auth_verifications', + (t) => ({ + id: id('verification'), + value: t.text('value').notNull(), + identifier: t.text('identifier').notNull(), + expiresAt: t.timestamp('expires_at', { + withTimezone: true, + }), + ...timestamps, + }), + (t) => [index('verification_identifier_index').on(t.identifier)], +).enableRLS() + +export type Verification = typeof verifications.$inferSelect +export type NewVerification = typeof verifications.$inferInsert +export const insertVerificationSchema = createInsertSchema(verifications) +export const selectVerificationSchema = createSelectSchema(verifications) diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts new file mode 100644 index 00000000..656cb454 --- /dev/null +++ b/packages/db/src/schema/index.ts @@ -0,0 +1,11 @@ +// Auth tables +export * from './auth/accounts' +export * from './auth/jwks' +export * from './auth/sessions' +export * from './auth/users' +export * from './auth/verifications' + +// Property tables +export * from './properties/addresses' +export * from './properties/models' +export * from './properties/properties' diff --git a/packages/db/src/schema/properties/addresses.ts b/packages/db/src/schema/properties/addresses.ts new file mode 100644 index 00000000..ea4925f3 --- /dev/null +++ b/packages/db/src/schema/properties/addresses.ts @@ -0,0 +1,53 @@ +import { pgTable, unique } from 'drizzle-orm/pg-core' +import { z } from 'zod' +import { id, timestampsColumns } from '../../helpers' + +export const addresses = pgTable( + 'properties_addresses', + (t) => ({ + id: id('address'), + streetNumber: t.text('street_number'), + route: t.text('route'), + routeShort: t.text('route_short'), + neighborhood: t.text('neighborhood'), + city: t.text('city'), + county: t.text('county'), + state: t.text('state'), + stateLong: t.text('state_long'), + postalCode: t.text('postal_code'), + postalCodeSuffix: t.text('postal_code_suffix'), + country: t.text('country'), + countryLong: t.text('country_long'), + latitude: t.numeric('latitude'), + longitude: t.numeric('longitude'), + rawJson: t.jsonb('raw_json'), + ...timestampsColumns, + }), + (t) => [ + // Unique constraint on core address components to prevent duplicates + unique('address_components_unique').on(t.streetNumber, t.route, t.city, t.state, t.postalCode), + ], +).enableRLS() + +// Create address schema manually to avoid issues with generated columns +export const addressSchema = z.object({ + streetNumber: z.string().optional(), + route: z.string().optional(), + routeShort: z.string().optional(), + neighborhood: z.string().optional(), + city: z.string().optional(), + county: z.string().optional(), + state: z.string().optional(), + stateLong: z.string().optional(), + postalCode: z.string().optional(), + postalCodeSuffix: z.string().optional(), + country: z.string().default('US'), + countryLong: z.string().optional(), + latitude: z.string().optional(), + longitude: z.string().optional(), + rawJson: z.record(z.string(), z.unknown()).optional(), +}) + +export type AddressSchema = z.infer +export type Address = typeof addresses.$inferSelect +export type NewAddress = typeof addresses.$inferInsert diff --git a/packages/db/src/schema/properties/models.ts b/packages/db/src/schema/properties/models.ts new file mode 100644 index 00000000..2f6a51ab --- /dev/null +++ b/packages/db/src/schema/properties/models.ts @@ -0,0 +1,31 @@ +import { relations } from 'drizzle-orm' +import { pgTable } from 'drizzle-orm/pg-core' +import { createInsertSchema, createSelectSchema } from 'drizzle-zod' +import { id, timestampsColumnsSoftDelete } from '../../helpers' +import { properties } from './properties' + +export const models = pgTable('properties_models', (t) => ({ + id: id('model'), + name: t.text('name'), + version: t.integer('version').default(1), + description: t.text('description'), + draft: t.boolean('draft').default(true), + propertyId: t + .text('property_id') + .references(() => properties.id, { onDelete: 'set null' }), + sceneGraph: t.jsonb('scene_graph'), + metadata: t.jsonb('metadata'), + ...timestampsColumnsSoftDelete, +})).enableRLS() + +export const modelsRelations = relations(models, ({ one }) => ({ + property: one(properties, { + fields: [models.propertyId], + references: [properties.id], + }), +})) + +export type Model = typeof models.$inferSelect +export type NewModel = typeof models.$inferInsert +export const insertModelSchema = createInsertSchema(models) +export const selectModelSchema = createSelectSchema(models) diff --git a/packages/db/src/schema/properties/properties.ts b/packages/db/src/schema/properties/properties.ts new file mode 100644 index 00000000..5e0dc416 --- /dev/null +++ b/packages/db/src/schema/properties/properties.ts @@ -0,0 +1,44 @@ +import { relations } from 'drizzle-orm' +import { index, pgTable } from 'drizzle-orm/pg-core' +import { createInsertSchema, createSelectSchema } from 'drizzle-zod' +import { id, timestampsColumns } from '../../helpers' +import { users } from '../auth/users' +import { addresses } from './addresses' + +export const properties = pgTable( + 'properties', + (t) => ({ + id: id('property'), + name: t.text('name'), + addressId: t + .text('address_id') + .references(() => addresses.id, { onDelete: 'set null' }) + .unique(), + ownerId: t + .text('owner_id') + .references(() => users.id, { onDelete: 'set null' }), + detailsJson: t.jsonb('details_json'), + metadata: t.jsonb('metadata'), + ...timestampsColumns, + }), + (t) => [ + index('property_address_idx').on(t.addressId), + index('property_owner_idx').on(t.ownerId), + ], +).enableRLS() + +export const propertiesRelations = relations(properties, ({ one }) => ({ + address: one(addresses, { + fields: [properties.addressId], + references: [addresses.id], + }), + owner: one(users, { + fields: [properties.ownerId], + references: [users.id], + }), +})) + +export type Property = typeof properties.$inferSelect +export type NewProperty = typeof properties.$inferInsert +export const insertPropertySchema = createInsertSchema(properties) +export const selectPropertySchema = createSelectSchema(properties) diff --git a/packages/db/src/server.ts b/packages/db/src/server.ts new file mode 100644 index 00000000..1de4837b --- /dev/null +++ b/packages/db/src/server.ts @@ -0,0 +1,23 @@ +import { createClient } from '@supabase/supabase-js' +import type { Database } from './types' + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL +const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY + +if (!supabaseUrl || !supabaseServiceRoleKey) { + throw new Error( + 'Missing Supabase environment variables. Add NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY to your .env.local file.' + ) +} + +/** + * Supabase client for server-side use with service role key + * Bypasses Row Level Security (RLS) - use with caution + * Always filter by user_id to enforce permissions + */ +export const supabaseAdmin = createClient(supabaseUrl, supabaseServiceRoleKey, { + auth: { + persistSession: false, + autoRefreshToken: false, + }, +}) diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts new file mode 100644 index 00000000..7e246c17 --- /dev/null +++ b/packages/db/src/types.ts @@ -0,0 +1,124 @@ +/** + * Database types for Supabase + * Generated from database schema + */ + +export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[] + +export interface Database { + public: { + Tables: { + properties: { + Row: { + id: string + name: string + owner_id: string + created_at: string + updated_at: string + } + Insert: { + id?: string + name: string + owner_id: string + created_at?: string + updated_at?: string + } + Update: { + id?: string + name?: string + owner_id?: string + created_at?: string + updated_at?: string + } + } + properties_addresses: { + Row: { + id: string + property_id: string + formatted_address: string + street_number: string | null + route: string | null + locality: string | null + administrative_area_level_1: string | null + administrative_area_level_2: string | null + country: string | null + postal_code: string | null + latitude: number | null + longitude: number | null + place_id: string | null + created_at: string + updated_at: string + } + Insert: { + id?: string + property_id: string + formatted_address: string + street_number?: string | null + route?: string | null + locality?: string | null + administrative_area_level_1?: string | null + administrative_area_level_2?: string | null + country?: string | null + postal_code?: string | null + latitude?: number | null + longitude?: number | null + place_id?: string | null + created_at?: string + updated_at?: string + } + Update: { + id?: string + property_id?: string + formatted_address?: string + street_number?: string | null + route?: string | null + locality?: string | null + administrative_area_level_1?: string | null + administrative_area_level_2?: string | null + country?: string | null + postal_code?: string | null + latitude?: number | null + longitude?: number | null + place_id?: string | null + created_at?: string + updated_at?: string + } + } + properties_models: { + Row: { + id: string + property_id: string + name: string + version: number + draft: boolean + scene_graph: Json | null + created_at: string + updated_at: string + } + Insert: { + id?: string + property_id: string + name: string + version?: number + draft?: boolean + scene_graph?: Json | null + created_at?: string + updated_at?: string + } + Update: { + id?: string + property_id?: string + name?: string + version?: number + draft?: boolean + scene_graph?: Json | null + created_at?: string + updated_at?: string + } + } + } + Views: {} + Functions: {} + Enums: {} + } +} diff --git a/packages/db/supabase/.branches/_current_branch b/packages/db/supabase/.branches/_current_branch new file mode 100644 index 00000000..88d050b1 --- /dev/null +++ b/packages/db/supabase/.branches/_current_branch @@ -0,0 +1 @@ +main \ No newline at end of file diff --git a/packages/db/supabase/.temp/cli-latest b/packages/db/supabase/.temp/cli-latest new file mode 100644 index 00000000..1dd61787 --- /dev/null +++ b/packages/db/supabase/.temp/cli-latest @@ -0,0 +1 @@ +v2.75.0 \ No newline at end of file diff --git a/packages/db/supabase/config.toml b/packages/db/supabase/config.toml new file mode 100644 index 00000000..6a42a735 --- /dev/null +++ b/packages/db/supabase/config.toml @@ -0,0 +1,38 @@ +# Supabase local development configuration +# This file is used by `supabase start` for local development + +project_id = "pascal-editor" + +[api] +enabled = true +port = 54321 +schemas = ["public"] +extra_search_path = ["public"] +max_rows = 1000 + +[db] +port = 54322 +shadow_port = 54320 +major_version = 17 + +[studio] +enabled = true +port = 54323 + +[inbucket] +enabled = true +port = 54324 +smtp_port = 54325 +pop3_port = 54326 + +[auth] +enabled = true +site_url = "http://localhost:3000" +additional_redirect_urls = ["http://localhost:3000"] +jwt_expiry = 3600 +enable_signup = true + +[auth.email] +enable_signup = true +double_confirm_changes = false +enable_confirmations = false diff --git a/packages/db/supabase/migrations/20240211000001_create_auth_tables.sql b/packages/db/supabase/migrations/20240211000001_create_auth_tables.sql new file mode 100644 index 00000000..efb99103 --- /dev/null +++ b/packages/db/supabase/migrations/20240211000001_create_auth_tables.sql @@ -0,0 +1,80 @@ +-- Better Auth tables (copied from working monorepo) +-- Using auth_ prefix and snake_case columns + +CREATE TYPE "public"."auth_user_roles" AS ENUM('user', 'admin'); + +CREATE TABLE "auth_accounts" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "provider_id" text NOT NULL, + "account_id" text NOT NULL, + "password" text, + "access_token" text, + "refresh_token" text, + "id_token" text, + "access_token_expires_at" timestamp with time zone, + "refresh_token_expires_at" timestamp with time zone, + "scope" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); + +ALTER TABLE "auth_accounts" ENABLE ROW LEVEL SECURITY; + +CREATE TABLE "auth_jwks" ( + "id" text PRIMARY KEY NOT NULL, + "public_key" text NOT NULL, + "private_key" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); + +ALTER TABLE "auth_jwks" ENABLE ROW LEVEL SECURITY; + +CREATE TABLE "auth_sessions" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "expires_at" timestamp with time zone, + "token" text NOT NULL, + "ip_address" text, + "user_agent" text, + "impersonated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); + +ALTER TABLE "auth_sessions" ENABLE ROW LEVEL SECURITY; + +CREATE TABLE "auth_users" ( + "id" text PRIMARY KEY NOT NULL, + "email" text NOT NULL, + "email_verified" boolean DEFAULT false NOT NULL, + "name" text NOT NULL, + "image" text, + "role" "auth_user_roles" DEFAULT 'user' NOT NULL, + "banned" boolean DEFAULT false NOT NULL, + "ban_reason" text, + "ban_expires" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); + +ALTER TABLE "auth_users" ENABLE ROW LEVEL SECURITY; + +CREATE TABLE "auth_verifications" ( + "id" text PRIMARY KEY NOT NULL, + "value" text NOT NULL, + "identifier" text NOT NULL, + "expires_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); + +ALTER TABLE "auth_verifications" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "auth_accounts" ADD CONSTRAINT "auth_accounts_user_id_auth_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_users"("id") ON DELETE cascade ON UPDATE no action; + +ALTER TABLE "auth_sessions" ADD CONSTRAINT "auth_sessions_user_id_auth_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_users"("id") ON DELETE cascade ON UPDATE no action; + +ALTER TABLE "auth_sessions" ADD CONSTRAINT "auth_sessions_impersonated_by_auth_users_id_fk" FOREIGN KEY ("impersonated_by") REFERENCES "public"."auth_users"("id") ON DELETE set null ON UPDATE no action; + +CREATE UNIQUE INDEX "email_unique_index" ON "auth_users" USING btree (lower("email")); diff --git a/packages/db/supabase/migrations/20240211000002_create_properties_tables.sql b/packages/db/supabase/migrations/20240211000002_create_properties_tables.sql new file mode 100644 index 00000000..aa17fbdd --- /dev/null +++ b/packages/db/supabase/migrations/20240211000002_create_properties_tables.sql @@ -0,0 +1,165 @@ +-- Properties tables +-- Simple property management without organizations + +-- Properties addresses table (must be created first due to foreign key) +CREATE TABLE IF NOT EXISTS properties_addresses ( + id TEXT PRIMARY KEY, + street_number TEXT, + route TEXT, + route_short TEXT, + neighborhood TEXT, + city TEXT, + county TEXT, + state TEXT, + state_long TEXT, + postal_code TEXT, + postal_code_suffix TEXT, + country TEXT, + country_long TEXT, + latitude NUMERIC, + longitude NUMERIC, + raw_json JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(street_number, route, city, state, postal_code) +); + +CREATE INDEX IF NOT EXISTS idx_properties_addresses_city_state ON properties_addresses(city, state); + +-- Properties table +CREATE TABLE IF NOT EXISTS properties ( + id TEXT PRIMARY KEY, + name TEXT, + address_id TEXT UNIQUE REFERENCES properties_addresses(id) ON DELETE SET NULL, + owner_id TEXT REFERENCES auth_users(id) ON DELETE SET NULL, + details_json JSONB, + metadata JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_properties_owner_id ON properties(owner_id); +CREATE INDEX IF NOT EXISTS idx_properties_address_id ON properties(address_id); + +-- Properties models table (scene graphs) +CREATE TABLE IF NOT EXISTS properties_models ( + id TEXT PRIMARY KEY, + name TEXT, + version INTEGER DEFAULT 1, + description TEXT, + draft BOOLEAN DEFAULT true, + property_id TEXT REFERENCES properties(id) ON DELETE SET NULL, + scene_graph JSONB, + metadata JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_properties_models_property_id ON properties_models(property_id); +CREATE INDEX IF NOT EXISTS idx_properties_models_version ON properties_models(property_id, version DESC); + +-- Enable Row Level Security (RLS) +ALTER TABLE properties ENABLE ROW LEVEL SECURITY; +ALTER TABLE properties_addresses ENABLE ROW LEVEL SECURITY; +ALTER TABLE properties_models ENABLE ROW LEVEL SECURITY; + +-- RLS Policies for properties table +CREATE POLICY "Users can view their own properties" + ON properties FOR SELECT + USING (owner_id = current_setting('app.user_id', true)::TEXT OR owner_id IS NULL); + +CREATE POLICY "Users can insert their own properties" + ON properties FOR INSERT + WITH CHECK (owner_id = current_setting('app.user_id', true)::TEXT OR owner_id IS NULL); + +CREATE POLICY "Users can update their own properties" + ON properties FOR UPDATE + USING (owner_id = current_setting('app.user_id', true)::TEXT OR owner_id IS NULL); + +CREATE POLICY "Users can delete their own properties" + ON properties FOR DELETE + USING (owner_id = current_setting('app.user_id', true)::TEXT OR owner_id IS NULL); + +-- RLS Policies for properties_addresses table +-- Addresses table doesn't have property_id, so we'll allow all authenticated users +CREATE POLICY "Authenticated users can view all addresses" + ON properties_addresses FOR SELECT + USING (true); + +CREATE POLICY "Authenticated users can insert addresses" + ON properties_addresses FOR INSERT + WITH CHECK (true); + +CREATE POLICY "Authenticated users can update addresses" + ON properties_addresses FOR UPDATE + USING (true); + +CREATE POLICY "Authenticated users can delete addresses" + ON properties_addresses FOR DELETE + USING (true); + +-- RLS Policies for properties_models table +CREATE POLICY "Users can view models of their own properties" + ON properties_models FOR SELECT + USING ( + EXISTS ( + SELECT 1 FROM properties + WHERE properties.id = properties_models.property_id + AND properties.owner_id = current_setting('app.user_id', true)::TEXT + ) + ); + +CREATE POLICY "Users can insert models for their own properties" + ON properties_models FOR INSERT + WITH CHECK ( + EXISTS ( + SELECT 1 FROM properties + WHERE properties.id = properties_models.property_id + AND properties.owner_id = current_setting('app.user_id', true)::TEXT + ) + ); + +CREATE POLICY "Users can update models of their own properties" + ON properties_models FOR UPDATE + USING ( + EXISTS ( + SELECT 1 FROM properties + WHERE properties.id = properties_models.property_id + AND properties.owner_id = current_setting('app.user_id', true)::TEXT + ) + ); + +CREATE POLICY "Users can delete models of their own properties" + ON properties_models FOR DELETE + USING ( + EXISTS ( + SELECT 1 FROM properties + WHERE properties.id = properties_models.property_id + AND properties.owner_id = current_setting('app.user_id', true)::TEXT + ) + ); + +-- Trigger to update updated_at timestamp +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER update_properties_updated_at + BEFORE UPDATE ON properties + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER update_properties_addresses_updated_at + BEFORE UPDATE ON properties_addresses + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER update_properties_models_updated_at + BEFORE UPDATE ON properties_models + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); diff --git a/packages/db/supabase/migrations/20240211000003_add_active_property_to_sessions.sql b/packages/db/supabase/migrations/20240211000003_add_active_property_to_sessions.sql new file mode 100644 index 00000000..645cbaf3 --- /dev/null +++ b/packages/db/supabase/migrations/20240211000003_add_active_property_to_sessions.sql @@ -0,0 +1,2 @@ +-- Add active_property_id to sessions table +ALTER TABLE auth_sessions ADD COLUMN IF NOT EXISTS active_property_id TEXT; diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json new file mode 100644 index 00000000..c6daff18 --- /dev/null +++ b/packages/db/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@repo/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +}