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:
2026-03-29 18:54:43 +02:00
parent 983221df33
commit e940afde52
11 changed files with 3144 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
[package]
name = "colony-types"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true }
chrono = { workspace = true }
ts-rs = { workspace = true }

View 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();
}
}