fix(leptos): correct shared-types timestamps to match JSON wire format
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# API/WS endpoints — set these before building
|
||||
VITE_BE_API_URL=http://localhost:3001
|
||||
VITE_BE_WS_URL=ws://localhost:3001/ws
|
||||
@@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["shared-types", "frontend"]
|
||||
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2026-06-01"
|
||||
components = ["rust-src", "rustc-dev"]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "shared-types"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
@@ -0,0 +1,83 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardStats {
|
||||
pub total_messages: u64,
|
||||
pub total_users: u64,
|
||||
pub total_flagged: u64,
|
||||
pub total_clean: u64,
|
||||
pub total_warned: u64,
|
||||
pub total_error: u64,
|
||||
pub total_voice_recordings: u64,
|
||||
pub total_profiles: u64,
|
||||
pub today_messages: u64,
|
||||
pub today_flagged: u64,
|
||||
pub active_users_24h: u64,
|
||||
pub top_channels: Vec<TopChannel>,
|
||||
pub moderation_overview: ModerationOverview,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TopChannel {
|
||||
pub channel_id: String,
|
||||
pub channel_name: String,
|
||||
pub message_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ModerationOverview {
|
||||
pub pending: u64,
|
||||
pub processing: u64,
|
||||
pub error: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardUser {
|
||||
pub user_id: String,
|
||||
pub username: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avatar_url: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub profile_summary: Option<String>,
|
||||
pub total_messages: u64,
|
||||
pub flagged_count: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_message_at: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trust_score: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardUserDetail {
|
||||
#[serde(flatten)]
|
||||
pub user: DashboardUser,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_analyzed_at: Option<i64>,
|
||||
pub clean_message_streak: u64,
|
||||
pub total_infractions: u64,
|
||||
pub clean_count: u64,
|
||||
pub recent_messages: Vec<super::message::MessageRecord>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardChannel {
|
||||
pub channel_id: String,
|
||||
pub channel_name: String,
|
||||
pub guild_id: String,
|
||||
pub total_messages: u64,
|
||||
pub flagged_count: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_message_at: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub culture_summary: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_analyzed_at: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardChannelDetail {
|
||||
#[serde(flatten)]
|
||||
pub channel: DashboardChannel,
|
||||
pub clean_count: u64,
|
||||
pub recent_messages: Vec<super::message::MessageRecord>,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Guild {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Channel {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel_type: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub parent_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GuildVoiceEntry {
|
||||
pub guild_id: String,
|
||||
pub channel_id: String,
|
||||
pub channel_name: String,
|
||||
pub connected_at: i64,
|
||||
}
|
||||
|
||||
// ── Config ────────────────────────────────────────────────
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub monitor_guild_id: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod message;
|
||||
pub mod guild;
|
||||
pub mod voice;
|
||||
pub mod media;
|
||||
pub mod dashboard;
|
||||
pub mod recording;
|
||||
pub mod ui_state;
|
||||
@@ -0,0 +1,30 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type MediaMode = String; // "music" | "screen"
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MediaItem {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
pub source: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mode: Option<MediaMode>,
|
||||
#[serde(rename = "durationMs")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub duration_ms: Option<u64>,
|
||||
#[serde(rename = "thumbnailUrl")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MediaState {
|
||||
pub playing: bool,
|
||||
#[serde(rename = "musicVolume")]
|
||||
pub music_volume: f64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub current: Option<MediaItem>,
|
||||
pub queue: Vec<MediaItem>,
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// ── AI Status ─────────────────────────────────────────────
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AiStatus {
|
||||
Pending,
|
||||
Processing,
|
||||
Clean,
|
||||
Warn,
|
||||
Flagged,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AiSeverity {
|
||||
None,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AiRecommendedAction {
|
||||
None,
|
||||
Monitor,
|
||||
Warn,
|
||||
Review,
|
||||
Delete,
|
||||
Escalate,
|
||||
}
|
||||
|
||||
// ── Message Metadata ──────────────────────────────────────
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct MessageMetadata {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stickers: Option<Vec<StickerInfo>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub attachments: Option<Vec<AttachmentRef>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub embeds: Option<Vec<EmbedInfo>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<ChannelRef>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StickerInfo {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AttachmentRef {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
#[serde(rename = "contentType")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EmbedInfo {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image: Option<EmbedMedia>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail: Option<EmbedMedia>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EmbedMedia {
|
||||
pub url: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub width: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub height: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ChannelRef {
|
||||
pub channel_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thread_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thread_name: Option<String>,
|
||||
}
|
||||
|
||||
// ── Message Record ────────────────────────────────────────
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MessageRecord {
|
||||
pub id: String,
|
||||
pub guild_id: String,
|
||||
pub channel_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thread_id: Option<String>,
|
||||
pub user_id: String,
|
||||
pub username: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avatar_url: Option<String>,
|
||||
pub content: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub edited_content: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub msg_type: String, // "text" | "edited" | "deleted"
|
||||
pub created_at: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub edited_at: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_status: Option<AiStatus>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_severity: Option<AiSeverity>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_confidence: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_moderation_flags: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_moderation_score: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_analysis: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_categories: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_recommended_action: Option<AiRecommendedAction>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_error: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_analyzed_at: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<MessageMetadata>,
|
||||
}
|
||||
|
||||
// ── Pagination ────────────────────────────────────────────
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PageResult<T> {
|
||||
pub data: Vec<T>,
|
||||
#[serde(rename = "nextCursor")]
|
||||
pub next_cursor: Option<String>,
|
||||
}
|
||||
|
||||
// ── Attachment ────────────────────────────────────────────
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AttachmentRecord {
|
||||
pub id: String,
|
||||
pub message_id: String,
|
||||
pub guild_id: String,
|
||||
pub channel_id: String,
|
||||
pub filename: String,
|
||||
pub size: u64,
|
||||
#[serde(rename = "type")]
|
||||
pub mime_type: String,
|
||||
pub discord_url: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub uploaded_url: Option<String>,
|
||||
pub upload_status: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub upload_error: Option<String>,
|
||||
pub created_at: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub uploaded_at: Option<i64>,
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VoiceRecording {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub username: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avatar_url: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub guild_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel_name: Option<String>,
|
||||
pub filename: String,
|
||||
pub size_bytes: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub download_url: Option<String>,
|
||||
pub upload_status: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub upload_error: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transcription: Option<String>,
|
||||
pub created_at: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub uploaded_at: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VoiceRecordingListResponse {
|
||||
pub items: Vec<VoiceRecording>,
|
||||
#[serde(rename = "nextCursor")]
|
||||
pub next_cursor: Option<String>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Tab {
|
||||
Messages,
|
||||
Live,
|
||||
Dashboard,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UiState {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected_guild: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected_voice_guild: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected_voice_channel: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected_text_guild: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected_text_channel: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_tab: Option<Tab>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_listening: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_streaming: Option<bool>,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::guild::GuildVoiceEntry;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VoiceStatus {
|
||||
pub connected: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_guild_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_channel_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_channel_name: Option<String>,
|
||||
pub connections: Vec<GuildVoiceEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ActiveSpeaker {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
pub user_id: String,
|
||||
pub username: String,
|
||||
pub avatar: String,
|
||||
pub speaking: bool,
|
||||
}
|
||||
Reference in New Issue
Block a user