# Cufflink

Deploy CRUD microservices in seconds. Define your data model in Rust, run `cufflink deploy`, and get a full REST API with PostgreSQL persistence, automatic schema migrations, NATS events, and multi-tenant isolation.

## Architecture

```
┌─────────────┐     ┌──────────────┐     ┌───────────┐
│  Cufflink   │────>│   Platform   │────>│ PostgreSQL│
│  CLI        │     │   (Axum)     │     │           │
└─────────────┘     └──────┬───────┘     └───────────┘
                           │
┌─────────────┐     ┌──────┴───────┐     ┌───────────┐
│  Web Admin  │────>│  Dynamic     │────>│   NATS    │
│  (Next.js)  │     │  CRUD Router │     │ JetStream │
└─────────────┘     └──────────────┘     └───────────┘
```

**SDK** — `#[derive(Table)]` proc macros generate JSON manifests from Rust structs
**CLI** — `cufflink deploy` captures the manifest and POSTs it to the platform
**Platform** — Applies schema migrations, registers CRUD routes, publishes events
**Web Admin** — Dashboard for browsing services, data, schemas, and deployments

## Quick Start

### Prerequisites

- Rust 1.70+
- Docker & Docker Compose

### 1. Start the platform

```bash
docker compose up -d
```

This starts PostgreSQL, NATS, Keycloak, the Cufflink platform, and the web admin.

- Platform API: http://localhost:8080
- Web Admin: http://localhost:3000
- NATS Monitoring: http://localhost:8222

### 2. Define a service

```rust
// examples/todo-service/src/main.rs
use cufflink::prelude::*;

#[derive(Table, Serialize, Deserialize, Clone)]
#[table(name = "todos")]
pub struct Todo {
    #[key] pub id: Uuid,
    pub title: String,
    pub completed: bool,
    #[timestamp] pub created_at: DateTime<Utc>,
    #[timestamp] pub updated_at: DateTime<Utc>,
}

cufflink::service! {
    name: "todo-service",
    tables: [Todo],
}
```

For services with authentication and permissions:

```rust
#[derive(Table, Serialize, Deserialize, Clone)]
#[table(name = "todos", auth_required, permission_area = "todos")]
pub struct Todo { /* ... */ }

cufflink::service! {
    name: "todo-service",
    tables: [Todo],
    authorization: [
        areas: [("todos", ["create", "view", "edit", "delete"])],
        default_roles: [
            ("admin", "Full access", ["todos:*"]),
            ("viewer", "Read-only", ["todos:view"]),
        ],
    ],
}
```

### 3. Deploy

```bash
cd examples/todo-service
cargo run -p cufflink -- deploy
```

### 4. Use the API

```bash
# Create
curl -X POST http://localhost:8080/svc/default/todo-service/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Buy groceries", "completed": false}'

# List (with filtering, sorting, search, pagination)
curl "http://localhost:8080/svc/default/todo-service/todos?sort=-created_at&per_page=10"

# Search
curl "http://localhost:8080/svc/default/todo-service/todos?search=groceries"

# Filter
curl --get http://localhost:8080/svc/default/todo-service/todos \
  --data-urlencode 'filter=[{"n":"completed","f":"=","v":"false"}]'

# Get by ID
curl http://localhost:8080/svc/default/todo-service/todos/{id}

# Update
curl -X PUT http://localhost:8080/svc/default/todo-service/todos/{id} \
  -H "Content-Type: application/json" \
  -d '{"completed": true}'

# Delete
curl -X DELETE http://localhost:8080/svc/default/todo-service/todos/{id}
```

## SDK Reference

### Table Attributes

