community feature
This commit is contained in:
parent
4f9c4655e6
commit
2e8b663001
254
SETUP.md
Normal file
254
SETUP.md
Normal file
@ -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=<generate_with_command_below>
|
||||
BETTER_AUTH_URL=http://localhost:3000
|
||||
|
||||
# Google Maps (optional, for address search)
|
||||
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=<your_google_maps_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.)
|
||||
9
apps/editor/app/api/auth/[...all]/route.ts
Normal file
9
apps/editor/app/api/auth/[...all]/route.ts
Normal file
@ -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)
|
||||
@ -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'
|
||||
|
||||
@ -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<AuthState['user']>
|
||||
export type Session = NonNullable<AuthState['session']>
|
||||
@ -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
|
||||
}
|
||||
@ -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=<generate_with_openssl_rand_base64_32>
|
||||
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
|
||||
7
apps/editor/features/community/lib/auth/client.ts
Normal file
7
apps/editor/features/community/lib/auth/client.ts
Normal file
@ -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'
|
||||
15
apps/editor/features/community/lib/database/server.ts
Normal file
15
apps/editor/features/community/lib/database/server.ts
Normal file
@ -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
|
||||
}
|
||||
@ -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",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/editor/public/audios/radios/classic/Clockwork Tea Party.mp3
Normal file
BIN
apps/editor/public/audios/radios/classic/Clockwork Tea Party.mp3
Normal file
Binary file not shown.
BIN
apps/editor/public/audios/radios/classic/Clockwork Teacups.mp3
Normal file
BIN
apps/editor/public/audios/radios/classic/Clockwork Teacups.mp3
Normal file
Binary file not shown.
Binary file not shown.
BIN
apps/editor/public/audios/radios/classic/Glass Atrium.mp3
Normal file
BIN
apps/editor/public/audios/radios/classic/Glass Atrium.mp3
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/editor/public/audios/sfx/grid_snap.mp3
Normal file
BIN
apps/editor/public/audios/sfx/grid_snap.mp3
Normal file
Binary file not shown.
BIN
apps/editor/public/audios/sfx/item_delete.mp3
Normal file
BIN
apps/editor/public/audios/sfx/item_delete.mp3
Normal file
Binary file not shown.
BIN
apps/editor/public/audios/sfx/item_pick.mp3
Normal file
BIN
apps/editor/public/audios/sfx/item_pick.mp3
Normal file
Binary file not shown.
BIN
apps/editor/public/audios/sfx/item_place.mp3
Normal file
BIN
apps/editor/public/audios/sfx/item_place.mp3
Normal file
Binary file not shown.
BIN
apps/editor/public/audios/sfx/structure_build.mp3
Normal file
BIN
apps/editor/public/audios/sfx/structure_build.mp3
Normal file
Binary file not shown.
BIN
apps/editor/public/audios/sfx/structure_delete.mp3
Normal file
BIN
apps/editor/public/audios/sfx/structure_delete.mp3
Normal file
Binary file not shown.
279
bun.lock
279
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=="],
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
142
packages/auth/README.md
Normal file
142
packages/auth/README.md
Normal file
@ -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=<generate_with_openssl_rand_base64_32>
|
||||
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 <button onClick={() => handleSignIn('user@example.com')}>Sign In</button>
|
||||
}
|
||||
```
|
||||
|
||||
### 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 <div>Loading...</div>
|
||||
if (!session) return <div>Not signed in</div>
|
||||
|
||||
return <div>Signed in as {session.user.email}</div>
|
||||
}
|
||||
```
|
||||
|
||||
## 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: <a href="${url}">${url}</a>`,
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## 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
|
||||
24
packages/auth/package.json
Normal file
24
packages/auth/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
37
packages/auth/src/client.ts
Normal file
37
packages/auth/src/client.ts
Normal file
@ -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<AuthState['user']>
|
||||
export type Session = NonNullable<AuthState['session']>
|
||||
91
packages/auth/src/server.ts
Normal file
91
packages/auth/src/server.ts
Normal file
@ -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 <noreply@pascal.app>',
|
||||
to: email,
|
||||
subject: 'Sign in to Pascal Editor',
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2>Sign in to Pascal Editor</h2>
|
||||
<p>Click the button below to sign in to your account:</p>
|
||||
<a href="${url}" style="display: inline-block; background-color: #000; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin: 20px 0;">
|
||||
Sign In
|
||||
</a>
|
||||
<p style="color: #666; font-size: 14px;">This link will expire in 5 minutes.</p>
|
||||
<p style="color: #666; font-size: 14px;">If you didn't request this email, you can safely ignore it.</p>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
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
|
||||
9
packages/auth/tsconfig.json
Normal file
9
packages/auth/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
127
packages/db/README.md
Normal file
127
packages/db/README.md
Normal file
@ -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=<your_anon_key>
|
||||
SUPABASE_SERVICE_ROLE_KEY=<your_service_role_key>
|
||||
|
||||
# Better Auth
|
||||
BETTER_AUTH_SECRET=<generate_with_openssl_rand_base64_32>
|
||||
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 <migration_name>
|
||||
```
|
||||
|
||||
## 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
|
||||
35
packages/db/package.json
Normal file
35
packages/db/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
17
packages/db/src/client.ts
Normal file
17
packages/db/src/client.ts
Normal file
@ -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<Database>(supabaseUrl, supabaseAnonKey)
|
||||
17
packages/db/src/drizzle.ts
Normal file
17
packages/db/src/drizzle.ts
Normal file
@ -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
|
||||
56
packages/db/src/helpers.ts
Normal file
56
packages/db/src/helpers.ts
Normal file
@ -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<string>()
|
||||
|
||||
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})`
|
||||
24
packages/db/src/index.ts
Normal file
24
packages/db/src/index.ts
Normal file
@ -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'
|
||||
31
packages/db/src/schema/auth/accounts.ts
Normal file
31
packages/db/src/schema/auth/accounts.ts
Normal file
@ -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)
|
||||
15
packages/db/src/schema/auth/jwks.ts
Normal file
15
packages/db/src/schema/auth/jwks.ts
Normal file
@ -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)
|
||||
28
packages/db/src/schema/auth/sessions.ts
Normal file
28
packages/db/src/schema/auth/sessions.ts
Normal file
@ -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)
|
||||
28
packages/db/src/schema/auth/users.ts
Normal file
28
packages/db/src/schema/auth/users.ts
Normal file
@ -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)
|
||||
22
packages/db/src/schema/auth/verifications.ts
Normal file
22
packages/db/src/schema/auth/verifications.ts
Normal file
@ -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)
|
||||
11
packages/db/src/schema/index.ts
Normal file
11
packages/db/src/schema/index.ts
Normal file
@ -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'
|
||||
53
packages/db/src/schema/properties/addresses.ts
Normal file
53
packages/db/src/schema/properties/addresses.ts
Normal file
@ -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<typeof addressSchema>
|
||||
export type Address = typeof addresses.$inferSelect
|
||||
export type NewAddress = typeof addresses.$inferInsert
|
||||
31
packages/db/src/schema/properties/models.ts
Normal file
31
packages/db/src/schema/properties/models.ts
Normal file
@ -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)
|
||||
44
packages/db/src/schema/properties/properties.ts
Normal file
44
packages/db/src/schema/properties/properties.ts
Normal file
@ -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)
|
||||
23
packages/db/src/server.ts
Normal file
23
packages/db/src/server.ts
Normal file
@ -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<Database>(supabaseUrl, supabaseServiceRoleKey, {
|
||||
auth: {
|
||||
persistSession: false,
|
||||
autoRefreshToken: false,
|
||||
},
|
||||
})
|
||||
124
packages/db/src/types.ts
Normal file
124
packages/db/src/types.ts
Normal file
@ -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: {}
|
||||
}
|
||||
}
|
||||
1
packages/db/supabase/.branches/_current_branch
Normal file
1
packages/db/supabase/.branches/_current_branch
Normal file
@ -0,0 +1 @@
|
||||
main
|
||||
1
packages/db/supabase/.temp/cli-latest
Normal file
1
packages/db/supabase/.temp/cli-latest
Normal file
@ -0,0 +1 @@
|
||||
v2.75.0
|
||||
38
packages/db/supabase/config.toml
Normal file
38
packages/db/supabase/config.toml
Normal file
@ -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
|
||||
@ -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"));
|
||||
@ -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();
|
||||
@ -0,0 +1,2 @@
|
||||
-- Add active_property_id to sessions table
|
||||
ALTER TABLE auth_sessions ADD COLUMN IF NOT EXISTS active_property_id TEXT;
|
||||
9
packages/db/tsconfig.json
Normal file
9
packages/db/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user