Metadata-Version: 2.5
Name: coasti-planner
Version: 0.2.2
Summary: Backend service for the Coasti Planning Framework.
Author-email: "Sebastian B. Mohr" <sebastian@mohrenclan.de>, "F. Paul Spitzner" <paul.spitzner@linkfish.eu>
Requires-Python: >=3.13
Requires-Dist: alembic>=1.20.0
Requires-Dist: argon2-cffi>=25.1.0
Requires-Dist: eyconf[pydantic]>=0.8.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: ruamel-yaml>=0.19.1
Requires-Dist: sqlalchemy>=2.0.0
Requires-Dist: typer>=0.27.3
Requires-Dist: uvicorn[standard]>=0.34.0
Description-Content-Type: text/markdown

# Coasti Planning Framework — Backend

The backend is a Python 3.13 FastAPI application managed with
[uv](https://docs.astral.sh/uv/). It serves the framework and content-package
assets from local Vite build directories.

## Setup

Run this command from the repository root:

```bash
uv sync --directory backend --locked
```

The repository root also provides a Nix/direnv development shell that installs
the required tools automatically. See the root [README](../README.md).

## Build the frontend distributions

The backend does not build the frontend itself. Build the framework and the
example extension before running the backend in local distribution mode; the
framework build is also bundled into the package when building a wheel (see
[Package the backend](#package-the-backend)).

### Framework distribution

Install the frontend workspace dependencies and build the planner framework:

```bash
pnpm --dir frontend install --frozen-lockfile
pnpm --dir frontend build
```

This creates the framework distribution in `frontend/app/dist`.

### Example extension distribution

Install the extension dependencies and build the example content package:

```bash
pnpm --dir content_example install --frozen-lockfile
pnpm --dir content_example build
```

This creates the extension distribution in `content_example/dist`. The build
includes the generated `manifest.json` and Module Federation entry point.

## Configuration

The server accepts a `server` command and an optional YAML configuration path.
The legacy form with only the configuration path remains supported.

The example configuration is available at
[config.example.yml](../content_example/config.example.yml):

```yaml
backend:
  host: 127.0.0.1
  port: 8000
framework:
  dist_folder: ../frontend/planner/dist
extension:
  dist_folder: ./dist
```

Configuration is loaded by [EYConf](https://eyconf.readthedocs.io/) using its
Pydantic validation backend. The configuration is represented by three pairs of
schema/runtime types in `coasti_planner.config`:

- `BackendConfigSchema` / `BackendConfig` define the listening host and port.
- `AssetSourceSchema` / `AssetSource` define a local distribution or remote
  development server.
- `ServerConfigSchema` / `ServerConfig` combine the backend, framework, and
  extension settings.

Each asset source defines a local `dist_folder`. Relative paths are resolved
relative to the configuration file, not relative to the current working
directory.

## Meta database

The framework owns a meta database, separate from the content-package databases.
It will hold users, permissions, and application metadata; the schema is
intentionally empty until the first models land. Configure a file-backed
SQLAlchemy connection string, top level next to `host` and `port`; relative
SQLite paths resolve against the configuration file:

```yaml
database: sqlite:///./coasti_planner.sqlite
```

Schema changes are versioned with Alembic, and the backend applies pending
migrations on startup, so there is no separate migration step before starting
the server. The migration scripts live in `src/coasti_planner/migrations` and
ship with the package. Alembic logs through the application logger.

Authoring or inspecting migrations still uses the CLI from `backend/`. Because
`alembic.ini` holds no connection URL, point the CLI at a database with
`COASTI_DATABASE_URL` or the app config named by `EYCONF_CONFIG_FILE`:

```bash
export COASTI_DATABASE_URL=sqlite:///./coasti_planner.sqlite
uv run alembic revision --autogenerate -m "add user roles"
uv run alembic check                              # fail when models and migrations diverge
```

## Run the backend

From the repository root, after building both distributions:

```bash
uv run --directory backend coasti-planner ../content_example/config.example.yml
```

### Run the full stack in development mode

Development mode does not require either frontend distribution directory. Run
the backend from the backend project directory:

```bash
cd backend
uv run backend server --dev
```

The backend uses Uvicorn autoreload and the example configuration by default.
Run the Vite development servers in separate terminals:

```bash
pnpm --dir frontend dev
pnpm --dir content_example dev
```

The frontend development server proxies API requests to the autoreloading
backend, while the frontend and extension servers reload their own source files.

## HTTP endpoints

- `/` serves the framework `index.html` and its root-relative assets.
- `/api_v1/framework/{path}` serves framework distribution files.
- `/api_v1/extension/{path}` serves extension distribution files.
- `/api_v1/extension/manifest.json` serves the extension's generated manifest.

The manifest is read from the extension distribution. It is not duplicated in
the backend.

## Package the backend

Building the backend bundles the framework build into the wheel:

```bash
pnpm --dir frontend build        # produces frontend/app/dist
uv build --directory backend     # bundles it into the package
```

The `CustomBuildHook` in `backend/hatch_build.py` copies `frontend/app/dist`
into `src/coasti_planner/assets` before packaging, and hatchling includes that
directory in both the wheel and the sdist through the `artifacts` entries in
`pyproject.toml`. The installed backend serves those bundled assets by default,
so it needs no separate framework distribution. Wheel and sdist builds fail with
a clear error when the framework has not been built yet. Editable installs
(`uv sync`) are allowed to skip the bundle because development serves the SPA
from the Vite dev server; they still pick it up when the build exists.
