···3232 run: nix flake check --refresh
33333434 - name: Build Rust binary
3535- run: nix build .#slice
3535+ run: nix build .#slices
36363737 - name: Build Docker image
3838- run: nix build .#sliceImg
3838+ run: nix build .#slicesImg
39394040 - name: Load Docker image
4141 run: docker load < result
···5858 echo "Checking PostgreSQL status..."
5959 docker ps | grep postgres-test
6060 docker logs postgres-test
6161-6161+6262 # Test PostgreSQL connection
6363 echo "Testing PostgreSQL connection..."
6464 docker exec postgres-test psql -U slice -d slice_test -c "SELECT version();"
6565-6565+6666 # Start the Slice container with PostgreSQL backend
6767 echo "Starting Slice container..."
6868 docker run -d --name slice-test -p 8080:8080 \
···7474 # Wait for the service to start and show logs
7575 echo "Waiting for service to start..."
7676 sleep 10
7777-7777+7878 echo "Checking all containers (including exited)..."
7979 docker ps -a | grep -E "(slice-test|postgres-test)" || true
8080-8080+8181 echo "Checking Slice container status..."
8282 docker ps | grep slice-test || echo "Slice container not running!"
8383-8383+8484 echo "Slice container logs:"
8585 docker logs slice-test 2>&1 || echo "Failed to get logs"
8686-8686+8787 echo "Checking if slice container exited:"
8888 docker inspect slice-test --format='{{.State.Status}}' || echo "Container not found"
8989 docker inspect slice-test --format='{{.State.ExitCode}}' || echo "No exit code"
9090-9090+9191 # Additional wait
9292 sleep 10
9393-9393+9494 echo "Final Slice container logs:"
9595 docker logs slice-test 2>&1 || echo "Failed to get final logs"
9696
+4-1
api/.env.example
···11# Database configuration
22-DATABASE_URL=postgresql://slice:slice@localhost:5432/slice
22+DATABASE_URL=postgresql://slices:slices@localhost:5432/slices
3344# Server configuration
55PORT=3000
···991010# AT Protocol relay endpoint for syncing data
1111RELAY_ENDPOINT=https://relay1.us-west.bsky.network
1212+1313+# System slice URI
1414+SYSTEM_SLICE_URI=at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhd4jhrd2z
12151316# Logging level
1417RUST_LOG=debug
+103-29
api/CLAUDE.md
···11-### OAuth 2.0 Endpoints with AIP
11+# CLAUDE.md
22+33+This file provides guidance to Claude Code (claude.ai/code) when working with
44+code in this repository.
55+66+## Project Overview
77+88+Slices is an AT Protocol (ATProto) indexing and querying service that allows
99+developers to create custom slices (subsets) of the ATProto network data. It
1010+indexes records from the Bluesky/ATProto network via Jetstream, validates them
1111+against Lexicon schemas, and provides flexible querying capabilities through an
1212+XRPC API.
21333-The AIP server implements the following OAuth 2.0 endpoints:
1414+## Development Setup
41555-- `GET ${AIP_BASE_URL}/oauth/authorize` - Authorization endpoint for OAuth flows
66-- `POST ${AIP_BASE_URL}/oauth/token` - Token endpoint for exchanging
77- authorization codes for access tokens
88-- `POST ${AIP_BASE_URL}/oauth/par` - Pushed Authorization Request endpoint
99- (RFC 9126)
1010-- `POST ${AIP_BASE_URL}/oauth/clients/register` - Dynamic Client Registration
1111- endpoint (RFC 7591)
1212-- `GET ${AIP_BASE_URL}/oauth/atp/callback` - ATProtocol OAuth callback handler
1313-- `GET ${AIP_BASE_URL}/.well-known/oauth-authorization-server` - OAuth server
1414- metadata discovery (RFC 8414)
1515-- `GET ${AIP_BASE_URL}/.well-known/oauth-protected-resource` - Protected
1616- resource metadata
1717-- `GET ${AIP_BASE_URL}/.well-known/jwks.json` - JSON Web Key Set for token
1818- verification
1919-- `GET ${AIP_BASE_URL}/oauth/userinfo` - introspection endpoint returning claims
2020- info where sub is the user's atproto did
2121-- `GET ${AIP_BASE_URL}/api/atproto/session` - returns atproto session data
1616+### Database Connection
1717+1818+The application uses PostgreSQL. You can connect to the database using:
1919+2020+1. **Docker Compose** (recommended for local development):
2121+ ```bash
2222+ docker-compose up postgres
2323+ ```
2424+ This starts PostgreSQL on port 5432 with:
2525+ - Database: `slices`
2626+ - User: `slices`
2727+ - Password: `slices`
2828+2929+2. **Environment Variables** (.env file): Create an `api/.env` file (copy from
3030+ `api/.env.example`):
3131+ ```
3232+ DATABASE_URL=postgresql://slices:slices@localhost:5432/slices
3333+ SYSTEM_SLICE_URI=at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhd4jhrd2z
3434+ AUTH_BASE_URL=http://localhost:8081
3535+ RELAY_ENDPOINT=https://relay1.us-west.bsky.network
3636+ ```
3737+3838+## Common Development Commands
3939+4040+```bash
4141+# Type checking and validation
4242+cargo check
4343+4444+# Run development server
4545+cargo run
4646+4747+# Run sync script
4848+./scripts/sync.sh http://localhost:3000 <token> # Local dev
4949+./scripts/sync.sh https://api.slices.network <token> # Production
5050+5151+# Database setup
5252+sqlx database create
5353+5454+# Database migrations
5555+sqlx migrate run
5656+sqlx migrate add <migration_name>
5757+5858+# sqlx query cache (run after changing queries)
5959+cargo sqlx prepare
6060+6161+# Build for production
6262+cargo build --release
6363+```
6464+6565+## High-Level Architecture
6666+6767+### Data Flow
6868+6969+1. **Real-time Indexing:** Jetstream → JetstreamConsumer → Lexicon Validation →
7070+ Database → Index
7171+2. **XRPC Query:** HTTP Request → OAuth Verification → Dynamic Handler →
7272+ Database Query → Response
7373+3. **Background Sync:** Trigger → Job Queue → SyncService → ATProto Relay →
7474+ Validation → Database
7575+7676+### Key Architectural Decisions
7777+7878+- **Single-table design** for records using PostgreSQL with JSONB for
7979+ flexibility across arbitrary lexicons
8080+- **Dynamic XRPC endpoint generation** - routes like
8181+ `/{collection}.createRecord` are generated at runtime
8282+- **Dual indexing strategy** - real-time via Jetstream and bulk sync via
8383+ background jobs
8484+- **Cursor-based pagination** using `base64(sort_value::indexed_at::cid)` for
8585+ stable pagination
8686+- **OAuth DPoP authentication** integrated with AIP server for ATProto
8787+ authentication
8888+8989+### Module Organization
9090+9191+- `src/api/` - HTTP handlers for XRPC endpoints (actors, records, oauth, sync,
9292+ etc.)
9393+- `src/main.rs` - Application entry point, server setup, Jetstream startup
9494+- `src/database.rs` - All database operations, query building, cursor pagination
9595+- `src/jetstream.rs` - Real-time event processing from ATProto firehose
9696+- `src/sync.rs` - Bulk synchronization operations with ATProto relay
9797+- `src/auth.rs` - OAuth verification and DPoP authentication setup
9898+- `src/errors.rs` - Error type definitions (reference for new errors)
229923100## Error Handling
2410125102All error strings must use this format:
261032727- error-aip-<domain>-<number> <message>: <details>
104104+ error-slices-<domain>-<number> <message>: <details>
2810529106Example errors:
301073131-- error-slice-resolve-1 Multiple DIDs resolved for method
3232-- error-slice-plc-1 HTTP request failed: https://google.com/ Not Found
3333-- error-slice-key-1 Error decoding key: invalid
108108+- error-slices-resolve-1 Multiple DIDs resolved for method
109109+- error-slices-plc-1 HTTP request failed: https://google.com/ Not Found
110110+- error-slices-key-1 Error decoding key: invalid
3411135112Errors should be represented as enums using the `thiserror` library when
36113possible using `src/errors.rs` as a reference and example.
···4812549126## HTTP Handler Organization
501275151-HTTP handlers should be organized as Rust source files in the `src/http`
5252-directory and should have the `handler_` prefix. Each handler should have it's
5353-own request and response types and helper functionality.
5454-5555-Example handler: `handler_index.rs`
128128+HTTP handlers should be organized as Rust source files in the `src/api`
129129+directory. Each handler should have its own request and response types and
130130+helper functionality.
5613157132- After updating, run `cargo check` to fix errors and warnings
58133- Don't use dead code, if it's not used remove it
5959-- Ise htmx and hyperscript when possible, if not javascript in script tag is ok