Local Development#
Prerequisites#
Required Tools#
- Rust (latest stable)
- Node.js 18+ and pnpm
- PostgreSQL 14+
- Docker (optional, for containerized Postgres)
Bluesky Account Setup#
- Create a Bluesky account at https://bsky.app (you'll use this for OAuth testing)
Environment Configuration#
Copy the template and configure for your environment:
cp .env.example .env
For local development, the defaults in .env.example work out of the box. You only need to ensure your PostgreSQL connection string is correct.
Testing OAuth Flow#
Step-by-Step#
-
Start PostgreSQL
# Using Docker docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:14 # Or use your local PostgreSQL installation -
Run migrations
just migrate -
Start backend
just startServer runs on http://localhost:8080
-
Start frontend
just web-devFrontend runs on http://localhost:3000
-
Test OAuth login
- Navigate to http://localhost:3000/login
- Enter your Bluesky handle (e.g.,
thunderbot.bsky.social) - Authorize the application on bsky.social
- Verify redirect back to app with successful login
OAuth Flow Details#
When you enter a handle like thunderbot.bsky.social, the system:
- Handle Resolution: DNS TXT lookup at
_atproto.thunderbot.bsky.socialor HTTPhttps://thunderbot.bsky.social/.well-known/atproto-did - DID Resolution: Resolved DID (e.g.,
did:plc:...) querieshttps://plc.directoryfor PDS endpoint - OAuth Discovery:
https://bsky.social/.well-known/oauth-authorization-serverfetched for endpoints - Authorization: User redirected to PDS authorization page with PKCE challenge
- Token Exchange: Authorization code exchanged for access/refresh tokens with DPoP binding
- Storage: Tokens stored in database with encrypted DPoP keypair
Testing Record Publishing#
After successful OAuth login:
- Create a deck or note in the UI
- Click "Publish" to publish to your PDS
- Check your Bluesky profile at https://bsky.app to see the published record
- Verify record appears in your AT Protocol repository
Testing Sync Flow#
The app supports offline-first editing with automatic sync when online.
Local Storage (IndexedDB)#
All decks, notes, and cards are stored locally in IndexedDB via Dexie.js. You can view this data:
- Navigate to Settings page
- Scroll to "Local Sync Data" section
- Use the Records/Queue tabs to view stored data
Testing Offline Mode#
- Create or edit a deck while online → syncs immediately
- Disconnect network (DevTools → Network → Offline)
- Create/edit content → stored locally with "local_only" or "pending_push" status
- Reconnect → content auto-syncs when online status changes
Sync Statuses#
| Status | Meaning |
|---|---|
local_only |
New content, never synced |
synced |
Content matches PDS |
pending_push |
Local changes waiting to sync |
conflict |
Local and remote versions differ |
Conflict Resolution#
When conflicts occur:
- Settings → Local Sync Data shows records with "conflict" status
- Click "Keep Local" to overwrite remote with local version
- Or use the API:
POST /api/sync/resolve/:type/:idwith strategy
Verifying Your Setup#
Check OAuth Tokens#
After successful login, verify tokens were stored:
SELECT
did,
pds_url,
LEFT(access_token, 20) || '...' as token_preview,
created_at,
updated_at
FROM oauth_tokens
WHERE did = 'your-did-here';
Replace 'your-did-here' with the DID from your login success page.
Check Indexed Records#
After publishing content, verify firehose indexing:
-- Check indexed decks
SELECT at_uri, title, indexed_at
FROM indexed_decks
WHERE did = 'your-did-here'
ORDER BY indexed_at DESC
LIMIT 10;
-- Check indexed cards
SELECT at_uri, front_content, indexed_at
FROM indexed_cards
WHERE did = 'your-did-here'
ORDER BY indexed_at DESC
LIMIT 10;
Note: Indexing may take 5-10 seconds after publishing.
Diagnostic Command#
Run this command to check handle resolution and database state:
just verify your-handle.bsky.social
This will verify:
- Database connection
- Handle → DID resolution
- DID → PDS URL resolution
- OAuth token status
- Indexed content count
Environment Variables Reference#
Required#
DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable"
Optional#
# OAuth Client Configuration
APP_URL=http://localhost:3000 # OAuth callback URL
APP_NAME=Malfestio # App display name
# Server Configuration
SERVER_HOST=127.0.0.1
SERVER_PORT=8080
# Frontend Configuration
VITE_API_URL=http://localhost:8080
# Logging
RUST_LOG=info,malfestio_server=debug
See .env.example for a complete template.