cedros-data 0.1.2

General-purpose Postgres-backed multi-site content and custom data storage
Documentation
# cedros-data (server crate)

`cedros-data` is a single Rust crate for storing one site's content and custom data in PostgreSQL.

The crate is library-first and includes optional embedded HTTP and CLI modes in the same package.

## Core guarantees

- One required env var only: `POSTGRES_URI`
- No built-in auth logic in the core data layer
- Preserve incoming field names exactly as provided
- JSONB compatibility mode and typed-table mode
- Idempotent schema operations
- Roll-forward schema evolution only (no destructive auto-drop)

## Install and features

```toml
[dependencies]
cedros-data = "0.1"
```

Feature flags:
- default: `cli`, `http`
- `cli`: enables `cedros-data` binary commands
- `http`: enables embedded Axum server
- `cedros-login-profile`: optional compatibility profile for delegating HTTP auth/authz to cedros-login

## Required environment variable

```bash
export POSTGRES_URI='postgres://user:password@localhost:5432/cedros_data'
```

No other environment variable is required by default.

## Quick start

```bash
cd server
cargo run -- migrate
cargo run -- serve --bind-addr 127.0.0.1:8080 --allow-unauthenticated
```

Embedded HTTP now fails closed by default. Use `--allow-unauthenticated` only for local development, or enable the Cedros compatibility profile for authenticated environments.

To allow cross-origin browser admin clients, repeat `--cors-origin` for each trusted origin:

```bash
cargo run -- serve \
  --bind-addr 127.0.0.1:8080 \
  --allow-unauthenticated \
  --cors-origin http://localhost:3000
```

`serve` startup automatically:
- runs base migrations
- creates the default deployment record on fresh databases
- bootstraps default collections and default entries for the active deployment

## Rust API

```rust
use cedros_data::{CedrosData, RegisterSiteRequest};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let store = CedrosData::from_env().await?;
    store.migrate().await?;

    store.register_site(RegisterSiteRequest {
        display_name: "Docs".into(),
        metadata: serde_json::json!({}),
    }).await?;

    Ok(())
}
```

Primary API methods:
- `migrate()`
- `register_site()`
- `register_collection()`
- `register_custom_schema()`
- `upsert_entry()`
- `query_entries()`
- `export_site()`
- `import_site()`
- `verify_contract()`
- `bootstrap_defaults()`

## Base schema and data model

Base migration creates:
- `site`
- `collections`
- `entries`
- `collection_contracts`
- `custom_schema_state`
- `custom_schema_migrations`

The active deployment has:
- a row in `site`
- a dedicated PostgreSQL schema for typed tables and custom schema objects
- default JSONB collections:
  - `pages`
  - `navigation`
  - `site_settings`

Default seeded pages:
- `home`
- `about`
- `contact`
- `docs`
- `blog`
- `privacy-policy`
- `terms-of-service`
- `not-found`

Additional seeded entries:
- `navigation/main`
- `site_settings/global`

Singleton guarantee:
- `cedros-data` stores site metadata in the singleton `site` table
- runtime code always addresses the installation-scoped row with `id = 1`

## Storage modes

- `jsonb` mode:
  - flexible ingestion into shared `entries` table
  - preserves field names exactly
- `typed` mode:
  - stores rows in deployment-specific typed tables
  - keeps JSON payload for compatibility and export/import workflows

## Custom schema registration

`register_custom_schema` supports deployment-scoped registration of:
- custom enum/composite types
- custom tables and columns
- indexes
- unique constraints
- foreign keys

Behavior:
- validates schema diff before apply
- reports additive vs breaking changes
- applies additive roll-forward changes only
- does not auto-drop existing objects
- stores versioned state and generated migration statements

## CLI commands

```bash
cargo run -- migrate
cargo run -- register-site --display-name "Docs"
cargo run -- register-collection --collection-name pages --mode jsonb
cargo run -- register-custom-schema --schema-file ./schema.json
cargo run -- upsert-entry --collection-name pages --entry-key home --payload-file ./home.json
cargo run -- query-entries --collection-name pages --limit 100 --offset 0
cargo run -- verify-contract --collection-name pages --samples-file ./samples.json
cargo run -- export-site --out ./docs-export.json
cargo run -- import-site --input ./docs-export.json --overwrite-contracts
```

`verify-contract` behavior:
- reports additive and breaking changes
- exits non-zero only on breaking changes

## Embedded HTTP API

Run HTTP mode:

```bash
cargo run -- serve --bind-addr 127.0.0.1:8080
```

If you are not enabling the Cedros compatibility profile, add `--allow-unauthenticated` for local-only usage:

```bash
cargo run -- serve --bind-addr 127.0.0.1:8080 --allow-unauthenticated
```

CORS behavior:
- same-origin requests remain unchanged by default
- cross-origin browser clients require one or more explicit `--cors-origin` values
- allowed methods are limited to `GET`, `POST`, `PUT`, and preflight `OPTIONS`
- allowed headers cover the shipped admin client request shape: `Authorization`, `Content-Type`, `Accept`, and `x-cedros-org-id`

Core endpoints:
- `POST /migrate`
- `POST /site`
- `POST /collections`
- `POST /custom-schema`
- `POST /entries/upsert`
- `POST /entries/query`
- `GET /site/export`
- `POST /import`
- `POST /contract/verify`

Admin endpoints:
- `GET /admin/default-pages`
- `POST /admin/bootstrap`
- `GET /admin/collections`
- `POST /admin/collections`
- `GET /admin/pages`
- `PUT /admin/pages/{page_key}`

## Optional Cedros compatibility profile

Core `cedros-data` remains auth-agnostic.  
The Cedros compatibility profile is opt-in and only affects HTTP mode.

```bash
cargo run --features cedros-login-profile -- serve \
  --cedros-login-profile \
  --cedros-login-verify-url http://localhost:3030/v1/auth/verify
```

In profile mode:
- derives the permissions endpoint from the configured verify URL
- authenticates and authorizes each request through the cedros-login permissions endpoint
- uses a shared HTTP client with a 2s connect timeout and 5s total request timeout
- resolves org scope from `x-cedros-org-id` header
- enforces route-level `data:*` permissions before data operations
- fails closed when org scope cannot be resolved
- surfaces cedros-login timeouts/rate limits as gateway-style availability errors

For full route/permission details see [docs/profiles.md](docs/profiles.md).

## Examples

- `examples/generic_blog.rs`
- `examples/cedros_compat_profile.rs`

## Additional docs

- [Schema + migration model]docs/schema.md
- [Profiles and compatibility mode]docs/profiles.md