feat: Make galaxy viewable without login requirement
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>
This commit is contained in:
133
tests/magnitude/03-public-galaxy.mag.ts
Normal file
133
tests/magnitude/03-public-galaxy.mag.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { test } from 'magnitude-test';
|
||||
|
||||
/**
|
||||
* Public Galaxy Viewing Tests
|
||||
*
|
||||
* These tests verify that:
|
||||
* 1. Galaxies can be viewed publicly via ?user={did} parameter
|
||||
* 2. Private nodes are hidden from public view
|
||||
* 3. Own galaxy requires authentication
|
||||
* 4. Invalid user DIDs are handled gracefully
|
||||
*/
|
||||
|
||||
test('Unauthenticated users can view public galaxies via user parameter', async (agent) => {
|
||||
// Navigate to a public galaxy using the user parameter
|
||||
// Note: This test assumes there's at least one user with public nodes
|
||||
await agent.act('Navigate to /galaxy?user=did:plc:example123');
|
||||
|
||||
// Check: The galaxy visualization should be displayed
|
||||
await agent.check('The 3D galaxy visualization is visible');
|
||||
|
||||
// Check: Public nodes should be visible
|
||||
await agent.check('At least one node sphere is visible in the 3D space');
|
||||
|
||||
// Check: A "Public Galaxy" banner should be shown
|
||||
await agent.check('A banner or indicator shows this is a public galaxy');
|
||||
|
||||
// Check: Can interact with nodes
|
||||
await agent.act('Click on a visible node sphere');
|
||||
await agent.check('A modal or panel displays the node title and content');
|
||||
});
|
||||
|
||||
test('Private nodes are not visible in public galaxy view', async (agent) => {
|
||||
// This test requires a user with both public and private nodes
|
||||
// Navigate to their public galaxy
|
||||
await agent.act('Navigate to /galaxy?user=did:plc:user-with-private-nodes');
|
||||
|
||||
// Check: Only public nodes are visible
|
||||
await agent.check('The galaxy shows only public nodes');
|
||||
|
||||
// Check: Private nodes are not visible in the visualization
|
||||
await agent.check('Private nodes are not rendered in the 3D space');
|
||||
});
|
||||
|
||||
test('Accessing own galaxy without user parameter shows authenticated galaxy', async (agent) => {
|
||||
// First, log in
|
||||
await agent.act('Navigate to /');
|
||||
await agent.act('Click the login button');
|
||||
await agent.act('Complete the Bluesky OAuth flow');
|
||||
|
||||
// Navigate to /galaxy without user parameter
|
||||
await agent.act('Navigate to /galaxy');
|
||||
|
||||
// Check: Should see own galaxy (including private nodes)
|
||||
await agent.check('The galaxy visualization shows all nodes including private ones');
|
||||
|
||||
// Check: No "Public Galaxy" banner should be shown (it's your own galaxy)
|
||||
await agent.check('There is no "Public Galaxy" banner visible');
|
||||
});
|
||||
|
||||
test('Accessing own galaxy without authentication shows empty state', async (agent) => {
|
||||
// Ensure user is logged out
|
||||
await agent.act('Navigate to /');
|
||||
await agent.act('If logged in, click logout');
|
||||
|
||||
// Navigate to /galaxy without user parameter
|
||||
await agent.act('Navigate to /galaxy');
|
||||
|
||||
// Check: Should see a message about logging in or viewing a public galaxy
|
||||
await agent.check('A message is displayed about logging in or visiting a public galaxy');
|
||||
|
||||
// Check: No nodes are visible
|
||||
await agent.check('No node spheres are visible in the visualization');
|
||||
});
|
||||
|
||||
test('Invalid user DID shows appropriate error or empty state', async (agent) => {
|
||||
// Navigate to galaxy with an invalid/non-existent user DID
|
||||
await agent.act('Navigate to /galaxy?user=did:plc:invalid-nonexistent-user');
|
||||
|
||||
// Check: Should handle gracefully (empty state or error message)
|
||||
await agent.check('An appropriate message is shown for invalid or empty galaxy');
|
||||
|
||||
// Check: No crash or loading spinner stuck
|
||||
await agent.check('The page is in a stable state (not stuck loading)');
|
||||
});
|
||||
|
||||
test('Public galaxy displays user information', async (agent) => {
|
||||
// Navigate to a public galaxy
|
||||
await agent.act('Navigate to /galaxy?user=did:plc:example123');
|
||||
|
||||
// Check: User DID or handle is displayed
|
||||
await agent.check('The user DID is visible in the UI');
|
||||
|
||||
// Check: Node count is displayed
|
||||
await agent.check('The number of public nodes is shown');
|
||||
});
|
||||
|
||||
test('Public galaxy node details show Bluesky link', async (agent) => {
|
||||
// Navigate to a public galaxy
|
||||
await agent.act('Navigate to /galaxy?user=did:plc:example123');
|
||||
|
||||
// Act: Click on a node
|
||||
await agent.act('Click on a visible node sphere');
|
||||
|
||||
// Check: Node details modal is shown
|
||||
await agent.check('A modal displays the node title and body');
|
||||
|
||||
// Check: "View on Bluesky" link is present
|
||||
await agent.check('A "View on Bluesky" link is visible');
|
||||
|
||||
// Check: Link points to Bluesky
|
||||
await agent.check('The link URL includes "bsky.app"');
|
||||
});
|
||||
|
||||
test('Can switch between own galaxy and public galaxy', async (agent) => {
|
||||
// Log in
|
||||
await agent.act('Navigate to /');
|
||||
await agent.act('Click the login button');
|
||||
await agent.act('Complete the Bluesky OAuth flow');
|
||||
|
||||
// View own galaxy
|
||||
await agent.act('Navigate to /galaxy');
|
||||
await agent.check('Own galaxy is displayed');
|
||||
|
||||
// Navigate to someone else's public galaxy
|
||||
await agent.act('Navigate to /galaxy?user=did:plc:other-user');
|
||||
await agent.check('The "Public Galaxy" banner is shown');
|
||||
await agent.check('A different set of nodes is visible');
|
||||
|
||||
// Navigate back to own galaxy
|
||||
await agent.act('Navigate to /galaxy');
|
||||
await agent.check('Own galaxy is displayed again');
|
||||
await agent.check('No "Public Galaxy" banner is shown');
|
||||
});
|
||||
Reference in New Issue
Block a user