| Attribute | Description |
|-----------|-------------|
| `#[table(name = "...")]` | SQL table name (required) |
| `#[table(auth_required)]` | Require authentication for CRUD access |
| `#[table(permission_area = "...")]` | Enable CRUD permission enforcement (e.g. `"staff"` checks `staff:view`, `staff:create`, etc.) |
| `#[key]` | Primary key (auto-generated UUID) |
| `#[timestamp]` | Auto-generated timestamp (DEFAULT now()) |
| `#[default("value")]` | Default value |
| `#[references("table.column")]` | Foreign key reference |
| `#[on_delete("cascade")]` | FK delete action: cascade, set_null, restrict, no_action |
| `#[hooks(before_create = "...")]` | CRUD lifecycle hooks — call WASM handlers before/after operations |

### Supported Types

| Rust Type | SQL Type |
|-----------|----------|
| `Uuid` | UUID |
| `String` | TEXT |
| `bool` | BOOLEAN |
| `i32` | INTEGER |
| `i64` | BIGINT |
| `f32` | REAL |
| `f64` | DOUBLE PRECISION |
| `DateTime<Utc>` | TIMESTAMPTZ |
| `NaiveDate` | DATE |
| `Value` | JSONB |
| `Option<T>` | Nullable version of T |

### Foreign Keys

```rust
#[derive(Table, Serialize, Deserialize, Clone)]
#[table(name = "comments")]
pub struct Comment {
    #[key] pub id: Uuid,
    #[references("posts.id")]
    #[on_delete("cascade")]
    pub post_id: Uuid,
    pub body: String,
}
```

## CLI Reference

| Command | Description |
|---------|-------------|
| `cufflink init <name>` | Scaffold a new service project |
| `cufflink deploy` | Deploy the service in the current directory |
| `cufflink deploy --tenant <slug>` | Deploy to a specific tenant |
| `cufflink rollback` | Rollback to previous version |
| `cufflink status` | Show service status and endpoints |
| `cufflink services` | List all deployed services |
| `cufflink login` | Authenticate with the platform |
| `cufflink test` | Run tests (hash-based, only changed) |
| `cufflink seed` | Seed data from a JSON file |
| `cufflink config list` | List service config values |
| `cufflink backup schema` | Show tables, columns, FK dependencies |
| `cufflink backup export` | Export service data to S3-backed NDJSON |
| `cufflink backup restore <file>` | Restore from a backup file |
| `cufflink backup list` | List completed backups |
| `cufflink backup jobs` | List all backup/restore jobs |
| `cufflink install <source>` | Install a cufflink package |
| `cufflink workspace deploy` | Deploy all workspace services |
| `cufflink workspace deploy --parallel` | Deploy all services concurrently |
| `cufflink workspace test` | Run tests for all workspace services |
| `cufflink workspace status` | Show status of all workspace services |
| `cufflink workspace preview create` | Create a preview environment |
| `cufflink workspace preview destroy` | Destroy a preview environment |
| `cufflink tenants create` | Create a new tenant (platform admin) |
| `cufflink tenants create-api-key` | Create platform admin API key |
| `cufflink openapi` | Print OpenAPI spec |
| `cufflink generate-client` | Generate TypeScript client |

See [docs/](docs/) for detailed guides on [workspace](docs/workspace.md), [backup](docs/backup.md), [packages](docs/packages.md), [observability](docs/observability.md), [authentication](docs/authentication.md), and more.

### Configuration

| Env Var | Default | Description |
|---------|---------|-------------|
| `CUFFLINK_API_URL` | `http://localhost:8080` | Platform API URL |
| `CUFFLINK_TENANT` | `default` | Tenant slug |
| `CUFFLINK_KEYCLOAK_URL` | `http://localhost:8180` | Keycloak URL |
| `CUFFLINK_CLIENT_ID` | `cufflink-cli` | Keycloak client ID |

## API Endpoints

### Management API

