use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::error::AppError; use crate::models::channels::VoiceParticipant; use crate::service::ImService; use super::session::ImSession; #[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)] pub struct UpdateVoiceStateParams { pub session_id: Option, pub muted: Option, pub deafened: Option, pub self_muted: Option, pub self_deafened: Option, pub self_video: Option, pub streaming: Option, } impl ImService { pub async fn voice_participant_list( &self, _ctx: &ImSession, channel_id: Uuid, ) -> Result, AppError> { sqlx::query_as::<_, VoiceParticipant>( "SELECT id, channel_id, user_id, session_id, deafened, muted, \ self_deafened, self_muted, self_video, streaming, speaking, \ joined_at, left_at \ FROM voice_participant WHERE channel_id = $1 AND left_at IS NULL \ ORDER BY joined_at", ) .bind(channel_id) .fetch_all(self.ctx.db.reader()) .await .map_err(AppError::Database) } pub async fn voice_state_update( &self, ctx: &ImSession, channel_id: Uuid, params: UpdateVoiceStateParams, ) -> Result { let now = chrono::Utc::now(); sqlx::query_as::<_, VoiceParticipant>( "INSERT INTO voice_participant \ (id, channel_id, user_id, session_id, muted, deafened, \ self_muted, self_deafened, self_video, streaming, speaking, joined_at) \ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, false, false, $10) \ ON CONFLICT (channel_id, user_id) DO UPDATE SET \ session_id = COALESCE($4, voice_participant.session_id), \ muted = COALESCE($5, voice_participant.muted), \ deafened = COALESCE($6, voice_participant.deafened), \ self_muted = COALESCE($7, voice_participant.self_muted), \ self_deafened = COALESCE($8, voice_participant.self_deafened), \ self_video = COALESCE($9, voice_participant.self_video) \ RETURNING id, channel_id, user_id, session_id, deafened, muted, \ self_deafened, self_muted, self_video, streaming, speaking, \ joined_at, left_at", ) .bind(Uuid::now_v7()) .bind(channel_id) .bind(ctx.user) .bind(params.session_id.as_deref()) .bind(params.muted.unwrap_or(false)) .bind(params.deafened.unwrap_or(false)) .bind(params.self_muted.unwrap_or(false)) .bind(params.self_deafened.unwrap_or(false)) .bind(params.self_video.unwrap_or(false)) .bind(now) .fetch_one(self.ctx.db.writer()) .await .map_err(AppError::Database) } }