use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Claims { pub sub: String, pub exp: usize, } // ── Enums ──────────────────────────────────────────────────────────────────── #[derive(Debug, Deserialize, Serialize, sqlx::Type)] #[sqlx(type_name = "auth_token_kind", rename_all = "snake_case")] pub enum AuthTokenKind { PasswordReset, EmailVerification, RefreshToken, } #[derive(Debug, Deserialize, Serialize, sqlx::Type)] #[sqlx(type_name = "workspace_role", rename_all = "snake_case")] pub enum WorkspaceRole { Owner, Admin, Member, } #[derive(Debug, Deserialize, Serialize, sqlx::Type)] #[sqlx(type_name = "conversation_kind", rename_all = "snake_case")] pub enum ConversationKind { Dm, Group, Channel, } // ── Tables ─────────────────────────────────────────────────────────────────── #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct User { pub id: Uuid, pub email: String, pub password_hash: String, pub username: String, pub display_name: String, pub avatar_url: Option, pub bio: Option, pub email_verified: bool, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub deleted_at: Option>, } #[derive(Debug, Serialize)] pub struct UserResponse { pub id: Uuid, pub email: String, pub username: String, pub display_name: String, pub avatar_url: Option, pub bio: Option, pub email_verified: bool, pub created_at: chrono::DateTime, } impl From for UserResponse { fn from(u: User) -> Self { Self { id: u.id, email: u.email, username: u.username, display_name: u.display_name, avatar_url: u.avatar_url, bio: u.bio, email_verified: u.email_verified, created_at: u.created_at, } } } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct AuthToken { pub id: Uuid, pub user_id: Uuid, pub token_hash: String, pub kind: AuthTokenKind, pub expires_at: chrono::DateTime, pub used_at: Option>, pub created_at: chrono::DateTime, } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct Workspace { pub id: Uuid, pub name: String, pub slug: String, pub owner_id: Uuid, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub deleted_at: Option>, } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct WorkspaceMember { pub workspace_id: Uuid, pub user_id: Uuid, pub role: WorkspaceRole, pub display_name: Option, pub avatar_url: Option, pub joined_at: chrono::DateTime, } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct WorkspaceInvitation { pub id: Uuid, pub workspace_id: Uuid, pub created_by: Uuid, pub token: String, pub max_uses: Option, pub use_count: i32, pub expires_at: Option>, pub created_at: chrono::DateTime, } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct Conversation { pub id: Uuid, pub workspace_id: Option, pub kind: ConversationKind, pub name: Option, pub is_public: bool, pub created_by: Uuid, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub deleted_at: Option>, } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct ConversationMember { pub conversation_id: Uuid, pub user_id: Uuid, pub joined_at: chrono::DateTime, } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct Message { pub id: Uuid, pub conversation_id: Uuid, pub user_id: Uuid, pub content: String, /// Immutable sort key within the conversation. Ordering follows this, never /// `created_at` — that is NOW(), i.e. transaction start, so it is neither /// unique nor commit-ordered. pub seq: i64, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub deleted_at: Option>, } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct Attachment { pub id: Uuid, pub message_id: Uuid, pub url: String, pub filename: String, pub mime_type: String, pub size_bytes: i64, pub created_at: chrono::DateTime, } #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct Reaction { pub id: Uuid, pub message_id: Uuid, pub user_id: Uuid, pub emoji: String, pub created_at: chrono::DateTime, }