S1: Colony backend skeleton — Axum + SQLite, channels + messages CRUD
Monorepo structure:
- crates/colony-types: API types (serde + ts-rs), separate from DB models
- crates/colony: Axum server, SQLite via sqlx, migrations
Working endpoints:
- GET /api/health
- GET/POST /api/channels
- GET /api/channels/{id}
- GET /api/channels/{id}/messages (?since=, ?type=, ?user_id=)
- POST /api/channels/{id}/messages (with type + metadata)
Data model includes:
- seq monotonic ordering, soft delete, same-channel reply constraint
- Seeded users (benji, neeraj) and #general channel
Also: codex-review skill, .gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
119
crates/colony-types/src/lib.rs
Normal file
119
crates/colony-types/src/lib.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
// ── API response types (wire format → TypeScript via ts-rs) ──
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct User {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub role: UserRole,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum UserRole {
|
||||
Ape,
|
||||
Agent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct Channel {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub created_by: Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct Message {
|
||||
pub id: Uuid,
|
||||
pub seq: i64,
|
||||
pub channel_id: Uuid,
|
||||
pub user: User,
|
||||
pub r#type: MessageType,
|
||||
pub content: String,
|
||||
#[ts(optional)]
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
#[ts(optional)]
|
||||
pub reply_to: Option<Uuid>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
#[ts(optional)]
|
||||
pub updated_at: Option<DateTime<Utc>>,
|
||||
#[ts(optional)]
|
||||
pub deleted_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MessageType {
|
||||
Text,
|
||||
Code,
|
||||
Result,
|
||||
Error,
|
||||
Plan,
|
||||
}
|
||||
|
||||
// ── Request types ──
|
||||
|
||||
#[derive(Debug, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct CreateChannel {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct PostMessage {
|
||||
pub content: String,
|
||||
pub r#type: MessageType,
|
||||
#[ts(optional)]
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
#[ts(optional)]
|
||||
pub reply_to: Option<Uuid>,
|
||||
}
|
||||
|
||||
// ── WebSocket event types ──
|
||||
|
||||
#[derive(Debug, Clone, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "event", content = "data")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WsEvent {
|
||||
Message(Message),
|
||||
Edit(Message),
|
||||
Delete { id: Uuid },
|
||||
}
|
||||
|
||||
// ── Query params ──
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MessageQuery {
|
||||
pub since: Option<DateTime<Utc>>,
|
||||
pub r#type: Option<MessageType>,
|
||||
pub user_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn export_ts_types() {
|
||||
// This test generates TypeScript bindings.
|
||||
// Run: cargo test -p colony-types export_ts_types
|
||||
// Files go to colony-types/bindings/
|
||||
let _ = User::export_all();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user