| Method | Path | Description |
|--------|------|-------------|
| GET | `/health` | Health check |
| POST | `/api/auth/bootstrap` | Create first API key (no auth) |
| POST | `/api/auth/api-keys` | Create API key (requires auth) |
| GET | `/api/auth/me` | Get authenticated user info |
| GET | `/api/services` | List all services |
| GET | `/api/services/{id}` | Get service details |
| DELETE | `/api/services/{id}` | Delete a service |
| POST | `/api/services/deploy` | Deploy a service |
| GET | `/api/services/{id}/deployments` | Deployment history |
| POST | `/api/services/{id}/rollback` | Rollback deployment |
| GET | `/api/services/{id}/openapi.json` | Auto-generated OpenAPI spec |
| POST | `/api/services/{id}/wasm` | Upload WASM module |
| GET | `/api/services/{id}/wasm/status` | WASM runtime status |
| GET | `/api/services/{id}/schema` | Table schema and FK info |
| POST | `/api/services/{id}/backup/export` | Start backup export job |
| POST | `/api/services/{id}/backup/restore` | Start backup restore job |
| GET | `/api/services/{id}/backup/jobs` | List backup jobs |
| GET | `/api/services/{id}/backup/jobs/{jid}` | Get job status |
| POST | `/api/services/{id}/backup/jobs/{jid}/cancel` | Cancel job |
| GET | `/api/services/{id}/backup/jobs/{jid}/download` | Download export |
| POST | `/api/platform/api-keys` | Create platform admin API key |
| GET | `/api/platform/api-keys` | List platform admin API keys |
| DELETE | `/api/platform/api-keys/{id}` | Revoke platform admin API key |

### Dynamic CRUD Routes

| Method | Path | Description |
|--------|------|-------------|
| GET | `/svc/{tenant}/{service}/{table}` | List records |
| POST | `/svc/{tenant}/{service}/{table}` | Create record |
| GET | `/svc/{tenant}/{service}/{table}/{id}` | Get record |
| PUT | `/svc/{tenant}/{service}/{table}/{id}` | Update record |
| DELETE | `/svc/{tenant}/{service}/{table}/{id}` | Delete record |

### Query Parameters

| Parameter | Example | Description |
|-----------|---------|-------------|
| `page` | `?page=2` | Page number |
| `per_page` | `?per_page=25` | Items per page (max 200) |
| `sort` | `?sort=-created_at,title` | Sort (prefix `-` for DESC) |
| `search` | `?search=hello` | Full-text search across text columns |
| `filter` | `?filter=[{"n":"status","f":"=","v":"active"}]` | JSON filter array |
| `select` | `?select=id,title` | Column selection |

### Filter Operators

`=`, `!=`, `>`, `<`, `>=`, `<=`, `LIKE`, `ILIKE`, `IN`, `IS NULL`, `IS NOT NULL`

## Authentication

### Bootstrap (first-time setup)

```bash
curl -X POST http://localhost:8080/api/auth/bootstrap \
  -H "Content-Type: application/json" \
  -d '{"tenant_name": "My Org", "tenant_slug": "my-org"}'
```

### Using API Keys

```bash
curl -H "Authorization: ApiKey ck_..." http://localhost:8080/api/auth/me
```

### CLI Login

```bash
cufflink login
```

## Project Structure

```
cufflink/
├── platform/          # Axum-based platform server
├── worker/            # WASM worker process (scales independently)
├── cli/               # CLI tool (cufflink binary)
├── sdk/               # User-facing SDK crate
├── sdk-macros/        # Proc macros (#[derive(Table)], service!{})
├── types/             # Shared types (ServiceManifest, etc.)
├── web/               # Next.js web admin dashboard
├── examples/
│   ├── todo-service/  # Simple single-table example
│   └── blog-service/  # Multi-table example with FK
├── docker-compose.yml
├── Dockerfile.platform
└── Dockerfile.worker
```

## Development

```bash
# Build everything
cargo build --workspace

# Run tests
cargo test --workspace

# Run platform locally (requires Docker services)
docker compose up -d postgres nats keycloak
cargo run -p cufflink-platform

# Run web admin locally
cd web && npm install && npm run dev
```

## License

MIT
