- Stack: Bun, Hono, Drizzle ORM, Zod, Handlebars, Pino - Models: ticket, queue, transaction, scrip, template, custom_field, user, lifecycle - Scrip engine: prepare/commit two-phase dispatch, template rendering, mock actions - Lifecycle validator: state machine transition validation with wildcard support - Routes: health, tickets (full CRUD + preview + transactions), queues, scrips, custom-fields, lifecycles - Middleware: Pino logging, error handler - Database: Drizzle ORM schema + initial migration (10 tables) - Type-check: passes (tsc --noEmit, zero errors)
203 lines
9.1 KiB
Markdown
203 lines
9.1 KiB
Markdown
Scaffold the Tessera Rust project workspace at /home/gjermund/projects/tessera.
|
|
|
|
## What to Create
|
|
|
|
### 1. Workspace Cargo.toml
|
|
At /home/gjermund/projects/tessera/Cargo.toml:
|
|
- [workspace] with members: crates/tessera-core, crates/tessera-api
|
|
- [workspace.dependencies] with versions for:
|
|
- axum 0.8.x
|
|
- tokio 1.x (full features)
|
|
- serde 1.x (derive feature)
|
|
- serde_json 1.x
|
|
- sqlx 0.8.x (runtime-tokio, tls-rustls, postgres features)
|
|
- uuid 1.x (v4, serde features)
|
|
- chrono 0.4.x (serde feature)
|
|
- tracing 0.1.x, tracing-subscriber 0.3.x
|
|
- tower 0.5.x, tower-http 0.6.x (cors feature)
|
|
- tonic 0.12.x
|
|
- prost 0.13.x
|
|
- anyhow 1.x
|
|
- thiserror 2.x
|
|
- lettre 0.11.x (rustls-tls feature)
|
|
- tera 1.x
|
|
- figment 0.10.x (toml, env features)
|
|
- dotenvy 0.15.x
|
|
- clap 4.x (derive feature)
|
|
|
|
### 2. tessera-core crate
|
|
At /home/gjermund/projects/tessera/crates/tessera-core/Cargo.toml:
|
|
- name = "tessera-core"
|
|
- Dependencies: serde, serde_json, sqlx, uuid, chrono, anyhow, thiserror, tracing, tera, lettre
|
|
- [lib] crate-type = ["lib"]
|
|
|
|
At /home/gjermund/projects/tessera/crates/tessera-core/src/lib.rs:
|
|
- Module declarations: pub mod models; pub mod scrip; pub mod lifecycle; pub mod query; pub mod db;
|
|
- Re-export key types
|
|
|
|
Create these source files (with proper module structure):
|
|
|
|
#### crates/tessera-core/src/models/mod.rs
|
|
- Module declarations: mod ticket; mod queue; mod transaction; mod scrip; mod template; mod custom_field; mod user;
|
|
- Re-exports
|
|
|
|
#### crates/tessera-core/src/models/ticket.rs
|
|
- Ticket struct with fields: id (Uuid), subject (String), queue_id (Uuid), status (String), owner_id (Option<Uuid>), creator_id (Uuid), created_at (DateTime<Utc>), updated_at (DateTime<Utc>), started_at (Option<DateTime<Utc>>), resolved_at (Option<DateTime<Utc>>)
|
|
|
|
#### crates/tessera-core/src/models/queue.rs
|
|
- Queue struct: id (Uuid), name (String), description (Option<String>), lifecycle_id (Option<Uuid>), created_at (DateTime<Utc>)
|
|
|
|
#### crates/tessera-core/src/models/transaction.rs
|
|
- Transaction struct: id (Uuid), ticket_id (Uuid), transaction_type (String), field (Option<String>), old_value (Option<String>), new_value (Option<String>), data (Option<serde_json::Value>), creator_id (Uuid), created_at (DateTime<Utc>)
|
|
|
|
#### crates/tessera-core/src/models/scrip.rs
|
|
- Scrip struct: id (Uuid), queue_id (Option<Uuid>), name (String), description (Option<String>), condition_type (String), condition_config (serde_json::Value), action_type (String), action_config (serde_json::Value), template_id (Option<Uuid>), stage (String), sort_order (i32), disabled (bool), created_at (DateTime<Utc>)
|
|
- ScripStage enum: TransactionCreate, TransactionBatch
|
|
|
|
#### crates/tessera-core/src/models/template.rs
|
|
- Template struct: id (Uuid), name (String), queue_id (Option<Uuid>), subject_template (String), body_template (String), created_at (DateTime<Utc>)
|
|
|
|
#### crates/tessera-core/src/models/custom_field.rs
|
|
- CustomField struct: id (Uuid), name (String), field_type (String), values (Option<serde_json::Value>), max_values (i32), pattern (Option<String>), created_at (DateTime<Utc>)
|
|
- QueueCustomField struct: id (Uuid), queue_id (Uuid), custom_field_id (Uuid), sort_order (i32)
|
|
- CustomFieldValue struct: id (Uuid), custom_field_id (Uuid), ticket_id (Uuid), value (String), created_at (DateTime<Utc>)
|
|
|
|
#### crates/tessera-core/src/models/user.rs
|
|
- User struct: id (Uuid), username (String), email (Option<String>), created_at (DateTime<Utc>)
|
|
|
|
#### crates/tessera-core/src/scrip/mod.rs
|
|
- Module declarations: mod engine; mod conditions; mod actions; mod templates;
|
|
- Re-export ScripEngine, PreparedScrip
|
|
|
|
#### crates/tessera-core/src/scrip/engine.rs
|
|
- ScripEngine struct with methods:
|
|
- new(pool: PgPool)
|
|
- async fn prepare(&self, ticket_id: Uuid, transactions: &[Transaction]) -> Result<Vec<PreparedScrip>>
|
|
- Load matching scrips from DB (global + queue-specific, filtered by transaction types matching condition_type, sorted by sort_order)
|
|
- For each: evaluate condition → if matches, build PreparedScrip with template substitution
|
|
- async fn commit(&self, prepared: Vec<PreparedScrip>) -> Result<Vec<ScripResult>>
|
|
- Execute each action, record results
|
|
- PreparedScrip struct: scrip_id, scrip_name, action_type, action_payload (serde_json::Value), dry_run bool
|
|
- ScripResult struct: scrip_id, success bool, message String
|
|
|
|
#### crates/tessera-core/src/scrip/conditions.rs
|
|
- ConditionEvaluator trait with fn evaluate(&self, ticket: &Ticket, transactions: &[Transaction]) -> bool
|
|
- OnCreate condition (true if any transaction is type "Create")
|
|
- OnStatusChange condition (true if any transaction is type "StatusChange")
|
|
- OnResolve condition (true if any transaction changes status to a "resolved" lifecycle state)
|
|
|
|
#### crates/tessera-core/src/scrip/actions.rs
|
|
- ActionExecutor trait with async fn execute(&self, payload: &serde_json::Value) -> Result<String>
|
|
- SendEmail action (placeholder — logs the email it would send)
|
|
- Webhook action (placeholder — logs the webhook it would fire)
|
|
- SetCustomField action (placeholder — logs the CF it would set)
|
|
|
|
#### crates/tessera-core/src/scrip/templates.rs
|
|
- TemplateRenderer struct that uses Tera
|
|
- async fn render(&self, template: &Template, context: &serde_json::Value) -> Result<(String, String)> — returns (subject, body)
|
|
|
|
#### crates/tessera-core/src/lifecycle/mod.rs
|
|
- Lifecycle struct: id (Uuid), name (String), definition (serde_json::Value)
|
|
- LifecycleValidator struct with methods:
|
|
- fn validate_transition(&self, lifecycle_def: &serde_json::Value, from: &str, to: &str) -> Result<()>
|
|
- fn is_valid_status(&self, lifecycle_def: &serde_json::Value, status: &str) -> bool
|
|
|
|
#### crates/tessera-core/src/query/mod.rs
|
|
- Placeholder module with comment: "TicketSQL query builder — post-MVP"
|
|
|
|
#### crates/tessera-core/src/db/mod.rs
|
|
- pub mod migrations;
|
|
- Pub async fn run_migrations(pool: &PgPool) -> Result<()>
|
|
|
|
#### crates/tessera-core/src/db/migrations.rs
|
|
- Function signatures for migration runner (placeholder — actual migrations in sql files)
|
|
|
|
### 3. tessera-api crate
|
|
At /home/gjermund/projects/tessera/crates/tessera-api/Cargo.toml:
|
|
- name = "tessera-api"
|
|
- Dependencies: tessera-core (path), axum, tokio, serde, serde_json, uuid, anyhow, tracing, tracing-subscriber, tower-http, clap, figment, dotenvy
|
|
- [[bin]] name = "tessera-api"
|
|
|
|
At /home/gjermund/projects/tessera/crates/tessera-api/src/main.rs:
|
|
- Clap CLI with subcommands: serve, migrate
|
|
- serve: start axum HTTP server
|
|
- migrate: run database migrations
|
|
- tracing_subscriber init with JSON formatting
|
|
- Load config from TESSERA_CONFIG env or tessera.toml
|
|
|
|
Create source files:
|
|
|
|
#### crates/tessera-api/src/routes/mod.rs
|
|
- Module declarations: mod health; mod tickets; mod queues; mod scrips; mod custom_fields; mod lifecycles;
|
|
|
|
#### crates/tessera-api/src/routes/health.rs
|
|
- GET /health → 200 with {"status": "ok", "version": "0.1.0"}
|
|
|
|
#### crates/tessera-api/src/routes/tickets.rs
|
|
- Router function returning axum Router with placeholder handlers:
|
|
- GET /api/tickets
|
|
- POST /api/tickets
|
|
- GET /api/tickets/:id
|
|
- PATCH /api/tickets/:id
|
|
- POST /api/tickets/:id/preview
|
|
- GET /api/tickets/:id/transactions
|
|
- POST /api/tickets/:id/comment
|
|
- Each handler returns 501 NotImplemented with a descriptive message
|
|
|
|
#### crates/tessera-api/src/routes/queues.rs
|
|
- GET /api/queues → 501
|
|
- POST /api/queues → 501
|
|
|
|
#### crates/tessera-api/src/routes/scrips.rs
|
|
- GET /api/scrips → 501
|
|
- POST /api/scrips → 501
|
|
- GET /api/scrips/:id → 501
|
|
- PATCH /api/scrips/:id → 501
|
|
|
|
#### crates/tessera-api/src/routes/custom_fields.rs
|
|
- GET /api/custom-fields → 501
|
|
- POST /api/custom-fields → 501
|
|
|
|
#### crates/tessera-api/src/routes/lifecycles.rs
|
|
- GET /api/lifecycles → 501
|
|
- POST /api/lifecycles → 501
|
|
|
|
#### crates/tessera-api/src/config.rs
|
|
- AppConfig struct: database_url (String), server_host (String), server_port (u16)
|
|
- impl from figment
|
|
|
|
### 4. SQL Migrations
|
|
At /home/gjermund/projects/tessera/migrations/0001_initial_schema.sql:
|
|
- All CREATE TABLE statements from the architecture document (tickets, queues, lifecycles, transactions, scrips, templates, custom_fields, queue_custom_fields, custom_field_values, users)
|
|
- CREATE INDEX statements on: tickets(queue_id), tickets(status), transactions(ticket_id), transactions(created_at), scrips(queue_id), custom_field_values(ticket_id), custom_field_values(custom_field_id)
|
|
|
|
### 5. Configuration
|
|
At /home/gjermund/projects/tessera/tessera.example.toml:
|
|
```toml
|
|
[database]
|
|
url = "postgres://tessera:tessera@localhost:5432/tessera"
|
|
|
|
[server]
|
|
host = "127.0.0.1"
|
|
port = 8080
|
|
```
|
|
|
|
### 6. .gitignore
|
|
Standard Rust .gitignore: target/, .env, *.log
|
|
|
|
## Rules
|
|
- Use `cargo init` where appropriate, or create files manually
|
|
- All code must compile: run `cargo check --workspace` after creating everything
|
|
- Fix any compilation errors
|
|
- Use `cargo fmt` and `cargo clippy` on the workspace
|
|
- Do NOT create a separate Cargo.lock — it will be generated
|
|
- The workspace Cargo.toml must NOT have a [package] section
|
|
|
|
## Verification
|
|
After creating all files, run:
|
|
1. `cargo check --workspace` — must succeed
|
|
2. `cargo fmt --check` — must pass
|
|
3. `cargo clippy --workspace` — should pass or have only minor warnings
|
|
|
|
If any verification fails, fix the issues and re-run until clean.
|