diff --git a/apps/editor/app/viewer/[id]/page.tsx b/apps/editor/app/viewer/[id]/page.tsx index 1235f3cf..bf0d66c4 100644 --- a/apps/editor/app/viewer/[id]/page.tsx +++ b/apps/editor/app/viewer/[id]/page.tsx @@ -1,7 +1,7 @@ 'use client' import { initSpatialGridSync, useScene } from '@pascal-app/core' -import { Viewer } from '@pascal-app/viewer' +import { Viewer, useViewer } from '@pascal-app/viewer' import { useParams } from 'next/navigation' import { useEffect, useState } from 'react' import { ViewerCameraControls } from './viewer-camera-controls' @@ -20,6 +20,8 @@ export default function ViewerPage() { const [projectId, setProjectId] = useState(null) const [projectName, setProjectName] = useState(null) const [owner, setOwner] = useState(null) + const [canShowScans, setCanShowScans] = useState(true) + const [canShowGuides, setCanShowGuides] = useState(true) const setScene = useScene((state) => state.setScene) useEffect(() => { @@ -42,10 +44,26 @@ export default function ViewerPage() { const result = await getProjectModelPublic(id) if (result.success && result.data) { - const { project, model } = result.data + const { project, model, isOwner } = result.data + const projectData = project as any setProjectId(project.id) setProjectName(project.name) - setOwner((project as any).owner ?? null) + setOwner(projectData.owner ?? null) + + // Apply public visibility settings for scans/guides (only for non-owners) + if (!isOwner) { + const scansAllowed = projectData.show_scans_public !== false + const guidesAllowed = projectData.show_guides_public !== false + setCanShowScans(scansAllowed) + setCanShowGuides(guidesAllowed) + + if (!scansAllowed) { + useViewer.getState().setShowScans(false) + } + if (!guidesAllowed) { + useViewer.getState().setShowGuides(false) + } + } if (model?.scene_graph) { const { nodes, rootNodeIds } = model.scene_graph @@ -88,7 +106,7 @@ export default function ViewerPage() { return (
- + diff --git a/apps/editor/app/viewer/[id]/viewer-overlay.tsx b/apps/editor/app/viewer/[id]/viewer-overlay.tsx index a46032fb..0cbb053c 100644 --- a/apps/editor/app/viewer/[id]/viewer-overlay.tsx +++ b/apps/editor/app/viewer/[id]/viewer-overlay.tsx @@ -19,9 +19,11 @@ const getNodeName = (node: AnyNode): string => { interface ViewerOverlayProps { projectName?: string | null owner?: ProjectOwner | null + canShowScans?: boolean + canShowGuides?: boolean } -export const ViewerOverlay = ({ projectName, owner }: ViewerOverlayProps) => { +export const ViewerOverlay = ({ projectName, owner, canShowScans = true, canShowGuides = true }: ViewerOverlayProps) => { const selection = useViewer((s) => s.selection) const nodes = useScene((s) => s.nodes) const showScans = useViewer((s) => s.showScans) @@ -171,8 +173,10 @@ export const ViewerOverlay = ({ projectName, owner }: ViewerOverlayProps) => { {/* Controls Panel - Top Right */}
{/* Visibility Controls */} + {(canShowScans || canShowGuides) && (
Visibility + {canShowScans && ( + )} + {canShowGuides && ( + )}
+ )} {/* Camera Mode */}
diff --git a/apps/editor/components/ui/sidebar/panels/settings-panel/index.tsx b/apps/editor/components/ui/sidebar/panels/settings-panel/index.tsx index a5d896a2..025d1458 100644 --- a/apps/editor/components/ui/sidebar/panels/settings-panel/index.tsx +++ b/apps/editor/components/ui/sidebar/panels/settings-panel/index.tsx @@ -3,9 +3,11 @@ import { useViewer } from "@pascal-app/viewer"; import { Camera, Download, Save, Trash2, Upload } from "lucide-react"; import { useRef, useState } from "react"; import { Button } from "@/components/ui/primitives/button"; +import { Switch } from "@/components/ui/primitives/switch"; import useEditor from "@/store/use-editor"; import { AudioSettingsDialog } from "./audio-settings-dialog"; import { useProjectStore } from "@/features/community/lib/projects/store"; +import { updateProjectVisibility } from "@/features/community/lib/projects/actions"; export function SettingsPanel() { const fileInputRef = useRef(null); @@ -77,8 +79,74 @@ export function SettingsPanel() { setTimeout(() => setIsGeneratingThumbnail(false), 3000); }; + const handleVisibilityChange = async ( + field: 'isPrivate' | 'showScansPublic' | 'showGuidesPublic', + value: boolean, + ) => { + if (!projectId) return; + + // Optimistic update + useProjectStore.setState((state) => ({ + activeProject: state.activeProject + ? { + ...state.activeProject, + ...(field === 'isPrivate' && { is_private: value }), + ...(field === 'showScansPublic' && { show_scans_public: value }), + ...(field === 'showGuidesPublic' && { show_guides_public: value }), + } + : null, + })); + + await updateProjectVisibility(projectId, { [field]: value }); + }; + return (
+ {/* Visibility Section (only for cloud projects) */} + {projectId && !isLocalProject && ( +
+ +
+
+
Public
+
+ {activeProject?.is_private ? 'Only you' : 'Anyone'} can view +
+
+ handleVisibilityChange('isPrivate', !checked)} + /> +
+
+
+
Show 3D Scans
+
+ Visible to public viewers +
+
+ handleVisibilityChange('showScansPublic', checked)} + /> +
+
+
+
Show Floorplans
+
+ Visible to public viewers +
+
+ handleVisibilityChange('showGuidesPublic', checked)} + /> +
+
+ )} + {/* Export Section */}
Public - setIsPrivate(!checked)} /> + handleVisibilityChange('isPrivate', !checked)} />
+ {/* Public Visibility Toggles */} +
+
+
Show 3D Scans
+
+ Visible to public viewers +
+
+ handleVisibilityChange('showScansPublic', checked)} /> +
+ +
+
+
Show Floorplans
+
+ Visible to public viewers +
+
+ handleVisibilityChange('showGuidesPublic', checked)} /> +
+ {/* Danger Zone */}

Danger Zone

@@ -136,31 +148,12 @@ export function ProjectSettingsDialog({ type="button" onClick={handleDelete} className="rounded-md border border-destructive bg-destructive/10 px-4 py-2 text-sm text-destructive hover:bg-destructive/20" - disabled={isDeleting || loading} + disabled={isDeleting} > {isDeleting ? 'Deleting...' : 'Delete Project'}
- - - - - ) diff --git a/apps/editor/features/community/components/username-gate.tsx b/apps/editor/features/community/components/username-gate.tsx index f5b58875..c3800fe8 100644 --- a/apps/editor/features/community/components/username-gate.tsx +++ b/apps/editor/features/community/components/username-gate.tsx @@ -17,10 +17,15 @@ export function UsernameGate({ children }: { children: React.ReactNode }) { setNeedsUsername(false) return } - getUsername().then((username) => { - setNeedsUsername(!username) - setChecking(false) - }) + getUsername() + .then((username) => { + setNeedsUsername(!username) + setChecking(false) + }) + .catch(() => { + setNeedsUsername(false) + setChecking(false) + }) }, [isAuthenticated, isLoading]) return ( diff --git a/apps/editor/features/community/lib/auth/actions.ts b/apps/editor/features/community/lib/auth/actions.ts index dc5acc2c..b49b61b5 100644 --- a/apps/editor/features/community/lib/auth/actions.ts +++ b/apps/editor/features/community/lib/auth/actions.ts @@ -64,10 +64,15 @@ export async function updateUsername( return { success: false, error: 'Username is already taken' } } - await db + const updated = await db .update(schema.users) .set({ username: trimmed }) .where(eq(schema.users.id, session.user.id)) + .returning({ id: schema.users.id }) + + if (updated.length === 0) { + return { success: false, error: 'User not found. Please sign out and sign in again.' } + } revalidatePath('/') revalidatePath('/settings') diff --git a/apps/editor/features/community/lib/projects/actions.ts b/apps/editor/features/community/lib/projects/actions.ts index 504063ce..1c3a1ded 100644 --- a/apps/editor/features/community/lib/projects/actions.ts +++ b/apps/editor/features/community/lib/projects/actions.ts @@ -461,7 +461,7 @@ export async function getPublicProjectsByUserId(userId: string): Promise + ActionResult<{ project: Project; model: any | null; isOwner: boolean }> > { try { const session = await getSession() @@ -515,6 +515,7 @@ export async function getProjectModelPublic(projectId: string): Promise< data: { project: projectData as Project, model: model || null, + isOwner: !!isOwner, }, } } catch (error) { @@ -607,6 +608,64 @@ export async function updateProjectPrivacy( } } +/** + * Update project visibility settings (privacy + public scan/guide visibility) + */ +export async function updateProjectVisibility( + projectId: string, + settings: { + isPrivate?: boolean + showScansPublic?: boolean + showGuidesPublic?: boolean + }, +): Promise { + try { + const session = await getSession() + + if (!session?.user) { + return { success: false, error: 'Not authenticated' } + } + + const supabase = await createServerSupabaseClient() + + // Verify ownership + const { data: project } = await supabase + .from('projects') + .select('owner_id') + .eq('id', projectId) + .single() + + if ((project as any)?.owner_id !== session.user.id) { + return { success: false, error: 'Unauthorized' } + } + + const updateData: Record = {} + if (settings.isPrivate !== undefined) updateData.is_private = settings.isPrivate + if (settings.showScansPublic !== undefined) updateData.show_scans_public = settings.showScansPublic + if (settings.showGuidesPublic !== undefined) updateData.show_guides_public = settings.showGuidesPublic + + if (Object.keys(updateData).length === 0) { + return { success: true, message: 'No changes' } + } + + const { error } = await (supabase + .from('projects') as any) + .update(updateData) + .eq('id', projectId) + + if (error) { + return { success: false, error: error.message } + } + + return { success: true, message: 'Visibility settings updated' } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to update visibility settings', + } + } +} + /** * Update project name */ diff --git a/apps/editor/features/community/lib/projects/types.ts b/apps/editor/features/community/lib/projects/types.ts index 9ddd658e..c286b77d 100644 --- a/apps/editor/features/community/lib/projects/types.ts +++ b/apps/editor/features/community/lib/projects/types.ts @@ -13,6 +13,8 @@ export type DbProject = { created_at: string updated_at: string is_private: boolean + show_scans_public: boolean + show_guides_public: boolean views: number likes: number thumbnail_url: string | null @@ -55,7 +57,7 @@ export type Database = { Tables: { projects: { Row: DbProject - Insert: Omit + Insert: Omit & { show_scans_public?: boolean; show_guides_public?: boolean } Update: Partial> } projects_addresses: { @@ -104,6 +106,8 @@ export type Project = { updated_at: string // Community features is_private: boolean + show_scans_public: boolean + show_guides_public: boolean views: number likes: number thumbnail_url: string | null diff --git a/packages/db/src/schema/projects/projects.ts b/packages/db/src/schema/projects/projects.ts index b9ac6475..f8cb47f8 100644 --- a/packages/db/src/schema/projects/projects.ts +++ b/packages/db/src/schema/projects/projects.ts @@ -20,6 +20,8 @@ export const projects = pgTable( metadata: t.jsonb('metadata'), // Community features isPrivate: t.boolean('is_private').notNull().default(true), + showScansPublic: t.boolean('show_scans_public').notNull().default(true), + showGuidesPublic: t.boolean('show_guides_public').notNull().default(true), views: t.integer('views').notNull().default(0), likes: t.integer('likes').notNull().default(0), thumbnailUrl: t.text('thumbnail_url'), diff --git a/supabase/migrations/20260219212649_young_dragon_man.sql b/supabase/migrations/20260219212649_young_dragon_man.sql new file mode 100644 index 00000000..abb4cc0c --- /dev/null +++ b/supabase/migrations/20260219212649_young_dragon_man.sql @@ -0,0 +1,2 @@ +ALTER TABLE "projects" ADD COLUMN "show_scans_public" boolean DEFAULT true NOT NULL;--> statement-breakpoint +ALTER TABLE "projects" ADD COLUMN "show_guides_public" boolean DEFAULT true NOT NULL; \ No newline at end of file diff --git a/supabase/migrations/meta/20260219212649_snapshot.json b/supabase/migrations/meta/20260219212649_snapshot.json new file mode 100644 index 00000000..9cebb088 --- /dev/null +++ b/supabase/migrations/meta/20260219212649_snapshot.json @@ -0,0 +1,1014 @@ +{ + "id": "65aa76b8-b827-4be8-a6ec-2017ad309dd9", + "prevId": "57bff2fc-7d82-473b-afdb-2dd34f7a2696", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.auth_accounts": { + "name": "auth_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "auth_accounts_user_id_auth_users_id_fk": { + "name": "auth_accounts_user_id_auth_users_id_fk", + "tableFrom": "auth_accounts", + "tableTo": "auth_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.auth_jwks": { + "name": "auth_jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.auth_sessions": { + "name": "auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_project_id": { + "name": "active_project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "auth_sessions_user_id_auth_users_id_fk": { + "name": "auth_sessions_user_id_auth_users_id_fk", + "tableFrom": "auth_sessions", + "tableTo": "auth_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auth_sessions_impersonated_by_auth_users_id_fk": { + "name": "auth_sessions_impersonated_by_auth_users_id_fk", + "tableFrom": "auth_sessions", + "tableTo": "auth_users", + "columnsFrom": [ + "impersonated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.auth_users": { + "name": "auth_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_url": { + "name": "github_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "x_url": { + "name": "x_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "auth_user_roles", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_unique_index": { + "name": "email_unique_index", + "columns": [ + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "username_unique_index": { + "name": "username_unique_index", + "columns": [ + { + "expression": "lower(\"username\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.auth_verifications": { + "name": "auth_verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_index": { + "name": "verification_identifier_index", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.feedback": { + "name": "feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.projects_addresses": { + "name": "projects_addresses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "street_number": { + "name": "street_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "route": { + "name": "route", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "route_short": { + "name": "route_short", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "neighborhood": { + "name": "neighborhood", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "county": { + "name": "county", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_long": { + "name": "state_long", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code_suffix": { + "name": "postal_code_suffix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country_long": { + "name": "country_long", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latitude": { + "name": "latitude", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "longitude": { + "name": "longitude", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "raw_json": { + "name": "raw_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "address_components_unique": { + "name": "address_components_unique", + "nullsNotDistinct": false, + "columns": [ + "street_number", + "route", + "city", + "state", + "postal_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.projects_likes": { + "name": "projects_likes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "projects_likes_project_id_projects_id_fk": { + "name": "projects_likes_project_id_projects_id_fk", + "tableFrom": "projects_likes", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_likes_project_user_unique": { + "name": "projects_likes_project_user_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.projects_models": { + "name": "projects_models", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "draft": { + "name": "draft", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scene_graph": { + "name": "scene_graph", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "projects_models_project_id_projects_id_fk": { + "name": "projects_models_project_id_projects_id_fk", + "tableFrom": "projects_models", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_id": { + "name": "address_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details_json": { + "name": "details_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_private": { + "name": "is_private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_scans_public": { + "name": "show_scans_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_guides_public": { + "name": "show_guides_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "likes": { + "name": "likes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "thumbnail_url": { + "name": "thumbnail_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_address_idx": { + "name": "project_address_idx", + "columns": [ + { + "expression": "address_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_owner_idx": { + "name": "project_owner_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_is_private_idx": { + "name": "project_is_private_idx", + "columns": [ + { + "expression": "is_private", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_views_idx": { + "name": "project_views_idx", + "columns": [ + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_likes_idx": { + "name": "project_likes_idx", + "columns": [ + { + "expression": "likes", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_address_id_projects_addresses_id_fk": { + "name": "projects_address_id_projects_addresses_id_fk", + "tableFrom": "projects", + "tableTo": "projects_addresses", + "columnsFrom": [ + "address_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "projects_owner_id_auth_users_id_fk": { + "name": "projects_owner_id_auth_users_id_fk", + "tableFrom": "projects", + "tableTo": "auth_users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": { + "public.auth_user_roles": { + "name": "auth_user_roles", + "schema": "public", + "values": [ + "user", + "admin" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/supabase/migrations/meta/_journal.json b/supabase/migrations/meta/_journal.json index a978044f..82f0dadf 100644 --- a/supabase/migrations/meta/_journal.json +++ b/supabase/migrations/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1771478645202, "tag": "20260219052405_add-social-profile-fields", "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1771536409437, + "tag": "20260219212649_young_dragon_man", + "breakpoints": true } ] } \ No newline at end of file