···2121- `bsky.app` is feeling 🦋 according to `https://bsky.app/status.json`
2222- `reddit.com` is feeling 🤓 according to `https://reddit.com/status.json`
23232424-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.
2424+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.
2525+2626+> `at://` is the URL scheme of the AT Protocol.
25272628## Step 1. Starting with our ExpressJS app
2729···234236await agent.getRecord({
235237 repo: agent.accountDid, // The user
236238 collection: 'app.bsky.actor.profile', // The collection
237237- rkey: 'self', // The record name
239239+ rkey: 'self', // The record key
238240})
239241```
240242241243We write records using a similar API. Since our goal is to write "status" records, let's look at how that will happen:
242244243245```typescript
246246+// Generate a time-based key for our record
247247+const rkey = TID.nextStr()
248248+249249+// Write the
244250await agent.putRecord({
245251 repo: agent.accountDid, // The user
246252 collection: 'com.example.status', // The collection
247247- rkey: 'self', // The record name
253253+ rkey, // The record key
248254 record: { // The record value
249255 status: "👍",
250250- updatedAt: new Date().toISOString()
256256+ createdAt: new Date().toISOString()
251257 }
252258})
253259```
···270276 const record = {
271277 $type: 'com.example.status',
272278 status: req.body?.status,
273273- updatedAt: new Date().toISOString(),
279279+ createdAt: new Date().toISOString(),
274280 }
275281276282 try {
···278284 await agent.putRecord({
279285 repo: agent.accountDid,
280286 collection: 'com.example.status',
281281- rkey: 'self',
287287+ rkey: TID.nextStr(),
282288 record,
283289 })
284290 } catch (err) {
···331337332338> ### Why create a schema?
333339>
334334-> 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.
340340+> 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.
335341336342Let's create our schema in the `/lexicons` folder of our codebase. You can [read more about how to define schemas here](#todo).
337343···343349 "defs": {
344350 "main": {
345351 "type": "record",
346346- "key": "literal:self",
352352+ "key": "tid",
347353 "record": {
348354 "type": "object",
349349- "required": ["status", "updatedAt"],
355355+ "required": ["status", "createdAt"],
350356 "properties": {
351357 "status": {
352358 "type": "string",
···354360 "maxGraphemes": 1,
355361 "maxLength": 32
356362 },
357357- "updatedAt": {
363363+ "createdAt": {
358364 "type": "string",
359365 "format": "datetime"
360366 }
···387393 const record = {
388394 $type: 'com.example.status',
389395 status: req.body?.status,
390390- updatedAt: new Date().toISOString(),
396396+ createdAt: new Date().toISOString(),
391397 }
392398 if (!Status.validateRecord(record).success) {
393399 return res.status(400).json({ error: 'Invalid status' })
···415421│ REPO │ Event stream
416422├──────┘
417423│ ┌───────────────────────────────────────────┐
418418-├───┼ 1 PUT /com.example.status/self │
424424+├───┼ 1 PUT /app.bsky.feed.post/3l244rmrxjx2v │
419425│ └───────────────────────────────────────────┘
420426│ ┌───────────────────────────────────────────┐
421427├───┼ 2 DEL /app.bsky.feed.post/3l244rmrxjx2v │
422428│ └───────────────────────────────────────────┘
423429│ ┌───────────────────────────────────────────┐
424424-├───┼ 3 PUT /app.bsky.actor/self │
430430+├───┼ 3 PUT /app.bsky.actor.profile/self │
425431▼ └───────────────────────────────────────────┘
426432```
427433···459465// Create our statuses table
460466await db.schema
461467 .createTable('status')
462462- .addColumn('authorDid', 'varchar', (col) => col.primaryKey())
468468+ .addColumn('uri', 'varchar', (col) => col.primaryKey())
469469+ .addColumn('authorDid', 'varchar', (col) => col.notNull())
463470 .addColumn('status', 'varchar', (col) => col.notNull())
464464- .addColumn('updatedAt', 'varchar', (col) => col.notNull())
471471+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
465472 .addColumn('indexedAt', 'varchar', (col) => col.notNull())
466473 .execute()
467474```
···480487 await db
481488 .insertInto('status')
482489 .values({
490490+ uri: evt.uri.toString(),
483491 authorDid: evt.author,
484492 status: record.status,
485485- updatedAt: record.updatedAt,
493493+ createdAt: record.createdAt,
486494 indexedAt: new Date().toISOString(),
487495 })
488496 .onConflict((oc) =>
489489- oc.column('authorDid').doUpdateSet({
497497+ oc.column('uri').doUpdateSet({
490498 status: record.status,
491491- updatedAt: record.updatedAt,
492499 indexedAt: new Date().toISOString(),
493500 })
494501 )
···546553<!-- src/pages/home.ts -->
547554${statuses.map((status, i) => {
548555 const handle = didHandleMap[status.authorDid] || status.authorDid
549549- const date = ts(status)
550556 return html`
551557 <div class="status-line">
552558 <div>
···587593 handler(async (req, res) => {
588594 // ...
589595596596+ let uri
590597 try {
591598 // Write the status record to the user's repository
592592- await agent.putRecord({
599599+ const res = await agent.putRecord({
593600 repo: agent.accountDid,
594601 collection: 'com.example.status',
595595- rkey: 'self',
602602+ rkey: TID.nextStr(),
596603 record,
597604 })
605605+ uri = res.uri
598606 } catch (err) {
599607 ctx.logger.warn({ err }, 'failed to write record')
600608 return res.status(500).json({ error: 'Failed to write record' })
···605613 await ctx.db
606614 .insertInto('status')
607615 .values({
616616+ uri,
608617 authorDid: agent.accountDid,
609618 status: record.status,
610610- updatedAt: record.updatedAt,
619619+ createdAt: record.createdAt,
611620 indexedAt: new Date().toISOString(),
612621 })
613613- .onConflict((oc) =>
614614- oc.column('authorDid').doUpdateSet({
615615- status: record.status,
616616- updatedAt: record.updatedAt,
617617- indexedAt: new Date().toISOString(),
618618- })
619619- )
620622 .execute()
621623 } catch (err) {
622624 ctx.logger.warn(