animedb-api 0.1.0

GraphQL API for the animedb Rust metadata catalog
Documentation
# animedb

`animedb` is a Rust-first metadata project for local media servers.

> Advisory
> `animedb` stores and normalizes public metadata, but it does not override provider Terms of Service, attribution requirements, authentication rules, rate limits or mature-content restrictions. Before enabling bulk sync for a source, verify that your intended usage is allowed by that source and configure conservative sync budgets.

It has two consumption modes that can be used separately or together:

- local-first: manage a local SQLite catalog with schema, downloads, sync, FTS5 search and JSON source payloads
- remote-first: query normalized metadata from remote providers without forcing client applications to manage persistence or provider-specific normalization

The project also ships a Rust GraphQL API on top of the same crate.

See [REFERENCE.md](/home/kokoro/btw/projects/animedb/REFERENCE.md) for the current library and API reference.

## Workspace

- `crates/animedb` - library crate with SQLite schema management, sync and query APIs
- `crates/animedb-api` - GraphQL API binary built on top of `animedb`

## Current Rust surface

### Local-first

```rust
use animedb::{AnimeDb, SourceName};

let (db, report) = AnimeDb::generate_database_with_report("/tmp/animedb.sqlite")?;
println!("downloaded {} records", report.total_upserted_records);

let updated = AnimeDb::sync_database("/tmp/animedb.sqlite")?;
println!("synced {} records", updated.total_upserted_records);

let monster = db.anime_metadata().by_external_id(SourceName::AniList, "19")?;
println!("{}", monster.name());

let movies = db.movie_metadata().search("spirited away")?;
println!("movie hits: {}", movies.len());
# Ok::<(), animedb::Error>(())
```

### Remote-first

```rust
use animedb::AnimeDb;

let remote = AnimeDb::remote_anilist();
let results = remote.anime_metadata().search("monster")?;
let media = remote.anime_metadata().by_id("19")?;
# Ok::<(), animedb::Error>(())
```

Or choose the provider dynamically:

```rust
use animedb::{AnimeDb, RemoteSource};

let remote = AnimeDb::remote(RemoteSource::Jikan);
let results = remote.movie_metadata().search("paprika")?;
# Ok::<(), animedb::Error>(())
```

## Database design

The SQLite catalog is created and migrated by the crate itself. The current schema includes:

- `media` - canonical normalized records
- `media_alias` - normalized aliases and synonyms
- `media_external_id` - source-specific identifiers
- `source_record` - raw per-source JSON payloads and source update metadata
- `field_provenance` - winner-per-field audit trail for canonical merge decisions
- `sync_state` - persisted sync checkpoints/cursors
- `media_fts` - `FTS5` index for title, alias and synopsis search

The connection is configured with:

- `PRAGMA journal_mode=WAL`
- `PRAGMA synchronous=NORMAL`
- `PRAGMA foreign_keys=ON`
- `PRAGMA busy_timeout=5000`
- `PRAGMA temp_store=MEMORY`

## GraphQL API

The GraphQL API is provided by `animedb-api`.

Run it locally:

```bash
cargo run -p animedb-api
```

Environment variables:

- `ANIMEDB_DATABASE_PATH` - SQLite file path, default `/data/animedb.sqlite`
- `ANIMEDB_LISTEN_ADDR` - bind address, default `0.0.0.0:8080`

Endpoints:

- `GET /` - GraphQL Playground
- `POST /graphql` - GraphQL endpoint
- `GET /healthz` - health check

## Docker

Build and run the Rust GraphQL API:

```bash
docker build -t animedb .
docker run --rm -p 8080:8080 -v $(pwd)/data:/data animedb
```

## Make targets

The repository now includes a `Makefile` for the common workflows:

- `make build` - compile the workspace
- `make test` - run the Rust test suite
- `make crate-real` - run the real-provider crate example against AniList, Jikan and Kitsu
- `make test-real` - run the end-to-end real pipeline: crate example + Docker API + GraphQL checks
- `make docker-build` - build the API image
- `make docker-run` - run the API image locally
- `make debug-api` - run the GraphQL API directly with `cargo run`