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/minutePOST /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 metadatacursors- Tracks firehose sync position (for future use)schema_migrations- Migration version tracking
Indexes:
idx_target_url- Fast lookups by page URLidx_author_did- Filter annotations by authoridx_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