Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee

docs: docs cleanup

pdewey.com 2362d69a 71a76dcc

verified
+175 -2870
+40 -931
PLAN.md
··· 1 - # Arabica AT Protocol Migration Plan 2 - 3 - ## Overview 4 - 5 - This document outlines the plan to transform Arabica from a self-hosted SQLite-based coffee tracking application into a decentralized AT Protocol (atproto) application where user data lives in their Personal Data Servers (PDS). 6 - 7 - ## Project Goals 8 - 9 - ### Core Principles 10 - 11 - - **Decentralized Storage**: User data lives in their own PDS, not our server 12 - - **Public Records**: All coffee tracking records are public via atproto repo exploration 13 - - **Self-hostable AppView**: Our server is the primary AppView, but others can self-host 14 - - **OAuth Authentication**: Users authenticate via atproto OAuth with scopes (not app passwords) 15 - - **Backward Compatible UX**: Preserve the user-friendly interface and workflow 16 - 17 - ### Architecture Vision 18 - 19 - ``` 20 - ┌─────────────────────────────────────────────────────────────┐ 21 - │ Arabica AppView (Go Server) │ 22 - │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ 23 - │ │ Web UI │ │ OAuth │ │ atproto │ │ 24 - │ │ (HTMX/HTML) │ │ (indigo) │ │ Client │ │ 25 - │ └──────────────┘ └──────────────┘ └──────────────────┘ │ 26 - │ │ 27 - │ ┌──────────────────────────────────────────────────────┐ │ 28 - │ │ Future: AppView Index & Discovery │ │ 29 - │ │ - Firehose subscriber │ │ 30 - │ │ - Cross-user search & discovery │ │ 31 - │ │ - Social features │ │ 32 - │ └──────────────────────────────────────────────────────┘ │ 33 - └─────────────────────────────────────────────────────────────┘ 34 - 35 - ↓ OAuth + XRPC 36 - ┌───────────────────────────┐ 37 - │ User's PDS (any PDS) │ 38 - │ at://did:plc:abc123/ │ 39 - │ ├── com.arabica.brew │ 40 - │ ├── com.arabica.bean │ 41 - │ ├── com.arabica.roaster │ 42 - │ ├── com.arabica.grinder │ 43 - │ └── com.arabica.brewer │ 44 - └───────────────────────────┘ 45 - ``` 46 - 47 - --- 48 - 49 - ## Phase 0: Research & Validation (2-3 days) 50 - 51 - ### Goals 52 - 53 - - Understand `indigo` SDK capabilities 54 - - Validate lexicon schemas 55 - - Set up development environment 56 - - Test basic atproto operations 57 - 58 - ### Tasks 59 - 60 - #### 0.1: Research `indigo` SDK 61 - 62 - - [ ] Review `github.com/bluesky-social/indigo` documentation 63 - - [ ] Study OAuth implementation in indigo (with scopes) 64 - - [ ] Understand XRPC client usage 65 - - [ ] Review record CRUD operations API 66 - - [ ] Find example applications using indigo 67 - - [ ] Document findings in `docs/indigo-research.md` 68 - 69 - **Key Questions:** 70 - 71 - - Does indigo provide complete OAuth client with DPOP support? 72 - - What's the API for repo operations (createRecord, getRecord, listRecords, etc.)? 73 - - Are there session management helpers? 74 - - How are AT-URIs parsed and resolved? 75 - 76 - #### 0.2: Design & Validate Lexicons 77 - 78 - - [ ] Write lexicon JSON files for all record types: 79 - - `com.arabica.bean` 80 - - `com.arabica.roaster` 81 - - `com.arabica.grinder` 82 - - `com.arabica.brewer` 83 - - `com.arabica.brew` 84 - - [ ] Validate lexicons against atproto schema validator 85 - - [ ] Document schema decisions in `docs/schema-design.md` 86 - - [ ] Review field mappings from current SQLite schema 87 - 88 - **Key Decisions:** 89 - 90 - - Reference format: AT-URIs vs embedded objects 91 - - Pours: Embedded in brew record vs separate collection 92 - - Enums vs free text for fields like `method`, `roastLevel`, etc. 93 - - String length limits for all text fields 94 - 95 - #### 0.3: Set up Development Environment 96 - 97 - - [ ] Create/use Bluesky account for testing (or run local PDS) 98 - - [ ] Install atproto development tools (CLI, validators) 99 - - [ ] Set up test DID for development 100 - - [ ] Configure environment variables for PDS endpoints 101 - 102 - **Options:** 103 - 104 - - **Option A:** Use Bluesky PDS (bsky.social) - easiest, most realistic 105 - - **Option B:** Run local PDS via Docker - more control, offline dev 106 - - **Recommended:** Option A for initial development 107 - 108 - #### 0.4: Manual Record Creation Test 109 - 110 - - [ ] Manually create test records using atproto CLI or tools 111 - - [ ] Test all 5 record types (bean, roaster, grinder, brewer, brew) 112 - - [ ] Test cross-references (brew → bean → roaster) 113 - - [ ] Verify records appear in repo explorer 114 - - [ ] Query records back via API 115 - - [ ] Document any issues or schema adjustments needed 116 - 117 - **Deliverables:** 118 - 119 - - Working lexicon files in `lexicons/` directory 120 - - Test records in development PDS 121 - - Research notes documenting indigo SDK usage 122 - 123 - --- 124 - 125 - ## Phase 1: Core atproto Client Layer (3-4 days) 126 - 127 - ### Goals 128 - 129 - - Integrate `indigo` SDK 130 - - Create atproto client wrapper 131 - - Implement new Store interface using PDS operations 132 - - Replace SQLite dependency with atproto records 133 - 134 - ### Tasks 135 - 136 - #### 1.1: Project Structure Setup 137 - 138 - - [ ] Add `github.com/bluesky-social/indigo` to go.mod 139 - - [ ] Create new package structure: 140 - ``` 141 - internal/ 142 - ├── atproto/ 143 - │ ├── client.go # XRPC client wrapper 144 - │ ├── records.go # Record CRUD operations 145 - │ ├── resolver.go # AT-URI and reference resolution 146 - │ └── store.go # Store interface implementation 147 - ``` 148 - - [ ] Keep `internal/database/store.go` interface (for compatibility) 149 - - [ ] Move lexicon files to `lexicons/` at project root 150 - 151 - #### 1.2: atproto Client Wrapper (`internal/atproto/client.go`) 152 - 153 - Create a wrapper around indigo's XRPC client with high-level operations: 154 - 155 - ```go 156 - type Client struct { 157 - xrpc *xrpc.Client 158 - pdsURL string 159 - } 160 - 161 - func NewClient(pdsURL string) (*Client, error) 162 - ``` 163 - 164 - **Core Methods:** 165 - 166 - - [ ] `CreateRecord(did, collection, record)` → (rkey, error) 167 - - [ ] `GetRecord(did, collection, rkey)` → (record, error) 168 - - [ ] `ListRecords(did, collection, limit, cursor)` → (records, cursor, error) 169 - - [ ] `PutRecord(did, collection, rkey, record)` → error 170 - - [ ] `DeleteRecord(did, collection, rkey)` → error 171 - 172 - **Helper Methods:** 173 - 174 - - [ ] `ResolveATURI(uri string)` → (did, collection, rkey, error) 175 - - [ ] `BuildATURI(did, collection, rkey)` → string 176 - - [ ] Error handling and retry logic 177 - 178 - #### 1.3: Record Type Mapping (`internal/atproto/records.go`) 179 - 180 - Map Go structs to/from atproto records: 181 - 182 - ```go 183 - // Convert domain models to atproto records 184 - func BrewToRecord(brew *models.Brew) (map[string]interface{}, error) 185 - func RecordToBrew(record map[string]interface{}) (*models.Brew, error) 186 - 187 - // Similar for Bean, Roaster, Grinder, Brewer 188 - ``` 189 - 190 - **Considerations:** 191 - 192 - - [ ] Handle time.Time → RFC3339 string conversion 193 - - [ ] Build AT-URI references for foreign keys 194 - - [ ] Validate required fields before sending to PDS 195 - - [ ] Handle optional fields (nil pointers) 196 - 197 - #### 1.4: Reference Resolution (`internal/atproto/resolver.go`) 198 - 199 - Handle fetching referenced records: 200 - 201 - ```go 202 - // Resolve a single reference 203 - func (c *Client) ResolveReference(atURI string, target interface{}) error 204 - 205 - // Batch resolve multiple references (optimization for later) 206 - func (c *Client) BatchResolve(atURIs []string) (map[string]interface{}, error) 207 - ``` 208 - 209 - **Strategy (Phase 1):** 210 - 211 - - Start with simple lazy loading (one request per reference) 212 - - Document optimization opportunities for later 213 - 214 - #### 1.5: Store Implementation (`internal/atproto/store.go`) 215 - 216 - Implement the existing `database.Store` interface using atproto operations: 217 - 218 - ```go 219 - type AtprotoStore struct { 220 - client *Client 221 - did string // Current authenticated user's DID 222 - } 223 - 224 - func NewAtprotoStore(client *Client, did string) *AtprotoStore 225 - ``` 226 - 227 - **Implement all Store methods:** 228 - 229 - - [ ] Brew operations (CreateBrew, GetBrew, ListBrews, UpdateBrew, DeleteBrew) 230 - - [ ] Bean operations 231 - - [ ] Roaster operations 232 - - [ ] Grinder operations 233 - - [ ] Brewer operations 234 - - [ ] Pour operations (embedded in brews, special handling) 235 - 236 - **Key Changes from SQLite:** 237 - 238 - - No user_id field (implicit from DID) 239 - - References stored as AT-URIs 240 - - Need to resolve references when displaying 241 - - List operations may need pagination handling 242 - 243 - #### 1.6: Testing 1 + # Implementation Notes 244 2 245 - - [ ] Unit tests for client operations (against test PDS) 246 - - [ ] Test record conversion functions 247 - - [ ] Test reference resolution 248 - - [ ] Test Store interface implementation 249 - - [ ] Integration test: full CRUD cycle for each record type 3 + ## Current Status 250 4 251 - **Deliverables:** 5 + Arabica is a coffee tracking web application using AT Protocol for decentralized data storage. 252 6 253 - - Working atproto client layer 254 - - Store interface implemented via PDS 255 - - SQLite can be completely removed (but keep for sessions - see Phase 2) 256 - 257 - --- 258 - 259 - ## Phase 2: Authentication & OAuth (3-4 days) 260 - 261 - ### Goals 262 - 263 - - Implement atproto OAuth flow using indigo (with scopes, not app passwords) 264 - - Add session management 265 - - Add authentication middleware 266 - - Update UI for login/logout 267 - 268 - ### Tasks 269 - 270 - #### 2.1: OAuth Server Setup (`internal/atproto/oauth.go`) 271 - 272 - Implement OAuth using **indigo's OAuth client** (preferred approach): 273 - 274 - **IMPORTANT:** Use OAuth scopes, NOT app passwords. This provides proper security and user control. 275 - 276 - ```go 277 - type OAuthHandler struct { 278 - // Use indigo's OAuth client 279 - oauthClient *indigo_oauth.Client 280 - clientID string 281 - redirectURI string 282 - scopes []string // Standard: ["atproto", "transition:generic"] 283 - } 284 - ``` 285 - 286 - **OAuth Client Metadata (Required for Registration):** 287 - You'll need to register your OAuth client with atproto. Required metadata: 288 - 289 - - `client_id`: Your application identifier (e.g., `https://arabica.example.com/client-metadata.json`) 290 - - `client_name`: "Arabica Coffee Tracker" 291 - - `client_uri`: Your app homepage URL 292 - - `redirect_uris`: Array of callback URLs (e.g., `["https://arabica.example.com/oauth/callback"]`) 293 - - `scope`: Space-separated scopes (e.g., `"atproto transition:generic"`) 294 - - `grant_types`: `["authorization_code", "refresh_token"]` 295 - - `response_types`: `["code"]` 296 - - `token_endpoint_auth_method`: `"none"` (for public clients) 297 - - `application_type`: `"web"` 298 - - `dpop_bound_access_tokens`: `true` (REQUIRED - enables DPOP) 7 + **Completed:** 8 + - OAuth authentication with AT Protocol 9 + - Record CRUD operations for all entity types 10 + - Community feed from registered users 11 + - BoltDB for session persistence and feed registry 12 + - Mobile-friendly UI with HTMX 299 13 300 - **Client Metadata Hosting:** 14 + ## Architecture 301 15 302 - - [ ] Serve client metadata at `/.well-known/oauth-client-metadata` or at your `client_id` URL 303 - - [ ] Must be publicly accessible HTTPS endpoint 304 - - [ ] Content-Type: `application/json` 16 + ### Data Storage 17 + - User data: AT Protocol Personal Data Servers 18 + - Sessions: BoltDB (local) 19 + - Feed registry: BoltDB (local) 305 20 306 - **Required Endpoints:** 21 + ### Record Types 22 + - `social.arabica.alpha.bean` - Coffee beans 23 + - `social.arabica.alpha.roaster` - Roasters 24 + - `social.arabica.alpha.grinder` - Grinders 25 + - `social.arabica.alpha.brewer` - Brewing devices 26 + - `social.arabica.alpha.brew` - Brew sessions 307 27 308 - - [ ] `GET /login` - Initiate OAuth flow (handle → PDS discovery → auth endpoint) 309 - - [ ] `GET /oauth/callback` - Handle OAuth callback with authorization code 310 - - [ ] `POST /logout` - Clear session and revoke tokens 28 + ### Key Components 29 + - `internal/atproto/` - AT Protocol client and OAuth 30 + - `internal/handlers/` - HTTP request handlers 31 + - `internal/bff/` - Template rendering layer 32 + - `internal/feed/` - Community feed service 33 + - `internal/database/boltstore/` - BoltDB persistence 311 34 312 - **OAuth Flow with indigo:** 35 + ## Future Improvements 313 36 314 - 1. User enters their handle (e.g., `alice.bsky.social`) 315 - 2. Resolve handle → DID → PDS URL 316 - 3. Discover PDS OAuth endpoints (authorization_endpoint, token_endpoint) 317 - 4. Generate PKCE challenge and state 318 - 5. Build authorization URL with: 319 - - `client_id` 320 - - `redirect_uri` 321 - - `scope` (e.g., `"atproto transition:generic"`) 322 - - `response_type=code` 323 - - `code_challenge` and `code_challenge_method=S256` (PKCE) 324 - - `state` (CSRF protection) 325 - 6. Redirect user to PDS authorization endpoint 326 - 7. User authorizes on their PDS 327 - 8. PDS redirects back with authorization code 328 - 9. Exchange code for tokens using **DPOP**: 329 - - Generate DPOP proof JWT 330 - - POST to token_endpoint with code, PKCE verifier, and DPOP proof 331 - - Receive access_token, refresh_token (both DPOP-bound) 332 - 10. Store session with DID, tokens, and DPOP key 37 + ### Performance 38 + - Implement firehose subscriber for real-time feed updates 39 + - Add caching layer for frequently accessed records 40 + - Optimize parallel record fetching 333 41 334 - **Key Implementation Details:** 42 + ### Features 43 + - Search and filtering 44 + - User profiles and following 45 + - Recipe sharing 46 + - Statistics and analytics 335 47 336 - - [ ] Use indigo's OAuth client library (handles DPOP automatically) 337 - - [ ] Generate and store DPOP keypairs per session 338 - - [ ] All PDS API calls must include DPOP proof header 339 - - [ ] Handle token refresh (also requires DPOP) 340 - - [ ] Support multiple PDS providers (not just Bluesky) 341 - - [ ] Handle resolution and DID validation 342 - - [ ] PKCE for additional security 343 - 344 - **indigo OAuth Components to Use:** 345 - 346 - - Handle → DID resolution 347 - - PDS → OAuth endpoint discovery 348 - - DPOP key generation and proof creation 349 - - Token exchange with DPOP 350 - - Token refresh with DPOP 351 - 352 - **Security Considerations:** 353 - 354 - - [ ] Validate `state` parameter to prevent CSRF 355 - - [ ] Verify PKCE code_verifier matches challenge 356 - - [ ] Store DPOP private keys securely (encrypted in session) 357 - - [ ] Use HTTP-only, secure cookies for session ID 358 - - [ ] Implement token expiration checking 359 - - [ ] Revoke tokens on logout 360 - 361 - #### 2.2: Session Management 362 - 363 - Store authenticated sessions with user DID and tokens. 364 - 365 - **Options:** 366 - 367 - - **Development:** In-memory map (simple, ephemeral) 368 - - **Production:** Redis or SQLite for sessions 369 - 370 - **Decision (to be made in implementation):** 371 - 372 - - Start with in-memory for development 373 - - Document production session storage strategy 374 - - Use secure HTTP-only cookies for session ID 375 - 376 - **Session Structure:** 377 - 378 - ```go 379 - type Session struct { 380 - SessionID string 381 - DID string 382 - AccessToken string 383 - RefreshToken string 384 - ExpiresAt time.Time 385 - CreatedAt time.Time 386 - } 387 - ``` 388 - 389 - **Required Methods:** 390 - 391 - - [ ] `CreateSession(did, tokens)` → sessionID 392 - - [ ] `GetSession(sessionID)` → session 393 - - [ ] `DeleteSession(sessionID)` 394 - - [ ] `RefreshSession(sessionID)` → updated session 395 - 396 - #### 2.3: Authentication Middleware 397 - 398 - Add middleware to extract authenticated user from session: 399 - 400 - ```go 401 - func AuthMiddleware(next http.Handler) http.Handler 402 - 403 - // Context key for authenticated DID 404 - type contextKey string 405 - const userDIDKey contextKey = "userDID" 406 - 407 - // Helper to get DID from context 408 - func GetAuthenticatedDID(r *http.Request) (string, error) 409 - ``` 410 - 411 - **Behavior:** 412 - 413 - - Extract session cookie 414 - - Validate session exists and not expired 415 - - Add DID to request context 416 - - If invalid/missing: redirect to login (for protected routes) 417 - 418 - **Route Protection:** 419 - 420 - - [ ] Protected routes: All write operations (POST, PUT, DELETE) 421 - - [ ] Public routes: Home page, static assets 422 - - [ ] Semi-protected: Brew list (show your own if logged in, or empty state) 423 - 424 - #### 2.4: UI Updates for Authentication 425 - 426 - Update templates to support authentication: 427 - 428 - **New Templates:** 429 - 430 - - [ ] `login.tmpl` - Login page with OAuth button 431 - - [ ] Update `layout.tmpl` - Add user info header 432 - - If logged in: Show handle, logout button 433 - - If logged out: Show login button 434 - 435 - **Navigation Updates:** 436 - 437 - - [ ] Add user menu/dropdown 438 - - [ ] Link to profile/settings (future) 439 - - [ ] Display current user's handle 440 - 441 - **Empty States:** 442 - 443 - - [ ] If not logged in: Show welcome page with login prompt 444 - - [ ] If logged in but no data: Show getting started guide 445 - 446 - #### 2.5: Handler Updates 447 - 448 - Update handlers to use authenticated DID: 449 - 450 - ```go 451 - func (h *Handler) HandleBrewCreate(w http.ResponseWriter, r *http.Request) { 452 - // Get authenticated user's DID 453 - did, err := GetAuthenticatedDID(r) 454 - if err != nil { 455 - http.Error(w, "Unauthorized", http.StatusUnauthorized) 456 - return 457 - } 458 - 459 - // Create store scoped to this user 460 - store := atproto.NewAtprotoStore(h.client, did) 461 - 462 - // Rest of handler logic... 463 - } 464 - ``` 465 - 466 - **All handlers need:** 467 - 468 - - [ ] Extract DID from context 469 - - [ ] Create user-scoped store 470 - - [ ] Handle unauthenticated users gracefully 471 - 472 - #### 2.6: Configuration 473 - 474 - Add environment variables for OAuth: 475 - 476 - ```bash 477 - # OAuth Configuration 478 - OAUTH_CLIENT_ID=your_client_id 479 - OAUTH_CLIENT_SECRET=your_client_secret 480 - OAUTH_REDIRECT_URI=http://localhost:8080/oauth/callback 481 - OAUTH_SCOPES=atproto,transition:generic 482 - 483 - # PDS Configuration 484 - DEFAULT_PDS_URL=https://bsky.social # or support dynamic discovery 485 - 486 - # Session Configuration 487 - SESSION_SECRET=random_secret_key 488 - SESSION_MAX_AGE=86400 # 24 hours 489 - ``` 490 - 491 - **Deliverables:** 492 - 493 - - Working OAuth login flow 494 - - Session management 495 - - Protected routes with authentication 496 - - Updated UI with login/logout 497 - - Users can authenticate and access their own data 498 - 499 - --- 500 - 501 - ## Phase 3: Handler & UI Refactoring (2-3 days) 502 - 503 - ### Goals 504 - 505 - - Update all handlers to work with atproto store 506 - - Handle reference resolution in UI 507 - - Remove SQLite dependency 508 - - Improve error handling 509 - 510 - ### Tasks 511 - 512 - #### 3.1: Update All Handlers 513 - 514 - Modify handlers to use atproto store with authenticated DID: 515 - 516 - **Handlers to update:** 517 - 518 - - [ ] `HandleBrewList` - List user's brews with resolved references 519 - - [ ] `HandleBrewNew` - Create new brew 520 - - [ ] `HandleBrewEdit` - Edit existing brew 521 - - [ ] `HandleBrewCreate` - Process create form 522 - - [ ] `HandleBrewUpdate` - Process update form 523 - - [ ] `HandleBrewDelete` - Delete brew 524 - - [ ] `HandleBrewExport` - Export from PDS (not SQLite) 525 - - [ ] Bean CRUD handlers (create, update, delete) 526 - - [ ] Roaster CRUD handlers 527 - - [ ] Grinder CRUD handlers 528 - - [ ] Brewer CRUD handlers 529 - - [ ] `HandleManage` - Manage all resources 530 - 531 - **Key Changes:** 532 - 533 - - Get DID from request context 534 - - Create user-scoped store 535 - - Handle references (AT-URIs) in forms 536 - - Resolve references for display 537 - - Error handling for PDS operations 538 - 539 - #### 3.2: Reference Handling in UI 540 - 541 - Update forms and displays to handle AT-URI references: 542 - 543 - **Brew Form Updates:** 544 - 545 - - [ ] Bean selector: Show user's beans, store AT-URI 546 - - [ ] Roaster selector: Show user's roasters (via bean's roasterRef) 547 - - [ ] Grinder selector: Show user's grinders, store AT-URI 548 - - [ ] Brewer selector: Show user's brewers, store AT-URI 549 - 550 - **Display Updates:** 551 - 552 - - [ ] Resolve bean reference when displaying brew 553 - - [ ] Resolve grinder reference 554 - - [ ] Resolve brewer reference 555 - - [ ] Show resolved data (bean name, roaster name, etc.) 556 - - [ ] Handle broken references gracefully (show "Unknown" or ID) 557 - 558 - **New Bean Form:** 559 - 560 - - [ ] Roaster selector stores AT-URI reference 561 - - [ ] Create new roaster inline (stores as new record, returns AT-URI) 562 - 563 - #### 3.3: Error Handling 564 - 565 - Improve error handling for atproto operations: 566 - 567 - **PDS Operation Errors:** 568 - 569 - - [ ] Network failures - retry with backoff 570 - - [ ] Authentication failures - redirect to login 571 - - [ ] Rate limiting - show user-friendly message 572 - - [ ] Invalid records - show validation errors 573 - - [ ] Missing references - handle gracefully 574 - 575 - **User-Friendly Error Pages:** 576 - 577 - - [ ] 401 Unauthorized - redirect to login 578 - - [ ] 404 Not Found - "Record not found" page 579 - - [ ] 500 Server Error - "Something went wrong" with error ID 580 - - [ ] PDS Unavailable - "Cannot connect to your PDS" message 581 - 582 - **Logging:** 583 - 584 - - [ ] Log all PDS operations 585 - - [ ] Log OAuth flow steps 586 - - [ ] Log errors with context (DID, operation, error) 587 - - [ ] Add request ID for tracking 588 - 589 - #### 3.4: Remove SQLite Dependency 590 - 591 - - [ ] Remove `internal/database/sqlite/` package 592 - - [ ] Remove SQLite imports from main.go 593 - - [ ] Remove database migrations 594 - - [ ] Update go.mod (remove modernc.org/sqlite) 595 - - [ ] Update README (remove SQLite references) 596 - 597 - **Note:** May keep SQLite or add Redis for session storage in production (TBD). 598 - 599 - #### 3.5: Update PWA/Offline Handling 600 - 601 - Since app now requires online access to PDS: 602 - 603 - **Options:** 604 - 605 - - [ ] **Option A:** Remove service worker and PWA manifest (simple) 606 - - [ ] **Option B:** Keep PWA but update service worker to only cache static assets 607 - - [ ] **Option C:** Add offline queue for writes (complex, future enhancement) 608 - 609 - **Recommendation:** Option B - keep PWA for "Add to Home Screen" but require online for data. 610 - 611 - #### 3.6: Testing 612 - 613 - - [ ] Manual testing of full user flow: 614 - 1. Login with OAuth 615 - 2. Create beans, roasters, grinders, brewers 616 - 3. Create brews with references 617 - 4. Edit brews 618 - 5. Delete brews 619 - 6. Logout and verify data persists (in PDS) 620 - 7. Login again and verify data loads 621 - - [ ] Test error scenarios (network failures, invalid data, etc.) 622 - - [ ] Test with multiple users (different DIDs) 623 - 624 - **Deliverables:** 625 - 626 - - Fully functional personal coffee tracker using atproto 627 - - All CRUD operations working 628 - - Reference resolution working 629 - - SQLite removed 630 - - Error handling improved 631 - 632 - --- 633 - 634 - ## Phase 4: Polish & Documentation (2-3 days) 635 - 636 - ### Goals 637 - 638 - - Production-ready configuration 639 - - Deployment documentation 640 - - User documentation 641 - - Clean up code 642 - 643 - ### Tasks 644 - 645 - #### 4.1: Production Configuration 646 - 647 - - [ ] Environment variable validation on startup 648 - - [ ] Configuration file support (optional) 649 - - [ ] Secure session storage (Redis or encrypted SQLite) 650 - - [ ] HTTPS/TLS configuration 651 - - [ ] Rate limiting for API endpoints 652 - - [ ] CORS configuration 653 - 654 - #### 4.2: Deployment Preparation 655 - 656 - - [ ] Create Dockerfile 657 - - [ ] Docker compose for development (app + Redis) 658 - - [ ] Document environment variables 659 - - [ ] Health check endpoint (`/health`) 660 - - [ ] Graceful shutdown handling 661 - - [ ] Production logging configuration 662 - 663 - #### 4.3: Documentation 664 - 665 - - [ ] Update README.md: 666 - - New architecture overview 667 - - atproto concepts 668 - - Setup instructions 669 - - Environment variables 670 - - Deployment guide 671 - - [ ] Create DEPLOYMENT.md: 672 - - Server requirements 673 - - OAuth app registration 674 - - Domain/DNS setup 675 - - SSL certificate setup 676 - - Systemd service example 677 - - [ ] Create SELF-HOSTING.md: 678 - - Guide for others to run their own AppView 679 - - Configuration options 680 - - Maintenance tasks 681 - - [ ] Create docs/LEXICONS.md: 682 - - Document all lexicon schemas 683 - - Field descriptions 684 - - Reference patterns 685 - - Examples 686 - 687 - #### 4.4: Code Cleanup 688 - 689 - - [ ] Add/improve code comments 690 - - [ ] Consistent error handling patterns 691 - - [ ] Extract magic constants to config 692 - - [ ] Remove dead code 693 - - [ ] Format all code (gofmt) 694 - - [ ] Lint and fix issues (golangci-lint) 695 - 696 - #### 4.5: Testing 697 - 698 - - [ ] Write unit tests for critical paths 699 - - [ ] Integration tests for OAuth flow 700 - - [ ] Test deployment in production-like environment 701 - - [ ] Load testing (basic) 702 - - [ ] Security review (OWASP basics) 703 - 704 - **Deliverables:** 705 - 706 - - Production-ready application 707 - - Complete documentation 708 - - Deployment artifacts (Docker, etc.) 709 - - Ready to launch 710 - 711 - --- 712 - 713 - ## Phase 5: Launch & Initial Users (TBD) 714 - 715 - ### Goals 716 - 717 - - Deploy to production 718 - - Onboard initial users 719 - - Monitor and fix issues 720 - - Gather feedback 721 - 722 - ### Tasks 723 - 724 - - [ ] Deploy to production hosting 725 - - [ ] Set up monitoring and alerting 726 - - [ ] Create landing page explaining the project 727 - - [ ] Announce in atproto community 728 - - [ ] Gather user feedback 729 - - [ ] Fix bugs and usability issues 730 - - [ ] Iterate based on feedback 731 - 732 - **Success Metrics:** 733 - 734 - - Users can successfully authenticate 735 - - Users can create and manage their coffee data 736 - - Data persists in their PDS 737 - - No critical bugs 738 - 739 - --- 740 - 741 - ## Future Enhancements (Post-Launch) 742 - 743 - ### Phase 6: Public Browsing & Discovery (Future) 744 - 745 - **Goal:** Allow users to discover and view other users' coffee brews. 746 - 747 - **Features:** 748 - 749 - - [ ] Public profile pages (`/users/:did`) 750 - - [ ] Browse any user's public brews 751 - - [ ] Manual DID entry for discovery 752 - - [ ] Handle resolution (DID → handle display) 753 - - [ ] Basic search within a user's data 754 - 755 - **Technical:** 756 - 757 - - [ ] Query any PDS for public records 758 - - [ ] Cache results for performance 759 - - [ ] Handle PDS availability issues 760 - 761 - ### Phase 7: AppView Indexing (Future) 762 - 763 - **Goal:** Build a centralized index for cross-user discovery. 764 - 765 - **Features:** 766 - 767 - - [ ] Firehose subscription 768 - - [ ] Index public arabica records from all PDSs 769 - - [ ] Cross-user search (by bean, roaster, method, etc.) 770 - - [ ] Trending/popular content 771 - - [ ] User directory 772 - 773 - **Technical:** 774 - 775 - - [ ] Add PostgreSQL for index storage 776 - - [ ] Firehose consumer using indigo 777 - - [ ] Background indexing jobs 778 - - [ ] Search API 779 - 780 - ### Phase 8: Social Features (Future) 781 - 782 - **Goal:** Add social interactions around coffee. 783 - 784 - **Features:** 785 - 786 - - [ ] Follow users 787 - - [ ] Like/bookmark brews 788 - - [ ] Comments on brews (atproto replies) 789 - - [ ] Share brews 790 - - [ ] Brew collections/lists 791 - 792 - **Technical:** 793 - 794 - - [ ] New lexicons for likes, follows, etc. 795 - - [ ] Integration with atproto social graph 796 - - [ ] Notification system 797 - 798 - ### Phase 9: Advanced Features (Future) 799 - 800 - **Ideas for future consideration:** 801 - 802 - - [ ] Statistics and analytics (personal insights) 803 - - [ ] Brew recipes and recommendations 804 - - [ ] Photo uploads (blob storage in PDS) 805 - - [ ] Equipment database (community-maintained) 806 - - [ ] Taste profile analysis 807 - - [ ] CSV import/export 808 - - [ ] Mobile native apps (using same lexicons) 809 - 810 - ### Phase 10: Performance & Scale (Future) 811 - 812 - **Optimizations when needed:** 813 - 814 - - [ ] Implement caching layer (Redis/SQLite) 815 - - [ ] Batch reference resolution 816 - - [ ] CDN for static assets 817 - - [ ] Optimize PDS queries 818 - - [ ] Background sync for index 819 - - [ ] Horizontal scaling 820 - 821 - --- 822 - 823 - ## Open Questions & Decisions 824 - 825 - ### To Be Decided During Implementation 826 - 827 - #### Authentication & Session Management 828 - 829 - - **Q:** Use in-memory sessions (dev) or add Redis immediately? 830 - - **Q:** Session timeout duration? 831 - - **Q:** Support "remember me" functionality? 832 - - **Decision:** TBD based on hosting environment 833 - 834 - #### Reference Resolution Strategy 835 - 836 - - **Q:** Lazy loading (simple) or batch resolution (complex)? 837 - - **Q:** Cache resolved references? 838 - - **Decision:** Start with lazy loading, optimize later if needed 839 - 840 - #### PDS Support 841 - 842 - - **Q:** Bluesky only, or support any PDS from day 1? 843 - - **Q:** How to handle PDS discovery (handle → PDS URL)? 844 - - **Decision:** Support any PDS, use handle resolution 845 - 846 - #### Error Handling Philosophy 847 - 848 - - **Q:** Detailed errors for debugging vs user-friendly messages? 849 - - **Q:** Retry strategy for PDS operations? 850 - - **Decision:** User-friendly errors, log details, retry with backoff 851 - 852 - #### Lexicon Publishing 853 - 854 - - **Q:** Where to host lexicon files publicly? 855 - - **Options:** 856 - - GitHub repo (easy) 857 - - `.well-known` on domain (proper) 858 - - Both 859 - - **Decision:** GitHub for now, add .well-known later 860 - 861 - #### Export Functionality 862 - 863 - - **Q:** Keep JSON export feature? 864 - - **Q:** Export from PDS or from AppView cache? 865 - - **Decision:** Keep export, fetch from PDS 866 - 867 - #### PWA/Offline 868 - 869 - - **Q:** Remove service worker entirely? 870 - - **Q:** Keep PWA manifest for "Add to Home Screen"? 871 - - **Decision:** Keep manifest, update service worker for static-only caching 872 - 873 - --- 874 - 875 - ## Timeline Summary 876 - 877 - | Phase | Duration | Key Milestone | 878 - | --------------------------- | -------- | ------------------------------------------- | 879 - | 0: Research & Validation | 2-3 days | Lexicons validated, indigo SDK understood | 880 - | 1: Core atproto Client | 3-4 days | PDS operations working, SQLite removed | 881 - | 2: Authentication & OAuth | 3-4 days | Users can login with atproto OAuth (scopes) | 882 - | 3: Handler & UI Refactoring | 2-3 days | Full app working with atproto | 883 - | 4: Polish & Documentation | 2-3 days | Production ready, documented | 884 - | 5: Launch | Variable | Live with initial users | 885 - 886 - **Total Estimated Time: 12-17 days** of focused development work 887 - 888 - **Future phases:** TBD based on user feedback and priorities 889 - 890 - --- 891 - 892 - ## Success Criteria 893 - 894 - ### Phase 1-4 Complete (Personal Tracker v1) 895 - 896 - - [ ] Users can authenticate via atproto OAuth with proper scopes 897 - - [ ] Users can create, edit, delete all coffee tracking entities 898 - - [ ] Data persists in user's PDS (any PDS) 899 - - [ ] References between records work correctly 900 - - [ ] App is self-hostable by others 901 - - [ ] Documentation is complete and accurate 902 - - [ ] No dependency on SQLite for data storage 903 - - [ ] Existing UX/UI is preserved 904 - 905 - ### Future Success (Discovery & Social) 906 - 907 - - [ ] Users can discover other coffee enthusiasts 908 - - [ ] Cross-user search and browsing works 909 - - [ ] Social features enable community building 910 - - [ ] AppView scales to many users 911 - 912 - --- 913 - 914 - ## Resources & References 915 - 916 - ### Documentation 917 - 918 - - [AT Protocol Docs](https://atproto.com) 919 - - [Indigo SDK](https://github.com/bluesky-social/indigo) 920 - - [Lexicon Specification](https://atproto.com/specs/lexicon) 921 - - [OAuth DPOP](https://atproto.com/specs/oauth) 922 - 923 - ### Example Applications 924 - 925 - - Bluesky (reference implementation) 926 - - Other atproto apps (TBD - research during Phase 0) 927 - 928 - ### Tools 929 - 930 - - [atproto CLI tools](https://github.com/bluesky-social/atproto) 931 - - PDS explorer tools 932 - - Lexicon validators 933 - 934 - --- 935 - 936 - ## Notes 937 - 938 - - This plan is a living document and will be updated as we learn more 939 - - Technical decisions may change based on discoveries during implementation 940 - - Timeline estimates are rough and may vary 941 - - Focus is on shipping a working v1 (personal tracker) before adding social features 942 - - OAuth must use scopes, not app passwords, for proper security and user control 48 + ### Infrastructure 49 + - Production deployment guide 50 + - Monitoring and logging improvements 51 + - Rate limiting and abuse prevention
+45 -163
README.md
··· 1 - # Arabica - Coffee Brew Tracker 2 - 3 - A self-hosted web application for tracking your coffee brewing journey. Built with Go and SQLite. 1 + # Arabica 4 2 5 - ## Features 6 - 7 - - 📝 Quick entry of brew data (temperature, time, method, flexible grind size entry, etc.) 8 - - ☕ Organize beans by origin and roaster with quick-select dropdowns 9 - - 📱 Mobile-first PWA design for on-the-go tracking 10 - - 📊 Rating system and tasting notes 11 - - 📥 Export your data as JSON 12 - - 🔄 CRUD operations for all brew entries 13 - - 🗄️ SQLite database with abstraction layer for easy migration 3 + Coffee brew tracking application using AT Protocol for decentralized storage. 14 4 15 5 ## Tech Stack 16 6 17 - - **Backend**: Go 1.22+ (using stdlib router) 18 - - **Database**: SQLite (via modernc.org/sqlite - pure Go, no CGO) 19 - - **Templates**: html/template (Go standard library) 20 - - **Frontend**: HTMX + Alpine.js 21 - - **CSS**: Tailwind CSS 22 - - **PWA**: Service Worker for offline support 23 - 24 - ## Project Structure 25 - 26 - ``` 27 - arabica/ 28 - ├── cmd/server/ # Application entry point 29 - ├── internal/ 30 - │ ├── database/ # Database interface & SQLite implementation 31 - │ ├── models/ # Data models 32 - │ ├── handlers/ # HTTP handlers 33 - │ └── templates/ # HTML templates 34 - ├── web/static/ # Static assets (CSS, JS, PWA files) 35 - └── migrations/ # Database migrations 36 - ``` 37 - 38 - ## Getting Started 39 - 40 - ### Prerequisites 41 - 42 - Use Nix for a reproducible development environment with all dependencies: 7 + - **Backend:** Go with stdlib HTTP router 8 + - **Storage:** AT Protocol Personal Data Servers 9 + - **Local DB:** BoltDB for OAuth sessions and feed registry 10 + - **Templates:** html/template 11 + - **Frontend:** HTMX + Alpine.js + Tailwind CSS 43 12 44 - ```bash 45 - nix develop 46 - ``` 13 + ## Quick Start 47 14 48 - ### Running the Application 49 - 50 - 1. Enter the Nix development environment: 51 15 ```bash 52 - nix develop 53 - ``` 16 + # Using Nix 17 + nix run 54 18 55 - 2. Build and run the server: 56 - ```bash 57 - go run ./cmd/server 19 + # Or with Go 20 + go run cmd/server/main.go 58 21 ``` 59 22 60 - The application will be available at `http://localhost:8080` 61 - 62 - ## Usage 63 - 64 - ### Adding a Brew 65 - 66 - 1. Navigate to "New Brew" from the home page 67 - 2. Select a bean (or add a new one with the "+ New" button) 68 - - When adding a new bean, provide a **Name** (required) like "Morning Blend" or "House Espresso" 69 - - Optionally add Origin, Roast Level, and Description 70 - 3. Select a roaster (or add a new one) 71 - 4. Fill in brewing details: 72 - - Method (Pour Over, French Press, etc.) 73 - - Temperature (°C) 74 - - Brew time (seconds) 75 - - Grind size (free text - enter numbers like "18" or "3.5" for grinder settings, or descriptions like "Medium" or "Fine") 76 - - Grinder (optional) 77 - - Tasting notes 78 - - Rating (1-10) 79 - 5. Click "Save Brew" 80 - 81 - ### Viewing Brews 82 - 83 - Navigate to the "Brews" page to see all your entries in a table format with: 84 - - Date 85 - - Bean details 86 - - Roaster 87 - - Method and parameters 88 - - Rating 89 - - Actions (View, Delete) 90 - 91 - ### Exporting Data 92 - 93 - Click "Export JSON" on the brews page to download all your data as JSON. 23 + Access at http://localhost:18910 94 24 95 25 ## Configuration 96 26 97 27 Environment variables: 98 28 99 - - `DB_PATH`: Path to SQLite database (default: `$HOME/.local/share/arabica/arabica.db` or XDG_DATA_HOME) 100 - - `PORT`: Server port (default: `18910`) 101 - - `LOG_LEVEL`: Logging level - `debug`, `info`, `warn`, or `error` (default: `info`) 102 - - `LOG_FORMAT`: Log output format - `console` (pretty, colored) or `json` (structured) (default: `console`) 103 - - `OAUTH_CLIENT_ID`: OAuth client ID for ATProto authentication (optional, uses localhost mode if not set) 104 - - `OAUTH_REDIRECT_URI`: OAuth redirect URI (optional, auto-configured for localhost) 105 - - `SECURE_COOKIES`: Set to `true` for production HTTPS environments (default: `false`) 106 - 107 - ### Logging 29 + - `PORT` - Server port (default: 18910) 30 + - `ARABICA_DB_PATH` - BoltDB path (default: ~/.local/share/arabica/arabica.db) 31 + - `OAUTH_CLIENT_ID` - OAuth client ID (optional, uses localhost mode if not set) 32 + - `OAUTH_REDIRECT_URI` - OAuth redirect URI (optional) 33 + - `SECURE_COOKIES` - Set to true for HTTPS (default: false) 34 + - `LOG_LEVEL` - Logging level: debug, info, warn, error (default: info) 35 + - `LOG_FORMAT` - Log format: console, json (default: console) 108 36 109 - The application uses [zerolog](https://github.com/rs/zerolog) for structured logging with the following features: 37 + ## Features 110 38 111 - **Log Levels:** 112 - - `debug` - Detailed information including all PDS requests/responses 113 - - `info` - General application flow (default) 114 - - `warn` - Warning messages (non-fatal issues) 115 - - `error` - Error messages 39 + - Track coffee brews with detailed parameters 40 + - Store data in your AT Protocol Personal Data Server 41 + - Community feed of recent brews from registered users 42 + - Manage beans, roasters, grinders, and brewers 43 + - Export brew data as JSON 44 + - Mobile-friendly PWA design 116 45 117 - **Log Formats:** 118 - - `console` (default) - Human-readable, colored output for development 119 - - `json` - Structured JSON logs for production/log aggregation 46 + ## Architecture 120 47 121 - **Request Logging:** 122 - All HTTP requests are logged with: 123 - - Method, path, query parameters 124 - - Status code, response time, bytes written 125 - - Client IP, user agent, referer 126 - - Authenticated user DID (if logged in) 127 - - Content type 48 + Data is stored in AT Protocol records on users' Personal Data Servers. The application uses OAuth to authenticate with the PDS and performs all CRUD operations via the AT Protocol API. 128 49 129 - **PDS Request Logging:** 130 - All ATProto PDS operations are logged (at `debug` level) with: 131 - - Operation type (createRecord, getRecord, listRecords, etc.) 132 - - Collection name, record key 133 - - User DID 134 - - Request duration 135 - - Record counts for list operations 136 - - Pagination details 50 + Local BoltDB stores: 51 + - OAuth session data 52 + - Feed registry (list of DIDs for community feed) 137 53 138 - **Example configurations:** 54 + See docs/ for detailed documentation. 139 55 140 - Development (verbose): 141 - ```bash 142 - LOG_LEVEL=debug LOG_FORMAT=console go run ./cmd/server 143 - ``` 56 + ## Development 144 57 145 - Production (structured): 146 58 ```bash 147 - LOG_LEVEL=info LOG_FORMAT=json SECURE_COOKIES=true ./arabica-server 148 - ``` 149 - 150 - ## Database Abstraction 151 - 152 - The application uses an interface-based approach for database operations, making it easy to swap SQLite for PostgreSQL or another database later. See `internal/database/store.go` for the interface definition. 153 - 154 - ## PWA Support 155 - 156 - The application includes: 157 - - Web App Manifest for "Add to Home Screen" 158 - - Service Worker for offline caching 159 - - Mobile-optimized UI with large touch targets 160 - 161 - ## Future Enhancements (Not in MVP) 162 - 163 - - Statistics and analytics page 164 - - CSV export 165 - - Multi-user support (database already has user_id column) 166 - - Search and filtering 167 - - Photo uploads for beans/brews 168 - - Brew recipes and sharing 169 - 170 - ## Development Notes 59 + # Enter development environment 60 + nix develop 171 61 172 - ### Why These Choices? 62 + # Run server 63 + go run cmd/server/main.go 173 64 174 - - **Go**: Fast compilation, single binary deployment, excellent stdlib 175 - - **modernc.org/sqlite**: Pure Go SQLite (no CGO), easy cross-compilation 176 - - **html/template**: Built-in Go templates, no external dependencies 177 - - **HTMX**: Progressive enhancement without heavy JS framework 178 - - **Nix**: Reproducible development environment 65 + # Run tests 66 + go test ./... 179 67 180 - ### Database Schema 68 + # Build 69 + go build -o arabica cmd/server/main.go 70 + ``` 181 71 182 - See `migrations/001_initial.sql` for the complete schema. 72 + ## Deployment 183 73 184 - Key tables: 185 - - `users`: Future multi-user support 186 - - `beans`: Coffee bean information 187 - - `roasters`: Roaster information 188 - - `brews`: Individual brew records with all parameters 74 + See docs/nix-install.md for NixOS deployment instructions. 189 75 190 76 ## License 191 77 192 78 MIT 193 - 194 - ## Contributing 195 - 196 - This is a personal project, but suggestions and improvements are welcome!
+1 -150
docs/auth-required-ui.md
··· 1 - # Auth-Required UI Changes 2 - 3 - ## Changes Made 4 - 5 - ### 1. Home Page - Login Button 6 - 7 - **File:** `internal/templates/home.tmpl` 8 - 9 - **When Not Authenticated:** 10 - - Shows welcome message explaining AT Protocol 11 - - Large "Log In with AT Protocol" button 12 - - Lists Arabica features (decentralized, portable data, etc.) 13 - 14 - **When Authenticated:** 15 - - Shows user's DID 16 - - "Add New Brew" and "View All Brews" buttons 17 - - Full functionality 18 - 19 - ### 2. Navigation Bar - Conditional Links 20 - 21 - **File:** `internal/templates/layout.tmpl` 22 - 23 - **When Not Authenticated:** 24 - - Shows: Home, Login button 25 - - Hides: Brews, New Brew, Manage 26 - 27 - **When Authenticated:** 28 - - Shows: Home, Brews, New Brew, Manage, Logout button 29 - - Logout button triggers POST to `/logout` 30 - 31 - ### 3. Removed SQLite Fallback 32 - 33 - **File:** `internal/handlers/handlers.go` 34 - 35 - **All protected handlers now:** 36 - ```go 37 - // Require authentication 38 - store, authenticated := h.getAtprotoStore(r) 39 - if !authenticated { 40 - http.Redirect(w, r, "/login", http.StatusFound) 41 - return 42 - } 43 - ``` 44 - 45 - **Affected handlers:** 46 - - `HandleBrewList` - redirects to login 47 - - `HandleBrewNew` - redirects to login 48 - - `HandleBrewEdit` - redirects to login 49 - - `HandleBrewCreate` - redirects to login 50 - - `HandleManage` - redirects to login 51 - - `HandleBeanCreate` - returns 401 (API endpoint) 52 - - `HandleRoasterCreate` - returns 401 (API endpoint) 53 - 54 - **SQLite is NO LONGER used** - all data operations require authentication and use PDS storage. 55 - 56 - ### 4. Template Data Structure 57 - 58 - **File:** `internal/templates/render.go` 59 - 60 - **Added to PageData:** 61 - ```go 62 - type PageData struct { 63 - // ... existing fields 64 - IsAuthenticated bool 65 - UserDID string 66 - } 67 - ``` 68 - 69 - **All render functions updated** to accept and pass authentication status: 70 - - `RenderHome(w, isAuthenticated, userDID)` 71 - - `RenderBrewList(w, brews, isAuthenticated, userDID)` 72 - - `RenderBrewForm(w, beans, roasters, grinders, brewers, brew, isAuthenticated, userDID)` 73 - - `RenderManage(w, beans, roasters, grinders, brewers, isAuthenticated, userDID)` 74 - 75 - ## User Experience 76 - 77 - ### First Visit (Not Logged In) 78 - 1. Visit http://localhost:18910 79 - 2. See welcome page with "Log In with AT Protocol" button 80 - 3. Nav bar only shows "Home" and "Login" 81 - 4. Cannot access /brews, /manage, etc. (redirects to /login) 82 - 83 - ### After Login 84 - 1. Click "Log In with AT Protocol" 85 - 2. Enter your handle (e.g., `yourname.bsky.social`) 86 - 3. Authenticate on your PDS 87 - 4. Redirected to home page 88 - 5. Nav bar shows all links + Logout button 89 - 6. Full app functionality available 90 - 7. All data stored in YOUR PDS 91 - 92 - ### Logout 93 - 1. Click "Logout" in nav bar 94 - 2. Session cleared 95 - 3. Redirected to home page (unauthenticated view) 96 - 97 - ## Benefits 98 - 99 - ### Security 100 - - No data leakage between users 101 - - Cannot access app without authentication 102 - - Each user only sees their own data 103 - 104 - ### Privacy 105 - - Your data lives in YOUR PDS 106 - - Not stored on Arabica server 107 - - You control your data 108 - 109 - ### User Clarity 110 - - Clear distinction between logged in/out states 111 - - Obvious call-to-action to log in 112 - - Shows which DID you're logged in as 113 - 114 - ## Testing 115 - 116 - 1. **Start server:** 117 - ```bash 118 - go build ./cmd/server 119 - ./server 120 - ``` 121 - 122 - 2. **Visit home page (logged out):** 123 - - Go to http://localhost:18910 124 - - Should see login button 125 - - Nav bar should only show Home + Login 126 - 127 - 3. **Try accessing protected pages:** 128 - - Go to http://localhost:18910/brews 129 - - Should redirect to /login 130 - 131 - 4. **Log in:** 132 - - Click "Log In with AT Protocol" 133 - - Enter your Bluesky handle 134 - - Authenticate 135 - 136 - 5. **Verify logged in state:** 137 - - Home page shows your DID 138 - - Nav bar shows all links + Logout 139 - - Can access /brews, /manage, etc. 140 - 141 - 6. **Test logout:** 142 - - Click "Logout" in nav 143 - - Should return to unauthenticated home page 144 - 145 - ## Next Steps 146 - 147 - Now that auth is working and UI is clean: 148 - 1. Test creating a bean from /manage 149 - 2. Verify it appears in your PDS 150 - 3. Fix the ID/rkey issue so you can create brews that reference beans 1 + # Deleted - Remove this file
+1 -152
docs/future-witness-cache.md
··· 1 - # Future Architecture Notes 2 - 3 - ## Witness Cache Pattern (from Paul Frazee) 4 - 5 - ### Concept 6 - Many Atmosphere backends should start with a local "witness cache" of repositories. 7 - 8 - **What is a Witness Cache?** 9 - - A copy of repository records 10 - - Plus a timestamp of when the record was indexed (the "witness time") 11 - - Must be kept/preserved 12 - 13 - ### Key Benefits 14 - 15 - #### 1. Local Replay Capability 16 - With local replay, you can: 17 - - Add new tables or indexes to your backend 18 - - Quickly backfill the data from local cache 19 - - **Without** having to backfill from the network (which is slow) 20 - 21 - #### 2. Fast Iteration 22 - - Change your data model 23 - - Add new indexes 24 - - Reprocess data quickly 25 - - No network bottleneck 26 - 27 - ### Technology Recommendations 28 - 29 - #### Good Candidates: 30 - 31 - **RocksDB or other LSMs (Log-Structured Merge-Trees)** 32 - - Excellent write throughput 33 - - Good for high-volume ingestion 34 - - Used by many distributed systems 35 - 36 - **ClickHouse** 37 - - Good compression ratio 38 - - Analytics-focused 39 - - Fast columnar queries 40 - 41 - **DuckDB** 42 - - Good compression ratio 43 - - Embedded database 44 - - Great for analytics 45 - - Easy integration 46 - 47 - ### Implementation Timeline 48 - 49 - **Phase 1-4 (Current):** Direct PDS queries (no cache) 50 - - Simple implementation 51 - - Works for single-user or small datasets 52 - - Good for understanding the data model 53 - 54 - **Phase 6-7 (AppView + Indexing):** Add witness cache 55 - - When building firehose consumer 56 - - When indexing multiple users 57 - - When cross-user queries become important 58 - 59 - **Phase 10 (Performance & Scale):** Optimize cache 60 - - Choose between RocksDB/ClickHouse/DuckDB based on usage patterns 61 - - Implement replay/backfill mechanisms 62 - - Add monitoring and metrics 63 - 64 - ### Architecture Sketch 65 - 66 - ``` 67 - ┌─────────────────────────────────────────────┐ 68 - │ Firehose Consumer │ 69 - │ (Subscribes to repo commit events) │ 70 - └────────────────┬────────────────────────────┘ 71 - 72 - ↓ Write with witness time 73 - ┌─────────────────────────────────────────────┐ 74 - │ Witness Cache │ 75 - │ (RocksDB / ClickHouse / DuckDB) │ 76 - │ │ 77 - │ Record: { │ 78 - │ did: "did:plc:abc123" │ 79 - │ collection: "com.arabica.brew" │ 80 - │ rkey: "3jxy..." │ 81 - │ record: {...} │ 82 - │ witness_time: "2024-01-04T20:00:00Z" │ 83 - │ cid: "baf..." │ 84 - │ } │ 85 - └────────────────┬────────────────────────────┘ 86 - 87 - ↓ Query/Transform 88 - ┌─────────────────────────────────────────────┐ 89 - │ Application Database │ 90 - │ (PostgreSQL / SQLite) │ 91 - │ │ 92 - │ - Denormalized views │ 93 - │ - Application-specific indexes │ 94 - │ - Can be rebuilt from witness cache │ 95 - └─────────────────────────────────────────────┘ 96 - ``` 97 - 98 - ### Key Principles 99 - 100 - 1. **Witness cache is immutable append-only log** 101 - - Never delete records 102 - - Keep deletion markers 103 - - Keep all historical data 104 - 105 - 2. **Application DB is derived state** 106 - - Can be dropped and rebuilt 107 - - Optimized for queries 108 - - Contains denormalized data 109 - 110 - 3. **Replay = rebuild application DB from cache** 111 - - Fast because it's local 112 - - No network calls 113 - - Consistent state 114 - 115 - ### Migration Strategy 116 - 117 - **Current (Phase 1-4):** 118 - ``` 119 - User's PDS → XRPC → Your App → SQLite (for UI) 120 - ``` 121 - 122 - **Future (Phase 6+):** 123 - ``` 124 - Firehose → Witness Cache → Application DB → Your App 125 - 126 - User's PDS (for writes) 127 - ``` 128 - 129 - ### Open Questions for Later 130 - 131 - 1. **Retention policy?** 132 - - Keep all historical data forever? 133 - - Compress old data? 134 - - Archive after N days? 135 - 136 - 2. **Consistency guarantees?** 137 - - Eventually consistent OK? 138 - - Need stronger guarantees? 139 - 140 - 3. **Witness time vs repo commit time?** 141 - - What if we receive events out of order? 142 - - How to handle backfills? 143 - 144 - 4. **Compression strategy?** 145 - - Compress old records? 146 - - Trade-off: space vs replay speed 147 - 148 - --- 149 - 150 - **Reference:** Paul Frazee's advice on Atmosphere backend architecture 151 - **Status:** Future enhancement (Phase 6+) 152 - **Priority:** Not needed for personal tracker v1 1 + # Deleted - Remove this file
+1 -193
docs/implementation-progress.md
··· 1 - # Phase 0 Complete! ✅ 2 - 3 - ## Summary 4 - 5 - We've successfully completed Phase 0 (Research & Validation) and have begun Phase 1/2 implementation. The OAuth authentication system is now integrated and functional. 6 - 7 - ## What We Accomplished 8 - 9 - ### 1. Lexicon Validation ✅ 10 - - Created 5 lexicon files for all Arabica record types 11 - - Validated all lexicons using `goat lex parse` 12 - - Passed lint checks with `goat lex lint` 13 - - Fixed issue with temperature field (changed from `number` to `integer` with tenths precision) 14 - 15 - **Lexicon Files:** 16 - - `lexicons/com.arabica.bean.json` 17 - - `lexicons/com.arabica.roaster.json` 18 - - `lexicons/com.arabica.grinder.json` 19 - - `lexicons/com.arabica.brewer.json` 20 - - `lexicons/com.arabica.brew.json` 21 - 22 - ### 2. OAuth Integration ✅ 23 - - Integrated indigo's OAuth client library 24 - - Created `internal/atproto/oauth.go` with OAuth manager 25 - - Implemented authentication handlers in `internal/handlers/auth.go` 26 - - Updated `cmd/server/main.go` to initialize OAuth 27 - 28 - **OAuth Endpoints Implemented:** 29 - - `GET /login` - Login page 30 - - `POST /auth/login` - Initiate OAuth flow 31 - - `GET /oauth/callback` - Handle OAuth callback 32 - - `POST /logout` - Logout 33 - - `GET /client-metadata.json` - OAuth client metadata 34 - - `GET /.well-known/oauth-client-metadata` - Standard metadata endpoint 35 - 36 - **Features:** 37 - - DPOP-bound access tokens (secure) 38 - - PKCE for public clients (no client secret needed) 39 - - Session management with cookies 40 - - Auth middleware for all requests 41 - - Scopes: `["atproto", "transition:generic"]` 42 - 43 - ### 3. Project Structure 44 - 45 - ``` 46 - arabica-site/ 47 - ├── cmd/server/ 48 - │ └── main.go # UPDATED: OAuth initialization 49 - ├── internal/ 50 - │ ├── atproto/ # NEW: atproto package 51 - │ │ └── oauth.go # OAuth manager 52 - │ ├── handlers/ 53 - │ │ ├── auth.go # NEW: Auth handlers 54 - │ │ └── handlers.go # UPDATED: Added OAuth field 55 - │ ├── database/ 56 - │ ├── models/ 57 - │ └── templates/ 58 - ├── lexicons/ # NEW: Lexicon schemas 59 - │ ├── com.arabica.bean.json 60 - │ ├── com.arabica.roaster.json 61 - │ ├── com.arabica.grinder.json 62 - │ ├── com.arabica.brewer.json 63 - │ └── com.arabica.brew.json 64 - ├── docs/ 65 - │ ├── indigo-research.md 66 - │ ├── schema-design.md 67 - │ └── phase0-summary.md 68 - └── PLAN.md 69 - ``` 70 - 71 - ### 4. Build Status ✅ 72 - - **Build:** Success ✅ 73 - - **OAuth metadata endpoint:** Working ✅ 74 - - **Login page:** Rendering ✅ 75 - - **Ready for testing:** YES ✅ 76 - 77 - ## Testing 78 - 79 - The server successfully: 80 - 1. Builds without errors 81 - 2. Starts and listens on port 18910 82 - 3. Serves OAuth client metadata correctly 83 - 4. Displays login page 84 - 85 - **OAuth Metadata Response:** 86 - ```json 87 - { 88 - "client_id": "http://localhost:18910/client-metadata.json", 89 - "application_type": "web", 90 - "grant_types": ["authorization_code", "refresh_token"], 91 - "scope": "atproto transition:generic", 92 - "response_types": ["code"], 93 - "redirect_uris": ["http://localhost:18910/oauth/callback"], 94 - "token_endpoint_auth_method": "none", 95 - "dpop_bound_access_tokens": true 96 - } 97 - ``` 98 - 99 - ## Next Steps 100 - 101 - ### Phase 1: Core atproto Client (In Progress) 102 - Now that OAuth is working, we need to: 103 - 104 - 1. **Create atproto Client Wrapper** (`internal/atproto/client.go`) 105 - - Wrap indigo's XRPC client 106 - - Methods for record CRUD operations 107 - - Use authenticated sessions for API calls 108 - 109 - 2. **Implement Record Type Conversions** (`internal/atproto/records.go`) 110 - - Convert Go models → atproto records 111 - - Convert atproto records → Go models 112 - - Handle AT-URI references 113 - 114 - 3. **Implement Store Interface** (`internal/atproto/store.go`) 115 - - Replace SQLite implementation 116 - - Use PDS for all data operations 117 - - Handle reference resolution 118 - 119 - ### Testing OAuth Flow 120 - To test the complete OAuth flow, you'll need: 121 - 1. A Bluesky account (or local PDS) 122 - 2. Try logging in at `http://localhost:18910/login` 123 - 3. Enter your handle (e.g., `alice.bsky.social`) 124 - 4. Authorize the app on your PDS 125 - 5. Get redirected back with session cookies 126 - 127 - ## Configuration 128 - 129 - The app now supports these environment variables: 130 - 131 - ```bash 132 - # OAuth Configuration (optional, defaults to localhost) 133 - OAUTH_CLIENT_ID=http://localhost:18910/client-metadata.json 134 - OAUTH_REDIRECT_URI=http://localhost:18910/oauth/callback 135 - 136 - # Server Configuration 137 - PORT=18910 # Server port 138 - DB_PATH=./arabica.db # SQLite database path (still used for now) 139 - ``` 140 - 141 - ## Key Decisions Made 142 - 143 - 1. **Temperature Storage:** Changed from `number` to `integer` (tenths of degree) 144 - - Reason: Lexicons don't support floating point 145 - - Solution: Store 935 for 93.5°C 146 - 147 - 2. **Session Storage:** Using in-memory MemStore for development 148 - - Production will need Redis or database-backed storage 149 - - Easy to swap out later 150 - 151 - 3. **Public Client:** Using OAuth public client (no secret) 152 - - PKCE provides security 153 - - Simpler for self-hosted deployments 154 - - DPOP binds tokens to client 155 - 156 - 4. **Local Development:** Using localhost URLs for OAuth 157 - - Works for development without HTTPS 158 - - Will need real domain for production 159 - 160 - ## Files Created/Modified 161 - 162 - ### Created: 163 - - `internal/atproto/oauth.go` 164 - - `internal/handlers/auth.go` 165 - - `lexicons/*.json` (5 files) 166 - - `docs/indigo-research.md` 167 - - `docs/schema-design.md` 168 - - `docs/phase0-summary.md` 169 - 170 - ### Modified: 171 - - `cmd/server/main.go` - Added OAuth setup 172 - - `internal/handlers/handlers.go` - Added OAuth field 173 - - `go.mod` / `go.sum` - Added indigo dependency 174 - - `PLAN.md` - Detailed OAuth section 175 - 176 - ## Known Issues / TODOs 177 - 178 - 1. **TODO:** Replace in-memory session store with persistent storage (Redis/SQLite) 179 - 2. **TODO:** Set `Secure: true` on cookies in production (requires HTTPS) 180 - 3. **TODO:** Create proper login template (currently inline HTML) 181 - 4. **TODO:** Add error handling UI (currently raw HTTP errors) 182 - 5. **TODO:** Implement Phase 1 - atproto client for record operations 183 - 184 - ## Resources 185 - 186 - - **indigo SDK:** https://github.com/bluesky-social/indigo 187 - - **OAuth Demo:** `indigo/atproto/auth/oauth/cmd/oauth-web-demo/main.go` 188 - - **ATProto Specs:** https://atproto.com 189 - - **Lexicons:** See `lexicons/` directory 190 - 191 - --- 192 - 193 - **Status:** Phase 0 Complete ✅ | OAuth Integration Complete ✅ | Ready for Phase 1 🚀 1 + # Deleted - Remove this file
+21 -444
docs/indigo-research.md
··· 1 - # Indigo SDK Research 1 + # AT Protocol Integration 2 2 3 3 ## Overview 4 - The `indigo` SDK from Bluesky provides a comprehensive Go implementation for atproto, including: 5 - - Complete OAuth client with DPOP support 6 - - XRPC client for API operations 7 - - Repository operations (create/read/update/delete records) 8 - - DID resolution and handle management 9 - - Lexicon support 10 4 11 - **Package:** `github.com/bluesky-social/indigo` 12 - **Version:** v0.0.0-20260103083015-78a1c1894f36 (pseudo-version, tracks main branch) 5 + Arabica uses the Bluesky indigo SDK for AT Protocol integration. 13 6 14 - --- 7 + **Package:** `github.com/bluesky-social/indigo` 15 8 16 - ## Key Packages 9 + ## Key Components 17 10 18 - ### 1. `atproto/auth/oauth` - OAuth Client Implementation 19 - **Location:** `github.com/bluesky-social/indigo/atproto/auth/oauth` 11 + ### OAuth Authentication 20 12 21 - **Features:** 22 - - Complete OAuth 2.0 + DPOP implementation 23 - - PKCE support (Proof Key for Code Exchange) 24 - - Public and confidential client support 25 - - Token refresh handling 26 - - Session management interfaces 27 - - PAR (Pushed Authorization Request) support 13 + - Public OAuth client with PKCE 14 + - DPOP-bound access tokens 15 + - Scopes: `atproto`, `transition:generic` 16 + - Session persistence via BoltDB 28 17 29 - **Main Components:** 18 + ### Record Operations 30 19 31 - #### `ClientApp` - High-Level OAuth Client 32 - ```go 33 - type ClientApp struct { 34 - Config ClientConfig 35 - Store ClientAuthStore 36 - } 20 + Standard AT Protocol record CRUD operations: 21 + - `com.atproto.repo.createRecord` 22 + - `com.atproto.repo.getRecord` 23 + - `com.atproto.repo.listRecords` 24 + - `com.atproto.repo.putRecord` 25 + - `com.atproto.repo.deleteRecord` 37 26 38 - func NewClientApp(config *ClientConfig, store ClientAuthStore) *ClientApp 39 - ``` 40 - - Manages OAuth flow for multiple users 41 - - Handles session persistence 42 - - Automatic token refresh 43 - - Thread-safe for concurrent use 27 + ### Client Implementation 44 28 45 - #### `ClientConfig` - OAuth Configuration 46 - ```go 47 - type ClientConfig struct { 48 - ClientID string // URL to client metadata (e.g., "https://arabica.com/client-metadata.json") 49 - RedirectURI string // Callback URL 50 - Scopes []string // e.g., ["atproto", "transition:generic"] 51 - // ... other fields 52 - } 29 + See `internal/atproto/client.go` for the XRPC client wrapper. 53 30 54 - func NewPublicConfig(clientID, redirectURI string, scopes []string) ClientConfig 55 - ``` 56 - 57 - #### `ClientAuthStore` - Session Persistence Interface 58 - ```go 59 - type ClientAuthStore interface { 60 - GetSession(ctx context.Context, sessionID string) (*ClientAuth, error) 61 - PutSession(ctx context.Context, session *ClientAuth) error 62 - DeleteSession(ctx context.Context, sessionID string) error 63 - } 64 - ``` 65 - 66 - **Built-in Implementations:** 67 - - `MemStore` - In-memory storage (ephemeral, for development) 68 - - Custom implementations needed for production (database-backed) 69 - 70 - #### `ClientAuth` - Session State 71 - ```go 72 - type ClientAuth struct { 73 - SessionID string 74 - DID string 75 - AccessToken string 76 - RefreshToken string 77 - TokenType string 78 - ExpiresAt time.Time 79 - Scope string 80 - // DPOP key material 81 - } 82 - ``` 31 + ## References 83 32 84 - **Example OAuth Flow (from doc.go):** 85 - ```go 86 - // 1. Create client app (once at startup) 87 - config := oauth.NewPublicConfig( 88 - "https://arabica.com/client-metadata.json", 89 - "https://arabica.com/oauth/callback", 90 - []string{"atproto", "transition:generic"}, 91 - ) 92 - oauthApp := oauth.NewClientApp(&config, oauth.NewMemStore()) 93 - 94 - // 2. Initiate login 95 - state, authURL, err := oauthApp.Authorize(ctx, handle, stateParam) 96 - // Redirect user to authURL 97 - 98 - // 3. Handle callback 99 - auth, err := oauthApp.HandleCallback(ctx, state, code) 100 - // auth.SessionID, auth.DID now available 101 - 102 - // 4. Make authenticated requests 103 - token, err := oauthApp.TokenForSession(ctx, auth.SessionID) 104 - ``` 105 - 106 - **OAuth Demo App:** 107 - - Example implementation: `atproto/auth/oauth/cmd/oauth-web-demo/main.go` 108 - - Shows complete flow with web UI 109 - - Good reference for our implementation 110 - 111 - --- 112 - 113 - ### 2. `atproto/atclient` - Repository Operations 114 - **Location:** `github.com/bluesky-social/indigo/atproto/atclient` 115 - 116 - Provides high-level client for atproto operations: 117 - - Record CRUD operations 118 - - Repository listing 119 - - Blob uploads 120 - - Identity resolution 121 - 122 - **Key Types:** 123 - 124 - #### `Client` - XRPC Client 125 - ```go 126 - type Client struct { 127 - // HTTP client with auth 128 - } 129 - 130 - // Record operations 131 - func (c *Client) CreateRecord(ctx context.Context, input *CreateRecordInput) (*CreateRecordOutput, error) 132 - func (c *Client) GetRecord(ctx context.Context, input *GetRecordInput) (*GetRecordOutput, error) 133 - func (c *Client) ListRecords(ctx context.Context, input *ListRecordsInput) (*ListRecordsOutput, error) 134 - func (c *Client) PutRecord(ctx context.Context, input *PutRecordInput) (*PutRecordOutput, error) 135 - func (c *Client) DeleteRecord(ctx context.Context, input *DeleteRecordInput) error 136 - ``` 137 - 138 - **Record Input/Output Types:** 139 - ```go 140 - type CreateRecordInput struct { 141 - Repo string // DID 142 - Collection string // e.g., "com.arabica.brew" 143 - Record map[string]interface{} // Record data 144 - RKey *string // Optional, uses TID if not provided 145 - } 146 - 147 - type CreateRecordOutput struct { 148 - URI string // AT-URI of created record 149 - CID string // Content ID 150 - } 151 - 152 - type GetRecordInput struct { 153 - Repo string // DID 154 - Collection string // e.g., "com.arabica.brew" 155 - RKey string // Record key 156 - } 157 - 158 - type GetRecordOutput struct { 159 - URI string 160 - CID string 161 - Value map[string]interface{} // Record data 162 - } 163 - 164 - type ListRecordsInput struct { 165 - Repo string // DID 166 - Collection string // e.g., "com.arabica.brew" 167 - Limit *int64 // Optional 168 - Cursor *string // For pagination 169 - } 170 - 171 - type ListRecordsOutput struct { 172 - Records []Record 173 - Cursor *string 174 - } 175 - ``` 176 - 177 - --- 178 - 179 - ### 3. `atproto/syntax` - AT-URI Parsing 180 - **Location:** `github.com/bluesky-social/indigo/atproto/syntax` 181 - 182 - Parse and construct AT-URIs: 183 - ```go 184 - // Parse AT-URI: at://did:plc:abc123/com.arabica.brew/3jxy... 185 - uri, err := syntax.ParseATURI("at://...") 186 - fmt.Println(uri.Authority()) // DID 187 - fmt.Println(uri.Collection()) // Collection name 188 - fmt.Println(uri.RecordKey()) // RKey 189 - 190 - // Construct AT-URI 191 - uri := syntax.ATURI{ 192 - Authority: did, 193 - Collection: "com.arabica.brew", 194 - RecordKey: rkey, 195 - } 196 - ``` 197 - 198 - --- 199 - 200 - ### 4. `atproto/identity` - DID and Handle Resolution 201 - **Location:** `github.com/bluesky-social/indigo/atproto/identity` 202 - 203 - Resolve handles to DIDs and discover PDS endpoints: 204 - ```go 205 - // Resolve handle to DID 206 - did, err := identity.ResolveHandle(ctx, "alice.bsky.social") 207 - 208 - // Resolve DID to PDS URL 209 - pdsURL, err := identity.ResolveDIDToPDS(ctx, did) 210 - 211 - // Resolve DID document 212 - didDoc, err := identity.ResolveDID(ctx, did) 213 - ``` 214 - 215 - --- 216 - 217 - ## Implementation Plan for Arabica 218 - 219 - ### Phase 1: OAuth Integration 220 - 221 - **Files to Create:** 222 - - `internal/atproto/oauth.go` - Wrapper around indigo's OAuth 223 - - `internal/atproto/session.go` - Session storage implementation 224 - - `internal/atproto/middleware.go` - Auth middleware 225 - 226 - **Key Decisions:** 227 - 228 - 1. **Session Storage:** 229 - - Development: Use `oauth.MemStore` (built-in) 230 - - Production: Implement `ClientAuthStore` with SQLite or Redis 231 - 232 - 2. **Client Metadata:** 233 - - Serve at `/.well-known/oauth-client-metadata` or at client_id URL 234 - - Generate from `config.ClientMetadata()` 235 - - Must be HTTPS in production 236 - 237 - 3. **Scopes:** 238 - - Start with: `["atproto", "transition:generic"]` 239 - - `atproto` scope gives full repo access 240 - - `transition:generic` allows legacy operations 241 - 242 - 4. **Client Type:** 243 - - Use **public client** (no client secret) 244 - - Simpler for self-hosted deployments 245 - - PKCE provides security 246 - 247 - **Implementation Steps:** 248 - 249 - 1. Create OAuth wrapper: 250 - ```go 251 - // internal/atproto/oauth.go 252 - type OAuthManager struct { 253 - app *oauth.ClientApp 254 - } 255 - 256 - func NewOAuthManager(clientID, redirectURI string) (*OAuthManager, error) { 257 - config := oauth.NewPublicConfig( 258 - clientID, 259 - redirectURI, 260 - []string{"atproto", "transition:generic"}, 261 - ) 262 - 263 - // Use MemStore for development 264 - store := oauth.NewMemStore() 265 - 266 - app := oauth.NewClientApp(&config, store) 267 - return &OAuthManager{app: app}, nil 268 - } 269 - 270 - func (m *OAuthManager) InitiateLogin(ctx context.Context, handle string) (string, error) { 271 - // Generate state, get auth URL, redirect 272 - } 273 - 274 - func (m *OAuthManager) HandleCallback(ctx context.Context, code, state string) (*oauth.ClientAuth, error) { 275 - // Exchange code for tokens 276 - } 277 - 278 - func (m *OAuthManager) GetSession(ctx context.Context, sessionID string) (*oauth.ClientAuth, error) { 279 - // Retrieve session 280 - } 281 - ``` 282 - 283 - 2. Add HTTP handlers: 284 - ```go 285 - // internal/handlers/auth.go 286 - func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) { 287 - handle := r.FormValue("handle") 288 - authURL, err := h.oauth.InitiateLogin(r.Context(), handle) 289 - http.Redirect(w, r, authURL, http.StatusFound) 290 - } 291 - 292 - func (h *Handler) HandleOAuthCallback(w http.ResponseWriter, r *http.Request) { 293 - code := r.URL.Query().Get("code") 294 - state := r.URL.Query().Get("state") 295 - 296 - auth, err := h.oauth.HandleCallback(r.Context(), code, state) 297 - 298 - // Set session cookie 299 - http.SetCookie(w, &http.Cookie{ 300 - Name: "session_id", 301 - Value: auth.SessionID, 302 - HttpOnly: true, 303 - Secure: true, // HTTPS only in production 304 - SameSite: http.SameSiteLaxMode, 305 - }) 306 - 307 - http.Redirect(w, r, "/", http.StatusFound) 308 - } 309 - ``` 310 - 311 - 3. Create auth middleware: 312 - ```go 313 - // internal/atproto/middleware.go 314 - func (m *OAuthManager) AuthMiddleware(next http.Handler) http.Handler { 315 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 316 - cookie, err := r.Cookie("session_id") 317 - if err != nil { 318 - // No session, continue without auth 319 - next.ServeHTTP(w, r) 320 - return 321 - } 322 - 323 - auth, err := m.GetSession(r.Context(), cookie.Value) 324 - if err != nil { 325 - // Invalid session 326 - http.Redirect(w, r, "/login", http.StatusFound) 327 - return 328 - } 329 - 330 - // Add DID to context 331 - ctx := context.WithValue(r.Context(), "userDID", auth.DID) 332 - next.ServeHTTP(w, r.WithContext(ctx)) 333 - }) 334 - } 335 - ``` 336 - 337 - --- 338 - 339 - ### Phase 2: Repository Operations 340 - 341 - **Files to Create:** 342 - - `internal/atproto/client.go` - XRPC client wrapper 343 - - `internal/atproto/records.go` - Record type conversions 344 - - `internal/atproto/store.go` - Store interface implementation 345 - 346 - **Implementation:** 347 - 348 - 1. Create authenticated XRPC client: 349 - ```go 350 - // internal/atproto/client.go 351 - type Client struct { 352 - oauth *OAuthManager 353 - xrpc *xrpc.Client 354 - } 355 - 356 - func (c *Client) GetAuthenticatedClient(ctx context.Context, sessionID string) (*xrpc.Client, error) { 357 - auth, err := c.oauth.GetSession(ctx, sessionID) 358 - if err != nil { 359 - return nil, err 360 - } 361 - 362 - // Create XRPC client with auth 363 - client := &xrpc.Client{ 364 - Host: auth.Host, // PDS URL 365 - Auth: &xrpc.AuthInfo{ 366 - AccessJwt: auth.AccessToken, 367 - RefreshJwt: auth.RefreshToken, 368 - Did: auth.DID, 369 - Handle: auth.Handle, 370 - }, 371 - } 372 - 373 - return client, nil 374 - } 375 - ``` 376 - 377 - 2. Record CRUD operations: 378 - ```go 379 - // internal/atproto/store.go 380 - type AtprotoStore struct { 381 - client *Client 382 - did string 383 - } 384 - 385 - func (s *AtprotoStore) CreateBrew(brew *models.CreateBrewRequest, userID int) (*models.Brew, error) { 386 - // Convert brew to record 387 - record := brewToRecord(brew) 388 - 389 - // Create record in PDS 390 - output, err := s.client.CreateRecord(ctx, &atclient.CreateRecordInput{ 391 - Repo: s.did, 392 - Collection: "com.arabica.brew", 393 - Record: record, 394 - }) 395 - 396 - // Convert back to Brew model 397 - return recordToBrew(output) 398 - } 399 - ``` 400 - 401 - --- 402 - 403 - ## Questions Answered 404 - 405 - ### ✅ Does indigo provide complete OAuth client with DPOP support? 406 - **YES** - Full implementation in `atproto/auth/oauth` package with: 407 - - DPOP JWT signing and nonces 408 - - Automatic token refresh with DPOP 409 - - Public and confidential client support 410 - - PKCE built-in 411 - 412 - ### ✅ What's the API for repo operations? 413 - **Answer:** `atproto/atclient` provides: 414 - - `CreateRecord()`, `GetRecord()`, `ListRecords()`, `PutRecord()`, `DeleteRecord()` 415 - - Input/output structs with proper typing 416 - - Handles AT-URIs and CIDs automatically 417 - 418 - ### ✅ Are there session management helpers? 419 - **YES** - `ClientAuthStore` interface with `MemStore` implementation: 420 - - Define interface for session persistence 421 - - Built-in in-memory store for development 422 - - Can implement custom stores (DB, Redis, etc.) 423 - 424 - ### ✅ How are AT-URIs parsed and resolved? 425 - **Answer:** `atproto/syntax` package: 426 - - `ParseATURI()` and `ATURI` type 427 - - Extract DID, collection, rkey components 428 - - Construct new AT-URIs 429 - 430 - --- 431 - 432 - ## Next Steps 433 - 434 - 1. ✅ **Understand indigo OAuth** - COMPLETE 435 - 2. **Create lexicon files** - Define schemas 436 - 3. **Build OAuth wrapper** - Use indigo's ClientApp 437 - 4. **Implement Store interface** - Use atclient for PDS operations 438 - 5. **Update handlers** - Add auth context 439 - 440 - --- 441 - 442 - ## Useful Resources 443 - 444 - - **Indigo GitHub:** https://github.com/bluesky-social/indigo 445 - - **OAuth Demo:** `atproto/auth/oauth/cmd/oauth-web-demo/main.go` 446 - - **API Docs:** GoDoc for each package 447 - - **atproto Specs:** https://atproto.com/specs/oauth 448 - 449 - --- 450 - 451 - ## Notes 452 - 453 - - Indigo is actively developed, uses pseudo-versions (no tags) 454 - - OAuth implementation is production-ready and used by Bluesky 455 - - Good test coverage and examples 456 - - Community support via Bluesky Discord 457 - - Well-architected with clear interfaces 33 + - indigo SDK: https://github.com/bluesky-social/indigo 34 + - AT Protocol docs: https://atproto.com
+33
docs/nix-install.md
··· 1 + # NixOS Installation 2 + 3 + ## Using the Module 4 + 5 + Add to your configuration.nix: 6 + 7 + ```nix 8 + { 9 + imports = [ ./arabica-site/module.nix ]; 10 + 11 + services.arabica = { 12 + enable = true; 13 + port = 18910; 14 + dataDir = "/var/lib/arabica"; 15 + logLevel = "info"; 16 + secureCookies = false; # Set true if behind HTTPS proxy 17 + }; 18 + } 19 + ``` 20 + 21 + ## Manual Installation 22 + 23 + Build and run directly: 24 + 25 + ```bash 26 + # Build 27 + nix-build -E 'with import <nixpkgs> {}; callPackage ./default.nix {}' 28 + 29 + # Run 30 + result/bin/arabica 31 + ``` 32 + 33 + The data directory will be created at `~/.local/share/arabica/` by default.
+1 -78
docs/oauth-token-fix.md
··· 1 - # OAuth Token Fix - DPOP Authentication 2 - 3 - ## The Problem 4 - 5 - Getting error: `XRPC ERROR 400: InvalidToken: Malformed token` 6 - 7 - ## Root Cause 8 - 9 - We were manually creating an XRPC client and setting the auth like this: 10 - 11 - ```go 12 - client.Auth = &xrpc.AuthInfo{ 13 - AccessJwt: sessData.AccessToken, 14 - RefreshJwt: sessData.RefreshToken, 15 - // ... 16 - } 17 - ``` 18 - 19 - **This doesn't work** because indigo's OAuth uses **DPOP (Demonstrating Proof of Possession)** tokens, which require: 20 - 1. Special cryptographic signing of each request 21 - 2. Token nonces that rotate 22 - 3. Proof-of-possession headers 23 - 24 - You can't just pass the access token directly - it needs DPOP signatures! 25 - 26 - ## The Solution 27 - 28 - Use indigo's built-in `ClientSession` which handles DPOP automatically: 29 - 30 - ```go 31 - // Resume the OAuth session 32 - session, err := c.oauth.app.ResumeSession(ctx, did, sessionID) 33 - 34 - // Get the authenticated API client 35 - apiClient := session.APIClient() 36 - 37 - // Now make requests - DPOP is handled automatically 38 - apiClient.Post(ctx, "com.atproto.repo.createRecord", body, &result) 39 - ``` 40 - 41 - ## What Changed 42 - 43 - **File:** `internal/atproto/client.go` 44 - 45 - **Before:** 46 - - Manually created `xrpc.Client` 47 - - Set `client.Auth` with raw tokens ❌ 48 - - Called `client.Do()` directly 49 - 50 - **After:** 51 - - Call `app.ResumeSession()` to get `ClientSession` 52 - - Call `session.APIClient()` to get authenticated client ✅ 53 - - Use `apiClient.Post()` and `apiClient.Get()` methods 54 - - DPOP signing happens automatically 55 - 56 - ## How DPOP Works (Behind the Scenes) 57 - 58 - 1. Each request gets a unique DPOP proof JWT 59 - 2. The proof is signed with a private key stored in the session 60 - 3. The proof includes: 61 - - HTTP method 62 - - Request URL 63 - - Current timestamp 64 - - Nonce from server 65 - 4. PDS validates the proof matches the access token 66 - 67 - **Why this matters:** Even if someone intercepts your access token, they can't use it without the private key to sign DPOP proofs. 68 - 69 - ## Testing 70 - 71 - Now you should be able to create beans/brews and see them in your PDS! 72 - 73 - Try creating a bean from `/manage` - it should work now. 74 - 75 - To verify it's actually in your PDS: 76 - ```bash 77 - curl "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=yourhandle.bsky.social&collection=com.arabica.bean" 78 - ``` 1 + # Deleted - Remove this file
+1 -205
docs/phase0-summary.md
··· 1 - # Phase 0 Summary - Research & Validation 2 - 3 - **Status:** ✅ COMPLETE 4 - **Date:** January 4, 2024 5 - 6 - --- 7 - 8 - ## Completed Tasks 9 - 10 - ### ✅ Task 0.1: Research `indigo` SDK 11 - 12 - **Findings:** 13 - - Indigo provides **complete OAuth client** with DPOP support 14 - - Package: `github.com/bluesky-social/indigo/atproto/auth/oauth` 15 - - High-level `ClientApp` API handles entire OAuth flow 16 - - Built-in `MemStore` for session storage (dev) 17 - - Excellent reference implementation in `oauth-web-demo` 18 - 19 - **Key Components Identified:** 20 - - `ClientApp` - Main OAuth client (handles login/callback/refresh) 21 - - `ClientConfig` - Configuration (clientID, redirectURI, scopes) 22 - - `ClientAuthStore` - Session persistence interface 23 - - `ClientAuth` - Session state (DID, tokens, DPOP keys) 24 - 25 - **Documentation:** See `docs/indigo-research.md` for full details 26 - 27 - ### ✅ Task 0.2: Design & Validate Lexicons 28 - 29 - **Created 5 lexicon files:** 30 - 1. `lexicons/com.arabica.bean.json` - Coffee beans 31 - 2. `lexicons/com.arabica.roaster.json` - Roasters 32 - 3. `lexicons/com.arabica.grinder.json` - Grinders 33 - 4. `lexicons/com.arabica.brewer.json` - Brewing devices 34 - 5. `lexicons/com.arabica.brew.json` - Brewing sessions 35 - 36 - **Key Design Decisions:** 37 - - **References:** AT-URIs for all relationships (e.g., `beanRef`, `roasterRef`) 38 - - **Pours:** Embedded in brew records (not separate collection) 39 - - **Enums:** Limited use (grinder types) - mostly free text 40 - - **Required Fields:** Minimal (name + createdAt for most) 41 - - **User ID:** Removed (implicit from DID/repo) 42 - 43 - **Schema Documentation:** See `docs/schema-design.md` for full schema 44 - 45 - ### ✅ Task 0.3: Set up Development Environment 46 - 47 - **Dependencies Added:** 48 - - `github.com/bluesky-social/indigo` v0.0.0-20260103083015-78a1c1894f36 49 - 50 - **Project Structure:** 51 - ``` 52 - arabica-site/ 53 - ├── docs/ 54 - │ ├── indigo-research.md 55 - │ └── schema-design.md 56 - ├── lexicons/ 57 - │ ├── com.arabica.bean.json 58 - │ ├── com.arabica.roaster.json 59 - │ ├── com.arabica.grinder.json 60 - │ ├── com.arabica.brewer.json 61 - │ └── com.arabica.brew.json 62 - └── PLAN.md 63 - ``` 64 - 65 - **Next Step:** Set up test PDS access (Bluesky account or local PDS) 66 - 67 - ### ⏸️ Task 0.4: Manual Record Creation Test 68 - 69 - **Status:** Pending 70 - **Blocker:** Need test environment (PDS access) 71 - 72 - **Options:** 73 - 1. **Use Bluesky account** - Easiest, can test immediately 74 - 2. **Run local PDS** - More control, requires Docker setup 75 - 3. **Use atproto sandbox** - If available 76 - 77 - **Recommendation:** Use Bluesky account for initial testing 78 - 79 - --- 80 - 81 - ## Key Insights 82 - 83 - ### OAuth Implementation 84 - - **Use indigo's OAuth** - Production-ready, handles DPOP automatically 85 - - **Public client** - No client secret needed (PKCE provides security) 86 - - **Scopes:** `["atproto", "transition:generic"]` 87 - - **Client metadata:** Must be served via HTTPS at client_id URL 88 - - **Session storage:** Start with MemStore, add persistent store later 89 - 90 - ### Schema Design 91 - - **Decentralized by design** - Each user has their own beans/roasters 92 - - **Duplicates OK** - Users don't share entities (matches PDS model) 93 - - **Simple references** - AT-URIs keep it standard 94 - - **Embedded pours** - Reduces complexity and queries 95 - 96 - ### Reference Resolution 97 - - Need to fetch referenced records (bean, grinder, etc.) when displaying brews 98 - - Start with lazy loading (simple) 99 - - Optimize with batch fetching later if needed 100 - 101 - --- 102 - 103 - ## Next Steps (Phase 1) 104 - 105 - ### Immediate Actions 106 - 1. **Set up test PDS access** 107 - - Create Bluesky account (or use existing) 108 - - Get DID and access credentials 109 - - Test manual record creation 110 - 111 - 2. **Validate lexicons** 112 - - Use atproto CLI or tools to validate JSON schemas 113 - - Create test records manually 114 - - Verify they appear in repo 115 - 116 - 3. **Study oauth-web-demo** 117 - - Review example OAuth implementation 118 - - Understand flow and integration points 119 - - Identify patterns to follow 120 - 121 - ### Phase 1 Preparation 122 - Once testing environment is ready: 123 - - Begin implementing atproto client wrapper 124 - - Create OAuth handler using indigo 125 - - Implement Store interface via PDS operations 126 - 127 - --- 128 - 129 - ## Questions for User 130 - 131 - 1. **PDS Testing:** 132 - - Do you have a Bluesky account we can use for testing? 133 - - Or should we set up a local PDS? 134 - 135 - 2. **Domain Setup:** 136 - - What domain will you use for the app? 137 - - (Needed for OAuth client_id and redirect_uri) 138 - 139 - 3. **Development Timeline:** 140 - - Ready to proceed to Phase 1 (implementation)? 141 - - Or need more time for research/planning? 142 - 143 - --- 144 - 145 - ## Resources Created 146 - 147 - - ✅ `docs/indigo-research.md` - Complete indigo SDK documentation 148 - - ✅ `docs/schema-design.md` - Lexicon schema specifications 149 - - ✅ `lexicons/*.json` - 5 lexicon files ready for validation 150 - - ✅ `PLAN.md` - Updated with detailed Phase 2 OAuth info 151 - 152 - --- 153 - 154 - ## Confidence Level 155 - 156 - **Phase 0 Assessment:** High confidence ✅ 157 - 158 - - Indigo SDK is well-suited for our needs 159 - - OAuth implementation is straightforward 160 - - Lexicon schemas are complete and validated conceptually 161 - - Clear path forward to implementation 162 - 163 - **Risks Identified:** 164 - - None major for Phase 1 165 - - Session storage will need production solution eventually 166 - - Reference resolution performance TBD (likely fine) 167 - 168 - **Ready to Proceed:** YES - Phase 1 can begin once test environment is set up 169 - 170 - --- 171 - 172 - ## Time Spent 173 - 174 - **Phase 0:** ~2 hours 175 - - Research: 1 hour 176 - - Schema design: 0.5 hours 177 - - Documentation: 0.5 hours 178 - 179 - **Estimate vs Actual:** On track (planned 2-3 days, much faster due to good SDK docs) 180 - 181 - --- 182 - 183 - ## Updated Phase 0 Checklist 184 - 185 - - [x] Review `indigo` documentation 186 - - [x] Study OAuth implementation in indigo 187 - - [x] Understand XRPC client usage 188 - - [x] Review record CRUD operations API 189 - - [x] Find example applications (oauth-web-demo) 190 - - [x] Document findings in `docs/indigo-research.md` 191 - - [x] Write lexicon JSON files (all 5) 192 - - [ ] Validate lexicons against atproto schema validator 193 - - [x] Document schema decisions in `docs/schema-design.md` 194 - - [x] Review field mappings from SQLite schema 195 - - [ ] Create/use Bluesky account for testing 196 - - [ ] Install atproto development tools 197 - - [ ] Set up test DID 198 - - [ ] Configure environment variables 199 - - [ ] Manually create test records 200 - - [ ] Test all record types 201 - - [ ] Test cross-references 202 - - [ ] Verify records in repo explorer 203 - 204 - **Progress:** 12/19 tasks complete (63%) 205 - **Remaining:** Test environment setup + manual validation 1 + # Deleted - Remove this file
+30 -367
docs/schema-design.md
··· 1 - # Arabica Lexicon Schema Design 2 - 3 - ## Overview 4 - 5 - This document describes the lexicon schemas for Arabica coffee tracking records in the AT Protocol ecosystem. 6 - 7 - **Namespace:** `com.arabica.*` 8 - **Records:** Public by default (visible via repo exploration) 9 - **Key Format:** TID (Timestamp Identifier) - automatic 10 - 11 - --- 12 - 13 - ## Schema Decisions 14 - 15 - ### Reference Strategy 16 - **Decision:** Use AT-URIs for all references between records 17 - 18 - **Rationale:** 19 - - Maintains decentralization (references work across any PDS) 20 - - Standard atproto pattern 21 - - Allows users to reference their own records 22 - - Each user maintains their own copy of beans/roasters (duplicates OK) 23 - 24 - **Example Reference:** 25 - ``` 26 - beanRef: "at://did:plc:abc123xyz/com.arabica.bean/3jxyabcd123" 27 - ``` 1 + # Lexicon Schemas 28 2 29 - ### Pours Handling 30 - **Decision:** Embed pours as an array within brew records 3 + ## Record Types 31 4 32 - **Rationale:** 33 - - Pours are tightly coupled to brews (no independent existence) 34 - - Simpler than separate collection 35 - - Reduces number of PDS queries 36 - - Matches original SQLite schema pattern 5 + Arabica defines 5 lexicon schemas: 37 6 38 - **Alternative Considered:** Separate `com.arabica.pour` collection 39 - - Rejected due to added complexity and query overhead 40 - 41 - ### Enum vs Free Text 42 - **Decision:** Mix of both approaches 7 + ### social.arabica.alpha.bean 8 + Coffee bean records with origin, roast level, process, and roaster reference. 43 9 44 - **Enums Used:** 45 - - `grinder.grinderType`: `["hand", "electric", "electric_hand"]` 46 - - `grinder.burrType`: `["conical", "flat", ""]` (empty string for unknown) 10 + ### social.arabica.alpha.roaster 11 + Coffee roaster records with name, location, and website. 47 12 48 - **Free Text Used:** 49 - - `bean.roastLevel`: User may have custom roast descriptions 50 - - `bean.process`: Processing methods vary 51 - - `brew.method`: Brewing methods are diverse 52 - - `brew.grindSize`: Can be numeric (grinder setting) or descriptive 13 + ### social.arabica.alpha.grinder 14 + Grinder records with type (hand/electric), burr type (conical/flat), and notes. 53 15 54 - **Rationale:** 55 - - Enums for truly limited, well-defined sets 56 - - Free text for user creativity and flexibility 57 - - Matches current app behavior 16 + ### social.arabica.alpha.brewer 17 + Brewing device records with name and description. 58 18 59 - ### Field Mappings from SQLite 19 + ### social.arabica.alpha.brew 20 + Brew session records including: 21 + - Bean reference (AT-URI) 22 + - Brewing parameters (temperature, time, water, coffee amounts) 23 + - Grinder and brewer references (optional) 24 + - Grind size, method, tasting notes, rating 25 + - Pours array (embedded, not separate records) 60 26 61 - #### Removed Fields 62 - - `user_id` - Implicit (records exist in user's DID repo) 63 - - `id` - Replaced by TID (rkey) 27 + ## Design Decisions 64 28 65 - #### Reference Changes 66 - ``` 67 - SQLite → Lexicon 68 - ----------------------------------------- 69 - bean.roaster_id (int) → bean.roasterRef (AT-URI) 70 - brew.bean_id (int) → brew.beanRef (AT-URI) 71 - brew.grinder_id (int) → brew.grinderRef (AT-URI) 72 - brew.brewer_id (int) → brew.brewerRef (AT-URI) 73 - ``` 29 + ### References 30 + All references use AT-URIs pointing to user's own records. 31 + Example: `at://did:plc:abc123/social.arabica.alpha.bean/3jxy123` 74 32 75 - #### Pours Changes 76 - ``` 77 - SQLite → Lexicon 78 - ------------------------------------------- 79 - pours table (separate) → brew.pours[] (embedded) 80 - pour.brew_id (int) → (removed, part of brew) 81 - pour.pour_number (int) → (removed, array index) 82 - ``` 33 + ### Temperature Storage 34 + Stored as integer in tenths of degrees Celsius. 35 + Example: 935 represents 93.5°C 83 36 84 - --- 85 - 86 - ## Schema Definitions 87 - 88 - ### 1. `com.arabica.bean` 89 - Coffee bean variety tracked by the user. 90 - 91 - **Required Fields:** 92 - - `name` (string, max 200 chars) 93 - - `createdAt` (datetime) 94 - 95 - **Optional Fields:** 96 - - `origin` (string, max 200 chars) - Geographic origin 97 - - `roastLevel` (string, max 100 chars) - Roast level description 98 - - `process` (string, max 100 chars) - Processing method 99 - - `description` (string, max 1000 chars) - Additional notes 100 - - `roasterRef` (at-uri) - Reference to roaster record 101 - 102 - **Example:** 103 - ```json 104 - { 105 - "name": "Ethiopian Yirgacheffe", 106 - "origin": "Ethiopia", 107 - "roastLevel": "Light", 108 - "process": "Washed", 109 - "description": "Floral and citrus notes", 110 - "roasterRef": "at://did:plc:abc/com.arabica.roaster/3jxy...", 111 - "createdAt": "2024-01-04T12:00:00Z" 112 - } 113 - ``` 114 - 115 - --- 116 - 117 - ### 2. `com.arabica.roaster` 118 - Coffee roaster company. 119 - 120 - **Required Fields:** 121 - - `name` (string, max 200 chars) 122 - - `createdAt` (datetime) 123 - 124 - **Optional Fields:** 125 - - `location` (string, max 200 chars) - Location description 126 - - `website` (uri, max 500 chars) - Roaster website URL 127 - 128 - **Example:** 129 - ```json 130 - { 131 - "name": "Blue Bottle Coffee", 132 - "location": "Oakland, CA", 133 - "website": "https://bluebottlecoffee.com", 134 - "createdAt": "2024-01-04T12:00:00Z" 135 - } 136 - ``` 137 - 138 - --- 139 - 140 - ### 3. `com.arabica.grinder` 141 - Coffee grinder equipment. 142 - 143 - **Required Fields:** 144 - - `name` (string, max 200 chars) 145 - - `createdAt` (datetime) 146 - 147 - **Optional Fields:** 148 - - `grinderType` (enum: `["hand", "electric", "electric_hand"]`) 149 - - `burrType` (enum: `["conical", "flat", ""]`) - Empty string for unknown 150 - - `notes` (string, max 1000 chars) - Additional notes 151 - 152 - **Example:** 153 - ```json 154 - { 155 - "name": "Baratza Encore", 156 - "grinderType": "electric", 157 - "burrType": "conical", 158 - "notes": "Great entry-level grinder", 159 - "createdAt": "2024-01-04T12:00:00Z" 160 - } 161 - ``` 162 - 163 - --- 164 - 165 - ### 4. `com.arabica.brewer` 166 - Coffee brewing device or method. 167 - 168 - **Required Fields:** 169 - - `name` (string, max 200 chars) 170 - - `createdAt` (datetime) 171 - 172 - **Optional Fields:** 173 - - `description` (string, max 1000 chars) - Description or notes 174 - 175 - **Example:** 176 - ```json 177 - { 178 - "name": "Hario V60", 179 - "description": "Size 02, ceramic", 180 - "createdAt": "2024-01-04T12:00:00Z" 181 - } 182 - ``` 183 - 184 - --- 185 - 186 - ### 5. `com.arabica.brew` 187 - Coffee brewing session with parameters. 188 - 189 - **Required Fields:** 190 - - `beanRef` (at-uri) - Reference to bean used 191 - - `createdAt` (datetime) 192 - 193 - **Optional Fields:** 194 - - `method` (string, max 100 chars) - Brewing method 195 - - `temperature` (number, 0-100) - Water temperature in Celsius 196 - - `waterAmount` (integer, ≥0) - Water amount in grams/ml 197 - - `timeSeconds` (integer, ≥0) - Total brew time 198 - - `grindSize` (string, max 50 chars) - Grind setting 199 - - `grinderRef` (at-uri) - Reference to grinder 200 - - `brewerRef` (at-uri) - Reference to brewer 201 - - `tastingNotes` (string, max 2000 chars) - Tasting notes 202 - - `rating` (integer, 1-10) - Rating 203 - - `pours` (array of pour objects) - Pour schedule 204 - 205 - **Pour Object:** 206 - - `waterAmount` (integer, required) - Water in this pour 207 - - `timeSeconds` (integer, required) - Time of pour 208 - 209 - **Example:** 210 - ```json 211 - { 212 - "beanRef": "at://did:plc:abc/com.arabica.bean/3jxy...", 213 - "method": "Pour Over", 214 - "temperature": 93, 215 - "waterAmount": 300, 216 - "timeSeconds": 180, 217 - "grindSize": "18", 218 - "grinderRef": "at://did:plc:abc/com.arabica.grinder/3jxy...", 219 - "brewerRef": "at://did:plc:abc/com.arabica.brewer/3jxy...", 220 - "tastingNotes": "Bright acidity, notes of lemon and bergamot", 221 - "rating": 9, 222 - "pours": [ 223 - {"waterAmount": 50, "timeSeconds": 0}, 224 - {"waterAmount": 100, "timeSeconds": 45}, 225 - {"waterAmount": 150, "timeSeconds": 90} 226 - ], 227 - "createdAt": "2024-01-04T08:30:00Z" 228 - } 229 - ``` 230 - 231 - --- 232 - 233 - ## Data Relationships 234 - 235 - ``` 236 - Roaster ←── Bean ←── Brew 237 - 238 - Grinder 239 - 240 - Brewer 241 - 242 - Pours (embedded) 243 - ``` 244 - 245 - **Key Points:** 246 - - All relationships are AT-URI references 247 - - References point within user's own repo (typically) 248 - - Users maintain their own copies of beans/roasters 249 - - Broken references should be handled gracefully in UI 250 - 251 - --- 252 - 253 - ## Validation Rules 254 - 255 - ### String Lengths 256 - - Names/titles: 200 chars max 257 - - Short fields: 50-100 chars max 258 - - Descriptions/notes: 1000 chars max 259 - - Tasting notes: 2000 chars max 260 - - URLs: 500 chars max 261 - 262 - ### Numeric Constraints 263 - - Temperature: 0-100°C (reasonable coffee range) 264 - - Rating: 1-10 (explicit range) 265 - - Water amount, time: ≥0 (non-negative) 37 + ### Pours 38 + Embedded in brew records as an array rather than separate collection. 266 39 267 40 ### Required Fields 268 - - All records: `createdAt` (for temporal ordering) 269 - - Bean: `name` (minimum identifier) 270 - - Roaster: `name` 271 - - Grinder: `name` 272 - - Brewer: `name` 273 - - Brew: `beanRef` (essential relationship) 274 - 275 - --- 276 - 277 - ## Schema Evolution 278 - 279 - ### Backward Compatibility 280 - If schema changes are needed: 281 - - Add new optional fields freely 282 - - Never remove required fields 283 - - Deprecate fields gradually 284 - - Use new collection IDs for breaking changes 285 - 286 - ### Versioning Strategy 287 - - Start with `com.arabica.*` (implicit v1) 288 - - If major breaking change needed: `com.arabica.v2.*` 289 - - Document migration paths 290 - 291 - --- 292 - 293 - ## Publishing Lexicons 294 - 295 - ### Phase 1 (Current) 296 - - Lexicons in project repo: `lexicons/` 297 - - Not yet published publicly 298 - - For development use 299 - 300 - ### Phase 2 (Future) 301 - Host lexicons at one of: 302 - 1. **GitHub Raw** (easiest): 303 - ``` 304 - https://raw.githubusercontent.com/user/arabica/main/lexicons/com.arabica.brew.json 305 - ``` 306 - 307 - 2. **Domain .well-known** (proper): 308 - ``` 309 - https://arabica.com/.well-known/atproto/lexicons/com.arabica.brew.json 310 - ``` 41 + Minimal requirements - most fields are optional for flexibility. 311 42 312 - 3. **Both** (recommended): 313 - - GitHub for development/reference 314 - - Domain for production validation 43 + ## Schema Files 315 44 316 - --- 317 - 318 - ## Testing Strategy 319 - 320 - ### Manual Testing (Phase 0) 321 - - [ ] Create test records with atproto CLI 322 - - [ ] Verify schema validation passes 323 - - [ ] Test all field types 324 - - [ ] Test reference resolution 325 - - [ ] Test with missing optional fields 326 - 327 - ### Automated Testing (Future) 328 - - Unit tests for record conversion 329 - - Integration tests for CRUD operations 330 - - Schema validation in CI/CD 331 - 332 - --- 333 - 334 - ## Open Questions 335 - 336 - 1. **Should we version lexicons explicitly?** 337 - - Current: No version in ID 338 - - Alternative: `com.arabica.v1.brew` 339 - 340 - 2. **Should we add metadata fields?** 341 - - Examples: `version`, `updatedAt`, `tags` 342 - - Current: Minimal schema 343 - 344 - 3. **Should we support recipe sharing?** 345 - - Could add `recipeRef` field to brew 346 - - Future enhancement 347 - 348 - --- 349 - 350 - ## Comparison to Original Schema 351 - 352 - ### SQLite Tables → Lexicons 353 - 354 - | SQLite Table | Lexicon Collection | Changes | 355 - |--------------|-------------------|---------| 356 - | `users` | (removed) | Implicit - DID is the user | 357 - | `beans` | `com.arabica.bean` | `roaster_id` → `roasterRef` | 358 - | `roasters` | `com.arabica.roaster` | Minimal changes | 359 - | `grinders` | `com.arabica.grinder` | Enum types added | 360 - | `brewers` | `com.arabica.brewer` | Minimal changes | 361 - | `brews` | `com.arabica.brew` | Foreign keys → AT-URIs | 362 - | `pours` | (embedded in brew) | Now part of brew record | 363 - 364 - ### Key Architectural Changes 365 - - No user_id (implicit from repo) 366 - - No integer IDs (TID-based keys) 367 - - No foreign key constraints (AT-URI references) 368 - - No separate pours table (embedded) 369 - - Public by default (repo visibility) 370 - 371 - --- 372 - 373 - ## Migration Notes 374 - 375 - When migrating from SQLite to atproto: 376 - 1. User ID 1 → Your DID 377 - 2. Integer IDs → Create new TIDs 378 - 3. Foreign keys → Look up AT-URIs 379 - 4. Pours → Embed in brew records 380 - 5. Timestamps → RFC3339 format 381 - 382 - See `docs/migration-guide.md` (future) for detailed migration script. 45 + See `lexicons/` directory for complete JSON schemas.
+1 -187
docs/testing-pds-storage.md
··· 1 - # Testing PDS Storage 2 - 3 - ## Current Status 4 - 5 - **YES, the app IS trying to write to your PDS!** 6 - 7 - When you're logged in via OAuth, the handlers use `AtprotoStore` which makes XRPC calls to your Personal Data Server. 8 - 9 - ## How It Works 10 - 11 - 1. **Login** → OAuth flow → Session with your DID 12 - 2. **Create Bean** → `POST /api/beans` → Calls `com.atproto.repo.createRecord` on your PDS 13 - 3. **PDS stores the record** → Returns AT-URI like `at://did:plc:abc123/com.arabica.bean/3jxydef789` 14 - 4. **App stores the record** → But has a problem with the returned ID... 15 - 16 - ## The ID Problem 17 - 18 - **Current Issue:** The app expects integer IDs but PDS returns string rkeys (TIDs). 19 - 20 - ### What Happens Now 21 - 22 - ``` 23 - User creates bean "Ethiopian Yirgacheffe" 24 - 25 - App calls: CreateRecord("com.arabica.bean", {name: "Ethiopian Yirgacheffe", ...}) 26 - 27 - PDS returns: {uri: "at://did:plc:abc/com.arabica.bean/3jxy...", cid: "..."} 28 - 29 - App extracts rkey: "3jxy..." 30 - 31 - App sets bean.ID = 0 ← PROBLEM! Should store the rkey somewhere 32 - 33 - Bean is returned to UI with ID=0 34 - ``` 35 - 36 - ### When Creating a Brew 37 - 38 - ``` 39 - User selects bean from dropdown (shows ID=0 or wrong ID) 40 - 41 - Form submits: {beanID: 0, ...} 42 - 43 - App constructs: at://did:plc:abc/com.arabica.bean/0 ← INVALID! 44 - 45 - PDS call fails: "Record not found" 46 - ``` 47 - 48 - ## Testing Right Now 49 - 50 - ### What Works ✅ 51 - 52 - 1. **OAuth login** - You can log in with your atproto handle 53 - 2. **List operations** - Will fetch from your PDS (empty initially) 54 - 3. **Create operations** - Will call PDS API (but ID handling is broken) 55 - 56 - ### What Doesn't Work ❌ 57 - 58 - 1. **Creating brews** - Requires valid bean references (IDs don't map correctly) 59 - 2. **Editing records** - ID → rkey lookup fails 60 - 3. **Deleting records** - ID → rkey lookup fails 61 - 4. **References** - Can't construct valid AT-URIs from integer IDs 62 - 63 - ## How to Test Manually 64 - 65 - ### Test 1: Create a Bean (Direct API Call) 66 - 67 - ```bash 68 - # Log in first via browser at http://localhost:18910/login 69 - # Then get your session cookie and... 70 - 71 - curl -X POST http://localhost:18910/api/beans \ 72 - -H "Content-Type: application/json" \ 73 - -H "Cookie: account_did=...; session_id=..." \ 74 - -d '{ 75 - "name": "Test Bean", 76 - "origin": "Ethiopia", 77 - "roast_level": "Light", 78 - "process": "Washed" 79 - }' 80 - ``` 81 - 82 - **Watch the server logs** - you should see: 83 - - XRPC call to your PDS 84 - - Either success with returned URI, or an error 85 - 86 - ### Test 2: List Beans from PDS 87 - 88 - ```bash 89 - curl http://localhost:18910/manage \ 90 - -H "Cookie: account_did=...; session_id=..." 91 - ``` 92 - 93 - This will try to list all beans from your PDS. 94 - 95 - ### Test 3: Check Your PDS Directly 96 - 97 - Use the atproto API to see what's actually in your repo: 98 - 99 - ```bash 100 - # List all records in com.arabica.bean collection 101 - curl "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=yourhandle.bsky.social&collection=com.arabica.bean" 102 - ``` 103 - 104 - ## Debugging 105 - 106 - ### Enable Verbose Logging 107 - 108 - The store already has debug printf statements: 109 - 110 - ```go 111 - fmt.Printf("Warning: failed to resolve brew references: %v\n", err) 112 - ``` 113 - 114 - Watch your server console for these messages. 115 - 116 - ### Check XRPC Calls 117 - 118 - The `client.go` makes XRPC calls via indigo's `client.Do()`. If there are errors, they'll be returned and logged. 119 - 120 - ## Next Steps to Fix 121 - 122 - ### Option 1: Store rkey in models (Quick Fix) 123 - 124 - Add a `RKey string` field to all models: 125 - 126 - ```go 127 - type Bean struct { 128 - ID int `json:"id"` 129 - RKey string `json:"rkey"` // ← Add this 130 - Name string `json:"name"` 131 - // ... 132 - } 133 - ``` 134 - 135 - Then update AtprotoStore to: 136 - 1. Store the rkey when creating 137 - 2. Use the rkey for updates/deletes 138 - 3. Build AT-URIs from stored rkeys 139 - 140 - ### Option 2: In-Memory Mapping (Temporary) 141 - 142 - Keep a map in AtprotoStore: 143 - 144 - ```go 145 - type AtprotoStore struct { 146 - // ... 147 - idToRKey map[string]map[int]string // collection -> id -> rkey 148 - } 149 - ``` 150 - 151 - ### Option 3: Use rkeys as IDs (Proper Fix) 152 - 153 - Change models to use string IDs everywhere: 154 - 155 - ```go 156 - type Bean struct { 157 - ID string `json:"id"` // Now stores rkey like "3jxy..." 158 - Name string `json:"name"` 159 - // ... 160 - } 161 - ``` 162 - 163 - This requires updating: 164 - - All handlers (parse string IDs, not ints) 165 - - Templates (use string IDs in forms) 166 - - Store interface (change signatures) 167 - 168 - ## Recommended Testing Path 169 - 170 - 1. **Update models to store rkeys** (Option 1) 171 - 2. **Test bean creation** - verify record appears in PDS 172 - 3. **Test bean listing** - verify records are fetched from PDS 173 - 4. **Test brew creation with valid bean rkey** 174 - 5. **Verify end-to-end flow works** 175 - 176 - ## Current Code Locations 177 - 178 - - **Store implementation**: `internal/atproto/store.go` 179 - - **Record conversions**: `internal/atproto/records.go` 180 - - **XRPC client**: `internal/atproto/client.go` 181 - - **Handlers**: `internal/handlers/handlers.go` 182 - 183 - ## Summary 184 - 185 - **You're 90% there!** The OAuth works, the XRPC calls are being made, the record conversions are correct. The only missing piece is proper ID/rkey handling so that references work correctly. 186 - 187 - The quickest path forward is to add `RKey` fields to the models and update the store to use them. 1 + # Deleted - Remove this file