learn and share notes on atproto (wip) 🦉
malfestio.stormlightlabs.org/
readability
solid
axum
atproto
srs
1# Local Development
2
3## Prerequisites
4
5### Required Tools
6
7- Rust (latest stable)
8- Node.js 18+ and pnpm
9- PostgreSQL 14+
10- Docker (optional, for containerized Postgres)
11
12### Bluesky Account Setup
13
141. Create a Bluesky account at <https://bsky.app> (you'll use this for OAuth testing)
15
16### Environment Configuration
17
18Copy the template and configure for your environment:
19
20```bash
21cp .env.example .env
22```
23
24For local development, the defaults in `.env.example` work out of the box. You only need to ensure your PostgreSQL connection string is correct.
25
26## Testing OAuth Flow
27
28### Step-by-Step
29
301. **Start PostgreSQL**
31
32 ```bash
33 # Using Docker
34 docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:14
35
36 # Or use your local PostgreSQL installation
37 ```
38
392. **Run migrations**
40
41 ```bash
42 just migrate
43 ```
44
453. **Start backend**
46
47 ```bash
48 just start
49 ```
50
51 Server runs on <http://localhost:8080>
52
534. **Start frontend**
54
55 ```bash
56 just web-dev
57 ```
58
59 Frontend runs on <http://localhost:3000>
60
615. **Test OAuth login**
62 - Navigate to <http://localhost:3000/login>
63 - Enter your Bluesky handle (e.g., `thunderbot.bsky.social`)
64 - Authorize the application on bsky.social
65 - Verify redirect back to app with successful login
66
67### OAuth Flow Details
68
69When you enter a handle like `thunderbot.bsky.social`, the system:
70
711. **Handle Resolution**: DNS TXT lookup at `_atproto.thunderbot.bsky.social` or HTTP `https://thunderbot.bsky.social/.well-known/atproto-did`
722. **DID Resolution**: Resolved DID (e.g., `did:plc:...`) queries `https://plc.directory` for PDS endpoint
733. **OAuth Discovery**: `https://bsky.social/.well-known/oauth-authorization-server` fetched for endpoints
744. **Authorization**: User redirected to PDS authorization page with PKCE challenge
755. **Token Exchange**: Authorization code exchanged for access/refresh tokens with DPoP binding
766. **Storage**: Tokens stored in database with encrypted DPoP keypair
77
78## Testing Record Publishing
79
80After successful OAuth login:
81
821. Create a deck or note in the UI
832. Click "Publish" to publish to your PDS
843. Check your Bluesky profile at <https://bsky.app> to see the published record
854. Verify record appears in your AT Protocol repository
86
87## Testing Sync Flow
88
89The app supports offline-first editing with automatic sync when online.
90
91### Local Storage (IndexedDB)
92
93All decks, notes, and cards are stored locally in IndexedDB via Dexie.js. You can view this data:
94
951. Navigate to Settings page
962. Scroll to "Local Sync Data" section
973. Use the Records/Queue tabs to view stored data
98
99### Testing Offline Mode
100
1011. Create or edit a deck while online → syncs immediately
1022. Disconnect network (DevTools → Network → Offline)
1033. Create/edit content → stored locally with "local_only" or "pending_push" status
1044. Reconnect → content auto-syncs when online status changes
105
106### Sync Statuses
107
108| Status | Meaning |
109| -------------- | -------------------------------- |
110| `local_only` | New content, never synced |
111| `synced` | Content matches PDS |
112| `pending_push` | Local changes waiting to sync |
113| `conflict` | Local and remote versions differ |
114
115### Conflict Resolution
116
117When conflicts occur:
118
1191. Settings → Local Sync Data shows records with "conflict" status
1202. Click "Keep Local" to overwrite remote with local version
1213. Or use the API: `POST /api/sync/resolve/:type/:id` with strategy
122
123## Verifying Your Setup
124
125### Check OAuth Tokens
126
127After successful login, verify tokens were stored:
128
129```sql
130SELECT
131 did,
132 pds_url,
133 LEFT(access_token, 20) || '...' as token_preview,
134 created_at,
135 updated_at
136FROM oauth_tokens
137WHERE did = 'your-did-here';
138```
139
140Replace `'your-did-here'` with the DID from your login success page.
141
142### Check Indexed Records
143
144After publishing content, verify firehose indexing:
145
146```sql
147-- Check indexed decks
148SELECT at_uri, title, indexed_at
149FROM indexed_decks
150WHERE did = 'your-did-here'
151ORDER BY indexed_at DESC
152LIMIT 10;
153
154-- Check indexed cards
155SELECT at_uri, front_content, indexed_at
156FROM indexed_cards
157WHERE did = 'your-did-here'
158ORDER BY indexed_at DESC
159LIMIT 10;
160```
161
162Note: Indexing may take 5-10 seconds after publishing.
163
164### Diagnostic Command
165
166Run this command to check handle resolution and database state:
167
168```bash
169just verify your-handle.bsky.social
170```
171
172This will verify:
173
174- Database connection
175- Handle → DID resolution
176- DID → PDS URL resolution
177- OAuth token status
178- Indexed content count
179
180## Environment Variables Reference
181
182### Required
183
184```bash
185DB_URL="postgres://postgres:postgres@localhost:5432/malfestio_dev?sslmode=disable"
186```
187
188### Optional
189
190```bash
191# OAuth Client Configuration
192APP_URL=http://localhost:3000 # OAuth callback URL
193APP_NAME=Malfestio # App display name
194
195# Server Configuration
196SERVER_HOST=127.0.0.1
197SERVER_PORT=8080
198
199# Frontend Configuration
200VITE_API_URL=http://localhost:8080
201
202# Logging
203RUST_LOG=info,malfestio_server=debug
204```
205
206See `.env.example` for a complete template.
207
208## Additional Resources
209
210- [AT Protocol OAuth Guide](https://docs.bsky.app/blog/oauth-atproto)
211- [OAuth Client Implementation](https://docs.bsky.app/docs/advanced-guides/oauth-client)
212- [PDS Self-Hosting](https://atproto.com/guides/self-hosting)
213- [AT Protocol Specifications](https://atproto.com)