community feature

This commit is contained in:
wass08
2026-02-11 15:23:18 +09:00
parent 4f9c4655e6
commit 2e8b663001
71 changed files with 1917 additions and 126 deletions
@@ -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)
+2 -2
View File
@@ -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
@@ -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'
@@ -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
}
+3 -1
View File
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.