ui stuff
This commit is contained in:
parent
0d79b42072
commit
d1a68c635c
5 changed files with 384 additions and 94 deletions
125
src/handlers.rs
125
src/handlers.rs
|
|
@ -11,7 +11,7 @@ use uuid::Uuid;
|
|||
use crate::{
|
||||
AppState,
|
||||
auth::{self, ApiError, AuthUser},
|
||||
db, voice,
|
||||
chat, db, voice,
|
||||
};
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
|
|
@ -22,17 +22,27 @@ pub fn routes() -> Router<AppState> {
|
|||
.route("/auth/logout", post(auth_logout))
|
||||
.route("/me", get(me))
|
||||
.route("/dms", get(list_dm_conversations))
|
||||
.route("/dms/{other_user_id}/messages", get(list_dm_messages).post(send_dm_message))
|
||||
.route(
|
||||
"/dms/{other_user_id}/messages",
|
||||
get(list_dm_messages).post(send_dm_message),
|
||||
)
|
||||
.route("/rtc-config", get(rtc_config))
|
||||
.route("/guilds", get(list_guilds).post(create_guild))
|
||||
.route("/guilds/{guild_id}/members", get(list_guild_members))
|
||||
.route("/guilds/{guild_id}/channels", get(list_channels))
|
||||
.route("/guilds/{guild_id}/voice-presence", get(guild_voice_presence))
|
||||
.route(
|
||||
"/guilds/{guild_id}/voice-presence",
|
||||
get(guild_voice_presence),
|
||||
)
|
||||
.route("/guilds/{guild_id}/invites", post(create_invite))
|
||||
.route("/invites/{code}/join", post(join_invite))
|
||||
.route("/channels", post(create_channel))
|
||||
.route("/channels/{channel_id}/messages", get(list_messages).post(send_message))
|
||||
.route(
|
||||
"/channels/{channel_id}/messages",
|
||||
get(list_messages).post(send_message),
|
||||
)
|
||||
.route("/channels/{channel_id}/voice/ws", get(voice_ws))
|
||||
.route("/ws", get(chat_ws))
|
||||
}
|
||||
|
||||
pub async fn health() -> &'static str {
|
||||
|
|
@ -142,7 +152,10 @@ async fn auth_callback(
|
|||
.map_err(|e| ApiError::bad_request(&format!("token exchange failed: {e}")))?;
|
||||
|
||||
if !token_res.status().is_success() {
|
||||
let text = token_res.text().await.unwrap_or_else(|_| "<no body>".to_string());
|
||||
let text = token_res
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "<no body>".to_string());
|
||||
return Err(ApiError::bad_request(&format!(
|
||||
"token exchange returned non-success: {text}"
|
||||
)));
|
||||
|
|
@ -162,7 +175,10 @@ async fn auth_callback(
|
|||
.map_err(|e| ApiError::bad_request(&format!("userinfo request failed: {e}")))?;
|
||||
|
||||
if !userinfo_res.status().is_success() {
|
||||
let text = userinfo_res.text().await.unwrap_or_else(|_| "<no body>".to_string());
|
||||
let text = userinfo_res
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "<no body>".to_string());
|
||||
return Err(ApiError::bad_request(&format!(
|
||||
"userinfo returned non-success: {text}"
|
||||
)));
|
||||
|
|
@ -185,9 +201,12 @@ async fn auth_callback(
|
|||
.await
|
||||
.map_err(|e| ApiError::internal(&format!("failed to persist user: {e}")))?;
|
||||
|
||||
let session_cookie =
|
||||
auth::new_session_cookie(user.id, &state.settings.session_secret, state.settings.cookie_secure)
|
||||
.map_err(|e| ApiError::internal(&e.to_string()))?;
|
||||
let session_cookie = auth::new_session_cookie(
|
||||
user.id,
|
||||
&state.settings.session_secret,
|
||||
state.settings.cookie_secure,
|
||||
)
|
||||
.map_err(|e| ApiError::internal(&e.to_string()))?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.append(
|
||||
|
|
@ -232,7 +251,10 @@ struct RtcIceServer {
|
|||
credential: Option<String>,
|
||||
}
|
||||
|
||||
async fn rtc_config(State(state): State<AppState>, _user: AuthUser) -> Result<impl IntoResponse, ApiError> {
|
||||
async fn rtc_config(
|
||||
State(state): State<AppState>,
|
||||
_user: AuthUser,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let mut ice_servers = Vec::new();
|
||||
|
||||
if !state.settings.stun_urls.is_empty() {
|
||||
|
|
@ -441,9 +463,13 @@ async fn create_channel(
|
|||
return Err(ApiError::bad_request("channel name must be 1..64 chars"));
|
||||
}
|
||||
|
||||
let kind = body.kind.unwrap_or_else(|| db::CHANNEL_KIND_TEXT.to_string());
|
||||
let kind = body
|
||||
.kind
|
||||
.unwrap_or_else(|| db::CHANNEL_KIND_TEXT.to_string());
|
||||
if kind != db::CHANNEL_KIND_TEXT && kind != db::CHANNEL_KIND_VOICE {
|
||||
return Err(ApiError::bad_request("channel kind must be 'text' or 'voice'"));
|
||||
return Err(ApiError::bad_request(
|
||||
"channel kind must be 'text' or 'voice'",
|
||||
));
|
||||
}
|
||||
|
||||
let channel = db::create_channel(&state.db, body.guild_id, trimmed, &kind)
|
||||
|
|
@ -497,10 +523,31 @@ async fn send_message(
|
|||
return Err(ApiError::bad_request("message body must be 1..4000 chars"));
|
||||
}
|
||||
|
||||
db::create_message(&state.db, channel_id, user.id, content)
|
||||
let message = db::create_message(&state.db, channel_id, user.id, content)
|
||||
.await
|
||||
.map_err(|e| ApiError::internal(&format!("failed to create message: {e}")))?;
|
||||
|
||||
// Broadcast to all guild members
|
||||
let guild_id = db::guild_id_for_channel(&state.db, channel_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::internal(&format!("channel lookup failed: {e}")))?
|
||||
.ok_or_else(|| ApiError::internal("channel not found after creation"))?;
|
||||
|
||||
let members = db::list_guild_member_ids(&state.db, guild_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::internal(&format!("failed to list guild members: {e}")))?;
|
||||
|
||||
state
|
||||
.chat
|
||||
.broadcast_to_many(
|
||||
members,
|
||||
chat::ServerEvent::MessageCreated {
|
||||
channel_id,
|
||||
message: serde_json::to_value(&message).unwrap_or_default(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok((StatusCode::CREATED, Json(SendMessageResponse { ok: true })))
|
||||
}
|
||||
|
||||
|
|
@ -557,10 +604,32 @@ async fn send_dm_message(
|
|||
return Err(ApiError::bad_request("message body must be 1..4000 chars"));
|
||||
}
|
||||
|
||||
db::create_direct_message(&state.db, user.id, other_user_id, content)
|
||||
let message = db::create_direct_message(&state.db, user.id, other_user_id, content)
|
||||
.await
|
||||
.map_err(|e| ApiError::internal(&format!("failed to create dm message: {e}")))?;
|
||||
|
||||
// Broadcast to both users
|
||||
state
|
||||
.chat
|
||||
.broadcast_to_user(
|
||||
user.id,
|
||||
chat::ServerEvent::DmCreated {
|
||||
other_user_id,
|
||||
message: serde_json::to_value(&message).unwrap_or_default(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
state
|
||||
.chat
|
||||
.broadcast_to_user(
|
||||
other_user_id,
|
||||
chat::ServerEvent::DmCreated {
|
||||
other_user_id: user.id,
|
||||
message: serde_json::to_value(&message).unwrap_or_default(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok((StatusCode::CREATED, Json(SendMessageResponse { ok: true })))
|
||||
}
|
||||
|
||||
|
|
@ -581,15 +650,27 @@ async fn voice_ws(
|
|||
})?;
|
||||
|
||||
if channel.kind != db::CHANNEL_KIND_VOICE {
|
||||
return Err(ApiError::bad_request("voice websocket requires a voice channel"));
|
||||
return Err(ApiError::bad_request(
|
||||
"voice websocket requires a voice channel",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ws.on_upgrade(move |socket| {
|
||||
voice::handle_socket(state, socket, channel_id, user.id)
|
||||
}))
|
||||
Ok(ws.on_upgrade(move |socket| voice::handle_socket(state, socket, channel_id, user.id)))
|
||||
}
|
||||
|
||||
async fn ensure_guild_member(state: &AppState, guild_id: Uuid, user_id: Uuid) -> Result<(), ApiError> {
|
||||
async fn chat_ws(
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<AppState>,
|
||||
user: AuthUser,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
Ok(ws.on_upgrade(move |socket| chat::handle_socket(state, socket, user.id)))
|
||||
}
|
||||
|
||||
async fn ensure_guild_member(
|
||||
state: &AppState,
|
||||
guild_id: Uuid,
|
||||
user_id: Uuid,
|
||||
) -> Result<(), ApiError> {
|
||||
let is_member = db::is_member_of_guild(&state.db, guild_id, user_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::internal(&format!("membership check failed: {e}")))?;
|
||||
|
|
@ -604,7 +685,11 @@ async fn ensure_guild_member(state: &AppState, guild_id: Uuid, user_id: Uuid) ->
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn ensure_channel_member(state: &AppState, channel_id: Uuid, user_id: Uuid) -> Result<(), ApiError> {
|
||||
async fn ensure_channel_member(
|
||||
state: &AppState,
|
||||
channel_id: Uuid,
|
||||
user_id: Uuid,
|
||||
) -> Result<(), ApiError> {
|
||||
let guild_id = db::guild_id_for_channel(&state.db, channel_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::internal(&format!("channel lookup failed: {e}")))?
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue