Social Annotations in the Atmosphere
15
fork

Configure Feed

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

README.md

Seams Annotation Indexing Backend#

A lightweight Go backend for indexing ATProto web annotations by URL, built for the seams.so annotation extension.

Features#

  • HTTP API: Simple REST endpoints for indexing and querying annotations
  • SQLite Storage: Single-file database for easy deployment
  • ATProto Integration: Fetches annotation records from Personal Data Servers
  • URL Normalization: Consistent URL matching (removes fragments, trailing slashes)
  • Firehose-Ready: Architecture supports future Jetstream integration

Quick Start#

Prerequisites#

  • Nix with flakes enabled

Installation#

Enter the development environment:

nix develop  # Full environment (default)

Or use the server-only shell:

nix develop .#server

Download dependencies:

cd server
go mod download

Run Server#

Development (with hot reload):

cd server
air

Production:

cd server
go run cmd/server/main.go

Server starts on http://localhost:8080

Configuration#

Environment variables:

  • PORT: Server port (default: 8080)
  • DB_PATH: SQLite database path (default: ./db/annotations.db)

Example:

PORT=3000 DB_PATH=/var/lib/annotations.db go run cmd/server/main.go

Rate Limits#

Built-in per-IP rate limiting:

  • GET /api/annotations: 100 requests/minute
  • POST /api/annotations/index: 10 requests/minute

Index Limits#

  • Maximum 1,000 annotations per URL
  • Maximum 100,000 total annotations

These limits prevent abuse and disk exhaustion.

API Endpoints#

Health Check#

GET /health

Response:

{"status": "healthy"}

Index Annotation#

POST /api/annotations/index
Content-Type: application/json

{
  "uri": "at://did:plc:abc123/community.lexicon.annotation.annotation/xyz789",
  "cid": "bafyreiabc123..."
}

Response:

{
  "success": true,
  "uri": "at://..."
}

Get Annotations#

By URL:

GET /api/annotations?url=https://example.com/article&limit=50

Recent (all URLs):

GET /api/annotations?limit=20

Response:

{
  "annotations": [
    {
      "uri": "at://...",
      "cid": "...",
      "authorDid": "did:plc:...",
      "authorHandle": "user.bsky.social",
      "targetUrl": "https://example.com/article",
      "exactText": "selected text",
      "selectors": "[...]",
      "body": "annotation comment",
      "createdAt": "2024-01-01T12:00:00Z"
    }
  ],
  "count": 1
}

Database Schema#

See internal/db/migrations/001_initial_schema.sql for the complete schema.

Main tables:

  • annotations - Stores annotation records with selectors and metadata
  • cursors - Tracks firehose sync position (for future use)
  • schema_migrations - Migration version tracking

Indexes:

  • idx_target_url - Fast lookups by page URL
  • idx_author_did - Filter annotations by author
  • idx_created_at - Chronological ordering

Migrations are automatically applied on server startup.

Testing#

Run Integration Tests#

go test ./internal/service -v

Manual Testing#

See TESTING.md for comprehensive manual testing checklist.

Architecture#

cmd/server/          - Main application entry point
internal/
  ├── api/           - HTTP handlers and routing
  ├── atproto/       - ATProto client for fetching records
  ├── db/            - SQLite database layer
  ├── models/        - Data models
  └── service/       - Core business logic (indexing, querying)

Firehose Integration (Future)#

The architecture supports adding a firehose subscriber:

cmd/firehose/        - New binary for Jetstream subscriber
internal/service/    - Shared indexing logic (already implemented)

Both HTTP and firehose paths will use the same IndexAnnotation() service method.

Extension Integration#

The browser extension should call the index endpoint after creating an annotation:

// After successful createRecord
const response = await fetch('http://localhost:8080/api/annotations/index', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    uri: createResult.uri,
    cid: createResult.cid
  })
});

Deployment#

Binary Build#

go build -o seams-server ./cmd/server
./seams-server

Docker (TODO)#

FROM golang:1.22-alpine
WORKDIR /app
COPY . .
RUN go build -o server ./cmd/server
CMD ["./server"]

Systemd Service (TODO)#

[Unit]
Description=Seams Annotation Server

[Service]
ExecStart=/usr/local/bin/seams-server
Environment="DB_PATH=/var/lib/seams/annotations.db"
Restart=always

[Install]
WantedBy=multi-user.target

License#

MIT