Statusphere Example App

repository·main·Indexed 43 days ago

https://github.com/bluesky-social/statusphere-example-app

An example Next.js application demonstrating how to build social apps on the AT Protocol. It features OAuth authentication, profile fetching, custom schema publication, and network firehose listening via the Tap data synchronization service.

Tokens
3.5K
Snippets
19
Records
21
Agent score
56%

What's inside Statusphere Example App

  1. Overview of Statusphere Example App capabilities

    main

    The Statusphere example app demonstrates several core AT Protocol patterns:

    • Authentication: Signing in via OAuth.
    • Profile Management: Fetching information about users (profiles).
    • Data Consumption: Listening to the network firehose for new data via Tap.
    • Data Publication: Publishing data on a user's account using a custom schema.

    This project is built using Next.js and uses Tap for syncing data from the Atmosphere.

  2. Install and run the Statusphere Example App

    main

    To set up the Statusphere example app locally, clone the repository, configure your environment variables from the template, install dependencies using pnpm, and start the development server.

    1. Clone the repository.
    2. Copy env.template to .env.local.
    3. Run pnpm install.
    4. Run pnpm dev.
    5. Access the application at http://127.0.0.1:3000.
    git clone https://github.com/bluesky-social/statusphere-example-app.git
    cd statusphere-example-app
    cp env.template .env.local
    pnpm install
    pnpm dev
  3. Set up Tap for network data synchronization

    main

    The Statusphere app requires an instance of Tap to read data from the network. Tap watches the xyz.statusphere.status collection, allowing the app to automatically pick up status updates from any account using this schema without manual repository registration.

    For the quickest local setup, use Docker Compose:

    docker compose up -d

    Data is persisted in the .tap-data/ directory between restarts. For advanced configurations, such as running from source or securing webhooks with TAP_ADMIN_PASSWORD, refer to the Tap repository documentation.

  4. Configure ESLint for the Statusphere Example App

    main

    The project uses ESLint with a configuration based on Next.js core web vitals and TypeScript rules. It utilizes eslint/config to define the configuration and globalIgnores to manage directory exclusions.

    By default, the configuration includes eslint-config-next/core-web-vitals and eslint-config-next/typescript. The configuration explicitly overrides the default ignores from eslint-config-next to ensure specific build and environment files are excluded from linting.

    import { defineConfig, globalIgnores } from "eslint/config";
    import nextVitals from "eslint-config-next/core-web-vitals";
    import nextTs from "eslint-config-next/typescript";
    
    const eslintConfig = defineConfig([
      ...nextVitals,
      ...nextTs,
      // Override default ignores of eslint-config-next.
      globalIgnores([
        // Default ignores of eslint-config-next:
        ".next/**",
        "out/**",
        "build/**",
        "next-env.d.ts",
      ]),
    ]);
    
    export default eslintConfig;
  5. Configure the Tap service via Docker Compose

    main

    The tap service runs the Tap engine, which handles signal collections and webhooks. You can configure its behavior using environment variables in your docker-compose.yml.

    Environment Variables

    • TAP_WEBHOOK_URL: The endpoint where Tap sends webhooks. By default, it is set to http://host.docker.internal:3000/api/webhook to reach a Next.js dev server running on the host machine from inside the container.
    • TAP_SIGNAL_COLLECTION: The AT Protocol collection used as the signal collection. Accounts with records in this collection are auto-discovered.
    • TAP_COLLECTION_FILTERS: The collection used to filter signals. Per Tap documentation, this should match the TAP_SIGNAL_COLLECTION.

    Default Service Configuration

    • Image: ghcr.io/bluesky-social/indigo/tap:latest
    • Command: /tap run --no-replay
    • Port Mapping: 127.0.0.1:2480:2480
    • Data Volume: ./.tap-data:/data
    services:
      tap:
        image: ghcr.io/bluesky-social/indigo/tap:latest
        command: ["/tap", "run", "--no-replay"]
        ports:
          - "127.0.0.1:2480:2480"
        volumes:
          - ./.tap-data:/data
        environment:
          TAP_WEBHOOK_URL: ${TAP_WEBHOOK_URL:-http://host.docker.internal:3000/api/webhook}
          TAP_SIGNAL_COLLECTION: xyz.statusphere.status
          TAP_COLLECTION_FILTERS: xyz.statusphere.status
  6. Insert or update a status

    main

    Inserts a new status into the database. If a status with the same uri already exists, it performs an update on the status, createdAt, and indexedAt fields. This operation is wrapped in a transaction that also updates the user's current status flag.

    import { StatusTable } from ".";
    
    await insertStatus({
      uri: 'at://bsky.social/post/123',
      authorDid: 'did:plc:example',
      status: 'Hello world!',
      createdAt: new Date(),
      indexedAt: new Date(),
      current: 1
    } as StatusTable);
  7. Retrieve an account handle by DID

    main

    Resolves an AT Protocol handle from a Decentralized Identifier (DID). It first attempts to look up the handle in the local database. If not found, it uses the getTap identity cache to resolve the DID via the AT Protocol. Returns null if the handle cannot be resolved.

    const handle = await getAccountHandle('did:plc:example');
  8. Initialize the database client with getDb()

    main

    Use getDb() to obtain a singleton Kysely instance configured for SQLite. The function automatically initializes the connection using better-sqlite3 and sets the pragma journal_mode = WAL for improved concurrency. The database file path is determined by the DATABASE_PATH environment variable, defaulting to app.db if not provided.

    import { getDb } from './lib/db';
    
    const db = getDb();
    // Use db with Kysely methods
    const account = await db
      .selectFrom('account')
      .selectAll()
      .executeTakeFirst();
  9. Get the Tap client instance with getTap()

    main

    Use getTap() to retrieve a singleton instance of the Tap client. This client is used to interact with the Atmosphere data sync service. By default, it attempts to connect to the URL specified in the TAP_URL environment variable, falling back to http://localhost:2480 if the variable is not set.

    import { getTap } from './lib/tap';
    
    const tap = getTap();
    // Use the tap instance to interact with Atmosphere data sync
  10. Fetch top trending statuses

    main

    Retrieves the most frequent status messages currently marked as current. It groups by the status text and counts occurrences to determine popularity. Defaults to the top 10.

    // Returns the top 10 trending statuses
    const top = await getTopStatuses();
    
    // Returns the top 50 trending statuses
    const topLarge = await getTopStatuses(50);