Scratch space for learning atproto app development

Remove my cutting room floor

Changed files
-130
-130
TUTORIAL.md
···
TODO
-
-
---
-
-
Let's create the client during the server init:
-
-
```typescript
-
/** index.ts **/
-
import { NodeOAuthClient } from '@atproto/oauth-client-node'
-
-
// static async create() {
-
// ...
-
-
const publicUrl = env.PUBLIC_URL
-
const url = publicUrl || `http://127.0.0.1:${env.PORT}`
-
const oauthClient = new NodeOAuthClient({
-
clientMetadata: {
-
client_name: 'AT Protocol Express App',
-
client_id: publicUrl
-
? `${url}/client-metadata.json`
-
: `http://localhost?redirect_uri=${encodeURIComponent(`${url}/oauth/callback`)}`,
-
client_uri: url,
-
redirect_uris: [`${url}/oauth/callback`],
-
scope: 'profile offline_access',
-
grant_types: ['authorization_code', 'refresh_token'],
-
response_types: ['code'],
-
application_type: 'web',
-
token_endpoint_auth_method: 'none',
-
dpop_bound_access_tokens: true,
-
},
-
stateStore: new StateStore(db),
-
sessionStore: new SessionStore(db),
-
})
-
-
// ...
-
// }
-
```
-
-
There's quite a bit of configuration which is [explained in the OAuth guide](#todo). We host that config at `/client-metadata.json` as part of the OAuth flow.
-
-
```typescript
-
/** src/routes.ts **/
-
-
// OAuth metadata
-
router.get(
-
'/client-metadata.json',
-
handler((_req, res) => {
-
return res.json(oauthClient.clientMetadata)
-
})
-
)
-
```
-
-
---
-
-
-
-
We're going to need to track two kinds of information:
-
-
- **OAuth State**. This is information about login flows that are in-progress.
-
- **OAuth Sessions**. This is the active session data.
-
-
The `oauth-client-node` library handles most of this for us, but we need to create some tables in our SQLite to store it. Let's update `/src/db.ts` for this.
-
-
```typescript
-
// ...
-
// Types
-
-
export type DatabaseSchema = {
-
auth_session: AuthSession
-
auth_state: AuthState
-
}
-
-
export type AuthSession = {
-
key: string
-
session: string // JSON
-
}
-
-
export type AuthState = {
-
key: string
-
state: string // JSON
-
}
-
-
// Migrations
-
-
migrations['001'] = {
-
async up(db: Kysely<unknown>) {
-
await db.schema
-
.createTable('auth_session')
-
.addColumn('key', 'varchar', (col) => col.primaryKey())
-
.addColumn('session', 'varchar', (col) => col.notNull())
-
.execute()
-
await db.schema
-
.createTable('auth_state')
-
.addColumn('key', 'varchar', (col) => col.primaryKey())
-
.addColumn('state', 'varchar', (col) => col.notNull())
-
.execute()
-
},
-
async down(db: Kysely<unknown>) {
-
await db.schema.dropTable('auth_state').execute()
-
await db.schema.dropTable('auth_session').execute()
-
},
-
}
-
-
// ...
-
```
-
-
-
----
-
-
-
Data in the Atmosphere is stored on users' personal servers. It's almost like each user has their own website. Our goal is to aggregate data from the users into our SQLite.
-
-
Think of our app like a Google. If Google's job was to say which emoji each website had under `/status.txt`, then it would show something like:
-
-
- `nytimes.com` is feeling 📰 according to `https://nytimes.com/status.txt`
-
- `bsky.app` is feeling 🦋 according to `https://bsky.app/status.txt`
-
- `reddit.com` is feeling 🤓 according to `https://reddit.com/status.txt`
-
-
The Atmosphere works the same way, except we're going to check `at://nytimes.com/com.example.status/self`. Literally, that's it! Each user has a domain, and each record gets published under an atproto URL.
-
-
```
-
AT Protocol
-
-
at://nytimes.com/com.example.status/self
-
▲ ▲ ▲
-
The user The data type The record name
-
```
-
-
When somebody logs into our app, they'll give read & write access to their personal `at://`. We'll use that to write the `/com.example.status/self` record. Then we'll crawl the Atmosphere for all the other `/com.example.status/self` records, and aggregate them into our SQLite database for fast reads.
-
-
Believe it or not, that's how most apps on the Atmosphere are built, including [Bluesky](#todo).