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

Remove my cutting room floor

-130
-130
TUTORIAL.md
··· 637 637 TODO 638 638 639 639 640 - 641 - --- 642 - 643 - Let's create the client during the server init: 644 - 645 - ```typescript 646 - /** index.ts **/ 647 - import { NodeOAuthClient } from '@atproto/oauth-client-node' 648 - 649 - // static async create() { 650 - // ... 651 - 652 - const publicUrl = env.PUBLIC_URL 653 - const url = publicUrl || `http://127.0.0.1:${env.PORT}` 654 - const oauthClient = new NodeOAuthClient({ 655 - clientMetadata: { 656 - client_name: 'AT Protocol Express App', 657 - client_id: publicUrl 658 - ? `${url}/client-metadata.json` 659 - : `http://localhost?redirect_uri=${encodeURIComponent(`${url}/oauth/callback`)}`, 660 - client_uri: url, 661 - redirect_uris: [`${url}/oauth/callback`], 662 - scope: 'profile offline_access', 663 - grant_types: ['authorization_code', 'refresh_token'], 664 - response_types: ['code'], 665 - application_type: 'web', 666 - token_endpoint_auth_method: 'none', 667 - dpop_bound_access_tokens: true, 668 - }, 669 - stateStore: new StateStore(db), 670 - sessionStore: new SessionStore(db), 671 - }) 672 - 673 - // ... 674 - // } 675 - ``` 676 - 677 - 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. 678 - 679 - ```typescript 680 - /** src/routes.ts **/ 681 - 682 - // OAuth metadata 683 - router.get( 684 - '/client-metadata.json', 685 - handler((_req, res) => { 686 - return res.json(oauthClient.clientMetadata) 687 - }) 688 - ) 689 - ``` 690 - 691 - --- 692 - 693 - 694 - 695 - We're going to need to track two kinds of information: 696 - 697 - - **OAuth State**. This is information about login flows that are in-progress. 698 - - **OAuth Sessions**. This is the active session data. 699 - 700 - 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. 701 - 702 - ```typescript 703 - // ... 704 - // Types 705 - 706 - export type DatabaseSchema = { 707 - auth_session: AuthSession 708 - auth_state: AuthState 709 - } 710 - 711 - export type AuthSession = { 712 - key: string 713 - session: string // JSON 714 - } 715 - 716 - export type AuthState = { 717 - key: string 718 - state: string // JSON 719 - } 720 - 721 - // Migrations 722 - 723 - migrations['001'] = { 724 - async up(db: Kysely<unknown>) { 725 - await db.schema 726 - .createTable('auth_session') 727 - .addColumn('key', 'varchar', (col) => col.primaryKey()) 728 - .addColumn('session', 'varchar', (col) => col.notNull()) 729 - .execute() 730 - await db.schema 731 - .createTable('auth_state') 732 - .addColumn('key', 'varchar', (col) => col.primaryKey()) 733 - .addColumn('state', 'varchar', (col) => col.notNull()) 734 - .execute() 735 - }, 736 - async down(db: Kysely<unknown>) { 737 - await db.schema.dropTable('auth_state').execute() 738 - await db.schema.dropTable('auth_session').execute() 739 - }, 740 - } 741 - 742 - // ... 743 - ``` 744 - 745 - 746 - ---- 747 - 748 - 749 - 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. 750 - 751 - 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: 752 - 753 - - `nytimes.com` is feeling 📰 according to `https://nytimes.com/status.txt` 754 - - `bsky.app` is feeling 🦋 according to `https://bsky.app/status.txt` 755 - - `reddit.com` is feeling 🤓 according to `https://reddit.com/status.txt` 756 - 757 - 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. 758 - 759 - ``` 760 - AT Protocol 761 - 762 - at://nytimes.com/com.example.status/self 763 - ▲ ▲ ▲ 764 - The user The data type The record name 765 - ``` 766 - 767 - 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. 768 - 769 - Believe it or not, that's how most apps on the Atmosphere are built, including [Bluesky](#todo).