the statusphere demo reworked into a vite/react app in a monorepo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update tutorial to reflect changes to example app

+32 -30
+31 -29
TUTORIAL.md
··· 21 21 - `bsky.app` is feeling 🦋 according to `https://bsky.app/status.json` 22 22 - `reddit.com` is feeling 🤓 according to `https://reddit.com/status.json` 23 23 24 - The Atmosphere works the same way, except we're going to check `at://` instead of `https://`. Each user has a data repo under an `at://` URL. We'll crawl all the `at://`s in the Atmosphere for all the `/status.json` records and aggregate them into our SQLite database. 24 + The Atmosphere works the same way, except we're going to check `at://` instead of `https://`. Each user has a data repo under an `at://` URL. We'll crawl all the `at://`s in the Atmosphere for all the "status.json" records and aggregate them into our SQLite database. 25 + 26 + > `at://` is the URL scheme of the AT Protocol. 25 27 26 28 ## Step 1. Starting with our ExpressJS app 27 29 ··· 234 236 await agent.getRecord({ 235 237 repo: agent.accountDid, // The user 236 238 collection: 'app.bsky.actor.profile', // The collection 237 - rkey: 'self', // The record name 239 + rkey: 'self', // The record key 238 240 }) 239 241 ``` 240 242 241 243 We write records using a similar API. Since our goal is to write "status" records, let's look at how that will happen: 242 244 243 245 ```typescript 246 + // Generate a time-based key for our record 247 + const rkey = TID.nextStr() 248 + 249 + // Write the 244 250 await agent.putRecord({ 245 251 repo: agent.accountDid, // The user 246 252 collection: 'com.example.status', // The collection 247 - rkey: 'self', // The record name 253 + rkey, // The record key 248 254 record: { // The record value 249 255 status: "👍", 250 - updatedAt: new Date().toISOString() 256 + createdAt: new Date().toISOString() 251 257 } 252 258 }) 253 259 ``` ··· 270 276 const record = { 271 277 $type: 'com.example.status', 272 278 status: req.body?.status, 273 - updatedAt: new Date().toISOString(), 279 + createdAt: new Date().toISOString(), 274 280 } 275 281 276 282 try { ··· 278 284 await agent.putRecord({ 279 285 repo: agent.accountDid, 280 286 collection: 'com.example.status', 281 - rkey: 'self', 287 + rkey: TID.nextStr(), 282 288 record, 283 289 }) 284 290 } catch (err) { ··· 331 337 332 338 > ### Why create a schema? 333 339 > 334 - > Schemas help other applications understand the data your app is creating. By publishing your schemas, you enable compatibility and reduce the chances of bad data affecting your app. 340 + > Schemas help other applications understand the data your app is creating. By publishing your schemas, you enable compatibility with other apps and reduce the chances of bad data affecting your app. 335 341 336 342 Let's create our schema in the `/lexicons` folder of our codebase. You can [read more about how to define schemas here](#todo). 337 343 ··· 343 349 "defs": { 344 350 "main": { 345 351 "type": "record", 346 - "key": "literal:self", 352 + "key": "tid", 347 353 "record": { 348 354 "type": "object", 349 - "required": ["status", "updatedAt"], 355 + "required": ["status", "createdAt"], 350 356 "properties": { 351 357 "status": { 352 358 "type": "string", ··· 354 360 "maxGraphemes": 1, 355 361 "maxLength": 32 356 362 }, 357 - "updatedAt": { 363 + "createdAt": { 358 364 "type": "string", 359 365 "format": "datetime" 360 366 } ··· 387 393 const record = { 388 394 $type: 'com.example.status', 389 395 status: req.body?.status, 390 - updatedAt: new Date().toISOString(), 396 + createdAt: new Date().toISOString(), 391 397 } 392 398 if (!Status.validateRecord(record).success) { 393 399 return res.status(400).json({ error: 'Invalid status' }) ··· 415 421 │ REPO │ Event stream 416 422 ├──────┘ 417 423 │ ┌───────────────────────────────────────────┐ 418 - ├───┼ 1 PUT /com.example.status/self │ 424 + ├───┼ 1 PUT /app.bsky.feed.post/3l244rmrxjx2v │ 419 425 │ └───────────────────────────────────────────┘ 420 426 │ ┌───────────────────────────────────────────┐ 421 427 ├───┼ 2 DEL /app.bsky.feed.post/3l244rmrxjx2v │ 422 428 │ └───────────────────────────────────────────┘ 423 429 │ ┌───────────────────────────────────────────┐ 424 - ├───┼ 3 PUT /app.bsky.actor/self │ 430 + ├───┼ 3 PUT /app.bsky.actor.profile/self │ 425 431 ▼ └───────────────────────────────────────────┘ 426 432 ``` 427 433 ··· 459 465 // Create our statuses table 460 466 await db.schema 461 467 .createTable('status') 462 - .addColumn('authorDid', 'varchar', (col) => col.primaryKey()) 468 + .addColumn('uri', 'varchar', (col) => col.primaryKey()) 469 + .addColumn('authorDid', 'varchar', (col) => col.notNull()) 463 470 .addColumn('status', 'varchar', (col) => col.notNull()) 464 - .addColumn('updatedAt', 'varchar', (col) => col.notNull()) 471 + .addColumn('createdAt', 'varchar', (col) => col.notNull()) 465 472 .addColumn('indexedAt', 'varchar', (col) => col.notNull()) 466 473 .execute() 467 474 ``` ··· 480 487 await db 481 488 .insertInto('status') 482 489 .values({ 490 + uri: evt.uri.toString(), 483 491 authorDid: evt.author, 484 492 status: record.status, 485 - updatedAt: record.updatedAt, 493 + createdAt: record.createdAt, 486 494 indexedAt: new Date().toISOString(), 487 495 }) 488 496 .onConflict((oc) => 489 - oc.column('authorDid').doUpdateSet({ 497 + oc.column('uri').doUpdateSet({ 490 498 status: record.status, 491 - updatedAt: record.updatedAt, 492 499 indexedAt: new Date().toISOString(), 493 500 }) 494 501 ) ··· 546 553 <!-- src/pages/home.ts --> 547 554 ${statuses.map((status, i) => { 548 555 const handle = didHandleMap[status.authorDid] || status.authorDid 549 - const date = ts(status) 550 556 return html` 551 557 <div class="status-line"> 552 558 <div> ··· 587 593 handler(async (req, res) => { 588 594 // ... 589 595 596 + let uri 590 597 try { 591 598 // Write the status record to the user's repository 592 - await agent.putRecord({ 599 + const res = await agent.putRecord({ 593 600 repo: agent.accountDid, 594 601 collection: 'com.example.status', 595 - rkey: 'self', 602 + rkey: TID.nextStr(), 596 603 record, 597 604 }) 605 + uri = res.uri 598 606 } catch (err) { 599 607 ctx.logger.warn({ err }, 'failed to write record') 600 608 return res.status(500).json({ error: 'Failed to write record' }) ··· 605 613 await ctx.db 606 614 .insertInto('status') 607 615 .values({ 616 + uri, 608 617 authorDid: agent.accountDid, 609 618 status: record.status, 610 - updatedAt: record.updatedAt, 619 + createdAt: record.createdAt, 611 620 indexedAt: new Date().toISOString(), 612 621 }) 613 - .onConflict((oc) => 614 - oc.column('authorDid').doUpdateSet({ 615 - status: record.status, 616 - updatedAt: record.updatedAt, 617 - indexedAt: new Date().toISOString(), 618 - }) 619 - ) 620 622 .execute() 621 623 } catch (err) { 622 624 ctx.logger.warn(
+1 -1
lexicons/status.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "record", 7 - "key": "literal:self", 7 + "key": "tid", 8 8 "record": { 9 9 "type": "object", 10 10 "required": ["status", "createdAt"],