Implemented public galaxy viewing feature that allows unauthenticated
users to view public thought galaxies via the ?user={did} parameter,
while maintaining privacy controls for node-level visibility.
Changes:
- Updated /api/galaxy/route.ts to support public access:
* Accept ?user={did} query parameter for viewing specific user's galaxy
* Show all nodes (including private) for authenticated user viewing own galaxy
* Filter to only public nodes when viewing someone else's galaxy
* Return empty state with helpful message when not authenticated
* Filter links to only show connections between visible nodes
- Added is_public field to database schema:
* Updated db/schema.surql with DEFAULT true (public by default)
* Created migration script scripts/add-is-public-field.ts
* Aligns with ATproto's public-by-default philosophy
- Enhanced ThoughtGalaxy component:
* Support viewing galaxies via ?user={did} parameter
* Display user info banner when viewing public galaxy
* Show appropriate empty state messages based on context
* Refetch data when user parameter changes
- Created comprehensive Magnitude tests:
* Test public galaxy viewing without authentication
* Verify private nodes are hidden from public view
* Test own galaxy access requires authentication
* Validate invalid user DID handling
* Test user info display and navigation between galaxies
- Documented implementation plan in plans/10-public-galaxy-viewing.md
This implements the "public by default" model while allowing future
node-level privacy controls. All canonical data remains on the user's
ATproto PDS, with SurrealDB serving as a high-performance cache.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
149 lines
5.2 KiB
Plaintext
149 lines
5.2 KiB
Plaintext
-- --------------------------------------------------
|
|
-- Ponderants :: SurrealDB Schema
|
|
-- --------------------------------------------------
|
|
|
|
-- --------------------------------------------------
|
|
-- Access Control (JWT)
|
|
-- --------------------------------------------------
|
|
|
|
-- Define the JWT access method. This tells SurrealDB to trust
|
|
-- JWTs signed by our Next.js backend using the HS512 algorithm
|
|
-- and the secret key provided in the environment.
|
|
-- (Note: DEFINE TOKEN is deprecated as of 2.x)
|
|
DEFINE ACCESS app_jwt
|
|
ON DATABASE
|
|
TYPE JWT
|
|
ALGORITHM HS512
|
|
KEY $env.SURREALDB_JWT_SECRET;
|
|
|
|
-- --------------------------------------------------
|
|
-- Table: user
|
|
-- --------------------------------------------------
|
|
|
|
-- Stores basic user information, cached from ATproto.
|
|
DEFINE TABLE user SCHEMAFULL;
|
|
|
|
-- The user's decentralized identifier (DID) is their primary key.
|
|
DEFINE FIELD did ON TABLE user TYPE string
|
|
ASSERT $value != NONE;
|
|
|
|
DEFINE FIELD handle ON TABLE user TYPE string;
|
|
|
|
-- Ensure DIDs are unique.
|
|
DEFINE INDEX user_did_idx ON TABLE user COLUMNS did UNIQUE;
|
|
|
|
-- --------------------------------------------------
|
|
-- Table: node
|
|
-- --------------------------------------------------
|
|
|
|
-- Stores a single "thought node." This is the cache record for
|
|
-- the com.ponderants.node lexicon.
|
|
DEFINE TABLE node SCHEMAFULL
|
|
-- THIS IS THE CORE SECURITY MODEL:
|
|
-- Users can only perform actions on nodes where the
|
|
-- node's 'user_did' field matches the 'did' claim
|
|
-- from their validated JWT ('$token.did').
|
|
PERMISSIONS
|
|
FOR select, create, update, delete
|
|
WHERE user_did = $token.did;
|
|
|
|
-- Foreign key linking to the user table (via DID).
|
|
DEFINE FIELD user_did ON TABLE node TYPE string
|
|
ASSERT $value != NONE;
|
|
|
|
-- The canonical URI of the record on the ATproto PDS.
|
|
DEFINE FIELD atp_uri ON TABLE node TYPE string;
|
|
|
|
DEFINE FIELD title ON TABLE node TYPE string;
|
|
DEFINE FIELD body ON TABLE node TYPE string;
|
|
|
|
-- The AI-generated vector embedding for the 'body'.
|
|
-- We use array<number> for the vector.
|
|
DEFINE FIELD embedding ON TABLE node TYPE array<number>;
|
|
|
|
-- The 3D coordinates calculated by UMAP.
|
|
-- Nodes start with NONE and get coordinates when user runs calculate-graph.
|
|
DEFINE FIELD coords_3d ON TABLE node TYPE option<array<number>>
|
|
-- Must be NONE or a 3-point array [x, y, z].
|
|
ASSERT $value = NONE OR array::len($value) = 3;
|
|
|
|
-- Privacy setting: Whether this node is publicly visible.
|
|
-- Defaults to true (public by default, aligns with ATproto philosophy).
|
|
-- When false, node is only visible to the owner.
|
|
DEFINE FIELD is_public ON TABLE node TYPE bool DEFAULT true;
|
|
|
|
-- Define the vector search index.
|
|
-- We use MTREE (or HNSW) for high-performance k-NN search.
|
|
-- The dimension (3072) MUST match the output of the
|
|
-- 'gemini-embedding-001' model.
|
|
DEFINE INDEX node_embedding_idx ON TABLE node FIELDS embedding MTREE DIMENSION 3072;
|
|
|
|
-- --------------------------------------------------
|
|
-- Relation: links_to
|
|
-- --------------------------------------------------
|
|
|
|
-- This is a graph edge table, relating (node)->(node).
|
|
DEFINE TABLE links_to SCHEMAFULL
|
|
-- Security for graph edges: A user can only create/view/delete
|
|
-- links between two nodes that BOTH belong to them.
|
|
PERMISSIONS
|
|
FOR select, create, delete
|
|
WHERE
|
|
(SELECT user_did FROM $from) = $token.did
|
|
AND
|
|
(SELECT user_did FROM $to) = $token.did;
|
|
|
|
-- (No fields needed, it's a simple relation)
|
|
-- Example usage: RELATE (node:1)-[links_to]->(node:2);
|
|
|
|
-- --------------------------------------------------
|
|
-- Table: oauth_state
|
|
-- --------------------------------------------------
|
|
|
|
-- Stores temporary OAuth state during the authorization flow.
|
|
-- Used for CSRF protection. States should expire after 1 hour.
|
|
DEFINE TABLE oauth_state SCHEMAFULL;
|
|
|
|
-- The state key (random string generated during authorize)
|
|
DEFINE FIELD key ON TABLE oauth_state TYPE string
|
|
ASSERT $value != NONE;
|
|
|
|
-- The state value (contains PKCE verifier, DPoP key, etc.)
|
|
DEFINE FIELD value ON TABLE oauth_state TYPE object
|
|
ASSERT $value != NONE;
|
|
|
|
-- Timestamp for cleanup
|
|
DEFINE FIELD created_at ON TABLE oauth_state TYPE datetime
|
|
DEFAULT time::now();
|
|
|
|
-- Index for fast lookups by key
|
|
DEFINE INDEX oauth_state_key_idx ON TABLE oauth_state COLUMNS key UNIQUE;
|
|
|
|
-- Event to auto-delete expired states (older than 1 hour)
|
|
DEFINE EVENT oauth_state_cleanup ON TABLE oauth_state WHEN time::now() - created_at > 1h THEN (
|
|
DELETE oauth_state WHERE id = $event.id
|
|
);
|
|
|
|
-- --------------------------------------------------
|
|
-- Table: oauth_session
|
|
-- --------------------------------------------------
|
|
|
|
-- Stores persistent OAuth sessions (access/refresh tokens).
|
|
-- Sessions are managed by the @atproto/oauth-client-node library.
|
|
DEFINE TABLE oauth_session SCHEMAFULL;
|
|
|
|
-- The user's DID (unique identifier)
|
|
DEFINE FIELD did ON TABLE oauth_session TYPE string
|
|
ASSERT $value != NONE;
|
|
|
|
-- The session data (contains tokens, DPoP key, etc.)
|
|
DEFINE FIELD session_data ON TABLE oauth_session TYPE object
|
|
ASSERT $value != NONE;
|
|
|
|
-- Timestamp for last update (useful for debugging)
|
|
DEFINE FIELD updated_at ON TABLE oauth_session TYPE datetime
|
|
DEFAULT time::now();
|
|
|
|
-- Index for fast lookups by DID
|
|
DEFINE INDEX oauth_session_did_idx ON TABLE oauth_session COLUMNS did UNIQUE;
|