Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
- `papyrus/services`: service-layer business logic. Prefer new domain modules here instead of growing route handlers.
- `papyrus/schemas`: Pydantic request and response models.
- `papyrus/models`: SQLAlchemy models and metadata exports used by Alembic.
- `papyrus/core`: shared infrastructure such as config, database, exceptions, and security.
- `papyrus/config.py`: validated environment configuration.
- `papyrus/core`: database, exceptions, security and shared infrastructure.
- `alembic`: Alembic environment and migration revisions.
- `tests/api/routes`: endpoint behavior and contract tests.
- `tests/services`: service-layer tests.
Expand Down Expand Up @@ -77,8 +78,7 @@ Local auth testing supports Mailpit for SMTP capture, a dev auth sandbox at `/__

Use `.env.example`, `tests/api/routes/test_auth.py`, and
`tests/integration/test_auth_smoke.py` for current configuration and test entry points.
The auth, Flutter integration, and PowerSync sandbox guides linked by the README
are absent from this checkout; do not assume their contents or invent commands from them.
See `README.md` for current auth/sync routes, local sandbox entry points and test lanes.

For client/server contracts, use the workspace's `papyrus-sync-contract` skill and
`../.agents/skills/papyrus-sync-contract/references/contract-map.md` when developing
Expand Down
95 changes: 70 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,86 @@
# Papyrus Server

FastAPI backend for Papyrus authentication, metadata, file storage, and
PowerSync-backed synchronization.
FastAPI services for authentication, book metadata, media, OPDS relay and
PowerSync-backed synchronization. PostgreSQL stores account-scoped library data;
the pinned PowerSync service replicates it to client SQLite databases.

## Auth And Sync
## Local setup

- Email/password auth uses `POST /v1/auth/register` and
`POST /v1/auth/login`.
- Google auth starts at `GET /v1/auth/oauth/google/start` and finishes through
`POST /v1/auth/exchange-code`.
- PowerSync credentials come from `POST /v1/auth/powersync-token`.
- PowerSync uploads use `POST /v1/sync/powersync-upload`.
Run from this repository using Python 3.12 and Docker Compose:

Read the focused guides:
```bash
uv sync --locked --extra dev
./scripts/bootstrap_local.sh
npm --prefix frontend/dev-pages ci
npm --prefix frontend/dev-pages run dev
```

Bootstrap creates missing development keys, starts the local databases, applies
Alembic migrations and configures PowerSync replication. Read `.env.example` and
`scripts/bootstrap_local.sh` before running it against an existing environment.
The Vite command is optional and serves the development sandbox assets.

## Auth and sync contracts

The API prefix is configured by `API_PREFIX`; `.env.example` uses `/v1`.

| Operation | Route under the configured prefix |
| --- | --- |
| Register / sign in | `POST /auth/register`, `POST /auth/login` |
| Start Google sign-in | `GET /auth/oauth/google/start` |
| Exchange the browser auth code | `POST /auth/exchange-code` |
| Refresh the session | `POST /auth/refresh` |
| Get PowerSync credentials | `POST /auth/powersync-token` |
| Upload offline changes | `POST /sync/powersync-upload` |

- [Flutter auth and PowerSync integration](docs/flutter-auth-integration.md)
- [Authentication testing](docs/auth-testing.md)
- [PowerSync sandbox](docs/powersync-sandbox.md)
- [Managed book acquisition](docs/acquisition-downloads.md)
Contracts are implemented in [auth routes](papyrus/api/routes/auth.py),
[sync routes](papyrus/api/routes/sync.py) and
[PowerSync replication rules](powersync/sync-config.yaml). The server validates
ownership and tombstones and commits mixed upload batches atomically. Media
removal happens after commit. Client token refresh and profile-scoped persistence
remain client responsibilities.

## Local Setup
With `DEBUG=true`, `/__dev/auth-sandbox` and `/__dev/powersync-sandbox` provide
local development tools. They are excluded from the public schema. Mailpit in
Compose captures local SMTP. See [.env.example](.env.example) and
[auth smoke tests](tests/integration/test_auth_smoke.py) for provider setup;
provider-backed tests require explicit SMTP/Google configuration.

Run from `server/`:
[Managed acquisition routes](papyrus/api/routes/acquisition.py) use owner-scoped
endpoints, jobs and rules. Provider adapters, job lifecycle and release tokens
live in [the acquisition service](papyrus/services/acquisition/). The monitor
imports selected media and handles retry/cancellation. [OPDS relay setup](docs/opds-relay.md)
describes catalog transport and origin restrictions.

## Verification

Inside the Papyrus workspace, run:

```bash
uv sync --extra dev
./scripts/bootstrap_local.sh
npm --prefix frontend/dev-pages install
npm --prefix frontend/dev-pages run dev
../tools/papyrus check server
../tools/papyrus test server
```

The bootstrap is idempotent: it creates development keys when missing, starts
the databases, applies Alembic migrations, configures logical replication, and
starts the healthy server and pinned PowerSync services.
The CLI excludes provider `auth_smoke` tests and checks for a separate local test
database. Pytest fixtures drop/recreate tables, including most route tests without
an `integration` marker. Direct pytest requires a distinct `*_test` database;
do not run concurrent suites on it. Read [test fixtures](tests/conftest.py) before
setting test database variables. Run provider smoke tests separately once configured.

## Checks
For schema changes, run `uv run --locked alembic current` and `heads`, apply the
reviewed migrations, then verify `current` and `check`. Keep migrations separate
from structural refactors.

## Public API documentation

The docs repository renders a generated schema rather than maintaining copied
endpoint definitions. Export with deterministic documentation settings:

```bash
uv run pytest --cov --cov-report html
uv run --locked python scripts/export_openapi.py ../docs/_static/openapi.json
uv run --locked python scripts/export_openapi.py ../docs/_static/openapi.json --check
```

Export does not load `.env`, start services or connect to a database. Set the docs
workflow's server revision to the source commit used for the snapshot. The runtime
`/openapi.json` remains the specification for a configured deployment.
2 changes: 1 addition & 1 deletion papyrus/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def create_app() -> FastAPI:
""",
contact={
"name": "Papyrus Support",
"url": "https://github.com/Eoic/Papyrus",
"url": "https://github.com/PapyrusReader/papyrus",
},
license_info={
"name": "AGPL-3.0",
Expand Down
4 changes: 3 additions & 1 deletion papyrus/models/powersync_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class PowerSyncDemoItem(Base):
__tablename__ = "powersync_demo_items"

item_id: Mapped[UUID] = mapped_column(Uuid, primary_key=True, default=uuid4)
owner_user_id: Mapped[UUID] = mapped_column(ForeignKey("users.user_id", ondelete="CASCADE"), nullable=False, index=True)
owner_user_id: Mapped[UUID] = mapped_column(
ForeignKey("users.user_id", ondelete="CASCADE"), nullable=False, index=True
)
title: Mapped[str] = mapped_column(String(255), nullable=False)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
Expand Down
Loading
Loading