Skip to content

Latest commit

 

History

History
162 lines (125 loc) · 4.21 KB

File metadata and controls

162 lines (125 loc) · 4.21 KB

Getting started

Install

cargo install rustwing-cli

Create a project

rustwing new my_saas
cd my_saas

This generates:

my_saas/
├── Cargo.toml              # workspace root
├── .env.example            # environment template
├── api/                    # web server
│   ├── Cargo.toml
│   ├── src/
│   │   ├── main.rs
│   │   ├── domain/user.rs  # User model
│   │   ├── http/           # routes, handlers, DTOs
│   │   ├── repository/     # SQLx-native database access
│   │   └── services/       # business logic and orchestration
│   └── migrations/         # auto-run on startup
├── worker/                 # DB/LLM-backed worker tick loop
└── frontend/               # your frontend (BYO)

Configure

cp .env.example .env
# Edit DATABASE_URL in .env to point to your Postgres database

Run

rustwing run

Or directly with cargo:

cargo run --bin api

The server:

  1. Connects to Postgres
  2. Runs pending migrations (creates tables automatically)
  3. Starts listening on http://0.0.0.0:3000

Test the API

# Register a user (returns a JWT token + user info)
curl -s -X POST http://localhost:3000/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"demo","email":"demo@test.com","password":"password123"}' | jq .
# The token in the response can be used immediately for authenticated requests.
# If the registration token does not work (e.g., "Invalid or expired token"),
# log in instead — both return the same type of token:

# Login to get a fresh token
curl -s -X POST http://localhost:3000/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"demo@test.com","password":"password123"}' | jq .

# Extract the token using jq
TOKEN=$(curl -s -X POST http://localhost:3000/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"demo@test.com","password":"password123"}' | jq -r '.token')

# List users (authenticated)
curl http://localhost:3000/users/cursor \
  -H "Authorization: Bearer $TOKEN"

# Get user by ID
curl http://localhost:3000/users/<id> \
  -H "Authorization: Bearer $TOKEN"

Generate a resource

rustwing g resource post \
  --fields 'title:string:required:length(1,255)' \
  --fields 'body:string:required'

This creates:

  • Domain model (Post)
  • Create/Update DTOs with validation
  • Service module for validation, pagination limits, and business logic
  • SQLx-native repository glue and explicit CRUD behavior
  • Route handlers with offset and cursor pagination
  • Router registration
  • Database migration
# After generating, create a post (authenticated)
curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"title":"Hello World","body":"My first post!"}'

Generate a scoped resource

For SaaS data, opt into tenant scope explicitly:

rustwing g resource ticket \
  --tenant org_id \
  --fields 'org_id:uuid:required' \
  --fields 'subject:string:required:length(1,255)' \
  --fields 'assigned_member_id:uuid:optional'

This generates routes like /orgs/{org_id}/tickets. The tenant ID comes from the path, so create/update request bodies do not include org_id.

Scopes also work for parent-child resources:

rustwing g resource comment \
  --scope ticket_id \
  --fields 'ticket_id:uuid:required' \
  --fields 'body:string:required'

This generates routes like /tickets/{ticket_id}/comments. Combine scopes when a resource needs both tenant and parent boundaries:

rustwing g resource note \
  --tenant org_id \
  --scope ticket_id \
  --fields 'org_id:uuid:required' \
  --fields 'ticket_id:uuid:required' \
  --fields 'body:string:required'

Run the worker

The generated worker connects to the same database, builds the configured LLM client, and runs a tick loop:

cargo run --bin worker

Set WORKER_TICK_SECONDS to change the polling interval.

Next steps