Appview
No description available
Install / Use
/learn @colibri-social/AppviewREADME
colibri-appview
[!WARNING] This implementation is almost entirely vibe-coded for the purpose of being able to quickly get started with development of the main application. It will be re-written in the near future to take advantage of Tap and be reworked to include all user data storage as well as any OAuth capabilities, which currently reside within the website's backend. If you are interested in helping with this, start a discussion on this repo!
An ATProto appview for the Colibri social platform, built with Rocket.rs.
Overview
| Feature | Details |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Jetstream consumer | Connects to a Colibri Jetstream WebSocket and ingests all Colibri lexicon records into PostgreSQL in real time, with cursor persistence and automatic reconnect. |
| Backfill | On startup, fetches historical records from each known DID's PDS so no data is missed. Triggered again automatically when the appview falls behind. Backfill includes Bluesky profiles (banner_url, handle, description) and actor status/emoji. |
| Client subscriptions | Clients connect via WebSocket (/api/subscribe?did=<did>) and subscribe to filtered event streams — messages by channel, community events by AT-URI. Community subscriptions automatically deliver member status and profile updates. Easy to extend with new event types. |
| Presence | Clients are marked online on WS connect, offline on disconnect, and away after 5 minutes of heartbeat-only activity. Preferred state (online/away/dnd) is persisted and restored on reconnect. |
| WebRTC signaling | Room-based peer-to-peer voice/video signaling over WebSocket (/api/webrtc/signal). |
| REST API | Full set of read endpoints for messages, authors, reactions, communities, channels, categories, members, and invite codes. |
Setup
Prerequisites
- Rust (stable, 1.75+)
- PostgreSQL 14+
1. Database
createdb colibri
Migrations run automatically on startup via sqlx::migrate!. No manual SQL needed.
2. Environment
cp .env.example .env
# Fill in at minimum DATABASE_URL and INVITE_API_KEY
3. Run
cargo run
The server listens on 0.0.0.0:8000 by default.
Docker
docker compose up --build
# or for development with live reload:
docker compose -f docker-compose.dev.yml up
Environment variables
| Variable | Required | Default | Description |
| ---------------- | -------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| DATABASE_URL | ✅ | — | PostgreSQL connection string, e.g. postgres://user:pass@localhost:5432/colibri |
| INVITE_API_KEY | ✅ | — | Bearer token required for POST /api/invite, DELETE /api/invite/<code>, GET /api/invites, POST /api/message/block, and POST /api/user/state |
| ROCKET_ADDRESS | ❌ | 0.0.0.0 | Bind address |
| ROCKET_PORT | ❌ | 8000 | Bind port |
| JETSTREAM_URL | ❌ | wss://jetstream2.us-east.bsky.network/subscribe?… | Override Jetstream endpoint (e.g. point at a self-hosted instance) |
| RUST_LOG | ❌ | colibri_appview=info,rocket=info | Log filter (trace, debug, info, warn, error) |
REST API
All endpoints return JSON. CORS is open (*).
Voice presence
- Each DID can only be active in one voice channel. Joining a different channel triggers a
voice_channel_updatedevent for the previous room before the new join is emitted, so clients see the previous membership cleared. - If a connection sends no non-heartbeat action for more than five minutes, it is marked away and the DID is removed from any active voice call so voice membership stays in sync.
The Jetstream consumer enforces the community's requiresApprovalToJoin flag when indexing messages. Communities with requiresApprovalToJoin = true (default) only accept messages from approved members and the owner; those that set the flag to false allow any DID with a membership declaration to send messages even before approval. If a community flips from open to approval-only while the appview is running, any member lacking an approval record is reverted to pending and a member_pending event is emitted so clients can refresh their UI.
Channels also support an ownerOnly record property (returned as owner_only by the appview). When owner_only is true, only the community owner can create or update messages in that channel.
Messages
GET /api/messages
Paginated message history for a channel, newest first. Each message includes the author profile.
| Parameter | Required | Description |
| --------- | -------- | ------------------------------------------------------------------------- |
| channel | ✅ | Channel rkey |
| limit | ❌ | 1–100, default 50 |
| before | ❌ | ISO 8601 timestamp — returns messages older than this (pagination cursor) |
| all | ❌ | Fetch all messages from the channel |
curl "http://localhost:8000/api/messages?channel=general&limit=20"
curl "http://localhost:8000/api/messages?channel=general&before=2024-03-01T12:00:00Z"
GET /api/message
Fetch a single message by author and record key.
| Parameter | Required | Description |
| --------- | -------- | ----------- |
| author | ✅ | Author DID |
| rkey | ✅ | Record key |
curl "http://localhost:8000/api/message?author=did:plc:xxx&rkey=3mxxx"
POST /api/message/block 🔒
Block a message, hiding it from all future responses. All connected WebSocket clients are immediately notified via a message_deleted event. Requires Authorization: Bearer <INVITE_API_KEY>.
Returns 204 No Content on success, 404 if the message doesn't exist or is already blocked.
| Parameter | Required | Description |
| ------------ | -------- | ------------------ |
| author_did | ✅ | Author DID |
| rkey | ✅ | Message record key |
curl -X POST "http://localhost:8000/api/message/block?author_did=did:plc:xxx&rkey=3mxxx" \
-H "Authorization: Bearer $INVITE_API_KEY"
Authors
GET /api/authors
Retrieve a cached author profile. Falls back to a live ATProto fetch if not yet cached.
| Parameter | Required | Description |
| --------- | -------- | ----------- |
| did | ✅ | Author DID |
curl "http://localhost:8000/api/authors?did=did:plc:xxx"
Response:
{
"did": "did:plc:xxx",
"display_name": "Alice",
"avatar_url": "https://cdn.bsky.app/...",
"banner_url": "https://cdn.bsky.app/...",
"description": "Building cool things",
"handle": "alice.bsky.social",
"status": "Working on something cool",
"emoji": "🚀",
"state": "online"
}
Reactions
GET /api/reactions
Reactions for a single message, grouped by emoji with reactor DIDs.
| Parameter | Required | Description |
| --------- | -------- | ------------------- |
| message | ✅ | Target message rkey |
GET /api/reactions/channel
All reactions in a channel, keyed by target message rkey.
| Parameter | Required | Description |
| --------- | -------- | ------------ |
| channel | ✅ | Channel rkey |
Response:
{
"3mxxx": [{ "emoji": "👍", "count": 3, "reactor_dids": ["did:plc:..."] }],
"3myyy": [{ "emoji": "❤️", "count": 1, "reactor_dids": ["did:plc:..."] }]
}
