1use opentalk_roomserver_types_recording::{RECORDING_MODULE_ID, command::RecordingCommand};
5use opentalk_roomserver_types_transcription::{
6 TRANSCRIPTION_MODULE_ID, command::TranscriptionCommand,
7};
8use opentalk_types_common::modules::ModuleId;
9use serde::{Deserialize, Serialize};
10pub use {
12 opentalk_roomserver_types::{
13 breakout::{BREAKOUT_MODULE_ID, command::BreakoutCommand},
14 core::{CORE_MODULE_ID, CoreCommand},
15 },
16 opentalk_roomserver_types_automod::{AUTOMOD_MODULE_ID, command::AutomodCommand},
17 opentalk_roomserver_types_chat::{CHAT_MODULE_ID, command::ChatCommand},
18 opentalk_roomserver_types_e2ee::{E2EE_MODULE_ID, E2eeCommand},
19 opentalk_roomserver_types_echo::{ECHO_MODULE_ID, command::EchoCommand},
20 opentalk_roomserver_types_excalidraw::{EXCALIDRAW_MODULE_ID, ExcalidrawCommand},
21 opentalk_roomserver_types_legal_vote::{LEGAL_VOTE_MODULE_ID, LegalVoteCommand},
22 opentalk_roomserver_types_livekit::{
23 LIVEKIT_MODULE_ID, LiveKitCommand, MicrophoneRestrictionState,
24 },
25 opentalk_roomserver_types_meeting_notes::{MEETING_NOTES_MODULE_ID, MeetingNotesCommand},
26 opentalk_roomserver_types_meeting_report::{
27 MEETING_REPORT_MODULE_ID, command::MeetingReportCommand,
28 },
29 opentalk_roomserver_types_moderation::{MODERATION_MODULE_ID, command::ModerationCommand},
30 opentalk_roomserver_types_polls::{POLLS_MODULE_ID, command::PollsCommand},
31 opentalk_roomserver_types_raise_hands::{RAISE_HANDS_MODULE_ID, command::RaiseHandsCommand},
32 opentalk_roomserver_types_reaction::{REACTION_MODULE_ID, ReactionCommand},
33 opentalk_roomserver_types_shared_folder::{
34 SHARED_FOLDER_MODULE_ID, command::SharedFolderCommand,
35 },
36 opentalk_roomserver_types_subroom_audio::{
37 SUBROOM_AUDIO_MODULE_ID, command::SubroomAudioCommand,
38 },
39 opentalk_roomserver_types_timer::{TIMER_MODULE_ID, TimerCommand},
40 opentalk_roomserver_types_training_participation_report::{
41 TRAINING_PARTICIPATION_REPORT_MODULE_ID, command::TrainingParticipationReportCommand,
42 },
43 opentalk_roomserver_types_whiteboard::{WHITEBOARD_MODULE_ID, WhiteboardCommand},
44};
45
46#[derive(Debug, Serialize, Deserialize)]
47pub struct SignalingCommand {
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub transaction_id: Option<u64>,
50 #[serde(flatten)]
51 pub payload: SignalingModuleCommand,
52}
53
54impl SignalingCommand {
55 pub fn namespace(&self) -> ModuleId {
56 self.payload.namespace()
57 }
58}
59
60impl From<SignalingModuleCommand> for SignalingCommand {
61 fn from(payload: SignalingModuleCommand) -> Self {
62 Self {
63 transaction_id: None,
64 payload,
65 }
66 }
67}
68
69#[derive(Debug, Serialize, Deserialize)]
70#[serde(tag = "namespace", content = "payload", rename_all = "snake_case")]
71pub enum SignalingModuleCommand {
72 Core(CoreCommand),
73 Automod(AutomodCommand),
74 Breakout(BreakoutCommand),
75 Echo(EchoCommand),
76 Chat(ChatCommand),
77
78 #[serde(rename = "livekit")]
79 LiveKit(LiveKitCommand),
80
81 E2ee(E2eeCommand),
82
83 Timer(TimerCommand),
84 Polls(PollsCommand),
85 SharedFolder(SharedFolderCommand),
86 MeetingReport(MeetingReportCommand),
87 Moderation(ModerationCommand),
88 RaiseHands(RaiseHandsCommand),
89 SubroomAudio(SubroomAudioCommand),
90 MeetingNotes(MeetingNotesCommand),
91 Whiteboard(WhiteboardCommand),
92 Excalidraw(ExcalidrawCommand),
93 LegalVote(LegalVoteCommand),
94 TrainingParticipationReport(TrainingParticipationReportCommand),
95 Recording(RecordingCommand),
96 Transcription(TranscriptionCommand),
97 Reaction(ReactionCommand),
98}
99
100impl SignalingModuleCommand {
101 pub fn namespace(&self) -> ModuleId {
102 match self {
103 Self::Core(..) => CORE_MODULE_ID,
104 Self::Automod(..) => AUTOMOD_MODULE_ID,
105 Self::Breakout(..) => BREAKOUT_MODULE_ID,
106 Self::Echo(..) => ECHO_MODULE_ID,
107 Self::Chat(..) => CHAT_MODULE_ID,
108 Self::LiveKit(..) => LIVEKIT_MODULE_ID,
109 Self::E2ee(..) => E2EE_MODULE_ID,
110 Self::Timer(..) => TIMER_MODULE_ID,
111 Self::Polls(..) => POLLS_MODULE_ID,
112 Self::SharedFolder(..) => SHARED_FOLDER_MODULE_ID,
113 Self::MeetingReport(..) => MEETING_REPORT_MODULE_ID,
114 Self::Moderation(..) => MODERATION_MODULE_ID,
115 Self::RaiseHands(..) => RAISE_HANDS_MODULE_ID,
116 Self::SubroomAudio(..) => SUBROOM_AUDIO_MODULE_ID,
117 Self::MeetingNotes(..) => MEETING_NOTES_MODULE_ID,
118 Self::Whiteboard(..) => WHITEBOARD_MODULE_ID,
119 Self::Excalidraw(..) => EXCALIDRAW_MODULE_ID,
120 Self::LegalVote(..) => LEGAL_VOTE_MODULE_ID,
121 Self::TrainingParticipationReport(..) => TRAINING_PARTICIPATION_REPORT_MODULE_ID,
122 Self::Recording(..) => RECORDING_MODULE_ID,
123 Self::Transcription(..) => TRANSCRIPTION_MODULE_ID,
124 Self::Reaction(..) => REACTION_MODULE_ID,
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use std::collections::BTreeSet;
132
133 use insta::assert_snapshot;
134 use opentalk_roomserver_types::{breakout::command::BreakoutCommand, core::CoreCommand};
135 use opentalk_roomserver_types_automod::command::AutomodCommand;
136 use opentalk_roomserver_types_chat::command::ChatCommand;
137 use opentalk_roomserver_types_echo::command::EchoCommand;
138 use opentalk_roomserver_types_excalidraw::ExcalidrawCommand;
139 use opentalk_roomserver_types_legal_vote::{
140 LegalVoteCommand, cancel::CustomCancelReason, vote::LegalVoteId,
141 };
142 use opentalk_roomserver_types_livekit::LiveKitCommand;
143 use opentalk_roomserver_types_meeting_notes::MeetingNotesCommand;
144 use opentalk_roomserver_types_meeting_report::command::MeetingReportCommand;
145 use opentalk_roomserver_types_moderation::command::ModerationCommand;
146 use opentalk_roomserver_types_polls::{
147 ChoiceId, PollId,
148 command::{Choices, PollsCommand, Vote},
149 };
150 use opentalk_roomserver_types_raise_hands::command::RaiseHandsCommand;
151 use opentalk_roomserver_types_reaction::ReactionCommand;
152 use opentalk_roomserver_types_recording::command::RecordingCommand;
153 use opentalk_roomserver_types_timer::{TimerCommand, command::Kind};
154 use opentalk_roomserver_types_training_participation_report::TrainingParticipationReportCommand;
155 use opentalk_roomserver_types_transcription::command::TranscriptionCommand;
156 use opentalk_roomserver_types_whiteboard::WhiteboardCommand;
157 use opentalk_types_common::modules::ModuleId;
158 use opentalk_types_signaling::ParticipantId;
159 use serde::Deserialize;
160
161 use super::SignalingModuleCommand;
162 use crate::api::command::SignalingCommand;
163
164 #[derive(Debug, Clone, Deserialize)]
165 pub struct NamespaceOnly {
166 pub namespace: ModuleId,
167 }
168
169 #[test]
170 fn serialize_command_core() {
171 let command = SignalingCommand {
172 transaction_id: None,
173 payload: SignalingModuleCommand::Core(CoreCommand::EnterRoom { display_name: None }),
174 };
175 let raw = serde_json::to_string_pretty(&command).unwrap();
176 assert_snapshot!(raw, @r#"
177 {
178 "namespace": "core",
179 "payload": {
180 "action": "enter_room"
181 }
182 }
183 "#);
184 }
185
186 #[test]
187 fn serialize_command_automod() {
188 let command = SignalingCommand {
189 transaction_id: None,
190 payload: SignalingModuleCommand::Automod(AutomodCommand::Stop),
191 };
192 let raw = serde_json::to_string_pretty(&command).unwrap();
193
194 assert_snapshot!(raw, @r#"
195 {
196 "namespace": "automod",
197 "payload": {
198 "action": "stop"
199 }
200 }
201 "#);
202 }
203
204 #[test]
205 fn serialize_command_breakout() {
206 let command = SignalingCommand {
207 transaction_id: None,
208 payload: SignalingModuleCommand::Breakout(BreakoutCommand::Stop { delay: None }),
209 };
210 let raw = serde_json::to_string_pretty(&command).unwrap();
211
212 assert_snapshot!(raw, @r#"
213 {
214 "namespace": "breakout",
215 "payload": {
216 "action": "stop"
217 }
218 }
219 "#);
220
221 let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
223 assert_eq!(namespace_only.namespace, command.namespace());
224 }
225
226 #[test]
227 fn serialize_command_chat() {
228 let command = SignalingCommand {
229 transaction_id: None,
230 payload: SignalingModuleCommand::Chat(ChatCommand::DisableChat),
231 };
232 let raw = serde_json::to_string_pretty(&command).unwrap();
233
234 assert_snapshot!(raw, @r#"
235 {
236 "namespace": "chat",
237 "payload": {
238 "action": "disable_chat"
239 }
240 }
241 "#);
242
243 let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
245 assert_eq!(namespace_only.namespace, command.namespace());
246 }
247
248 #[test]
249 fn serialize_command_echo() {
250 let command = SignalingCommand {
251 transaction_id: None,
252 payload: SignalingModuleCommand::Echo(EchoCommand::Ping),
253 };
254 let raw = serde_json::to_string_pretty(&command).unwrap();
255
256 assert_snapshot!(raw, @r#"
257 {
258 "namespace": "echo",
259 "payload": {
260 "action": "ping"
261 }
262 }
263 "#);
264
265 let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
267 assert_eq!(namespace_only.namespace, command.namespace());
268 }
269
270 #[test]
271 fn serialize_command_livekit() {
272 let command = SignalingCommand {
273 transaction_id: None,
274 payload: SignalingModuleCommand::LiveKit(LiveKitCommand::CreateNewAccessToken),
275 };
276 let raw = serde_json::to_string_pretty(&command).unwrap();
277
278 assert_snapshot!(raw, @r#"
279 {
280 "namespace": "livekit",
281 "payload": {
282 "action": "create_new_access_token"
283 }
284 }
285 "#);
286
287 let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
289 assert_eq!(namespace_only.namespace, command.namespace());
290 }
291
292 #[test]
293 fn serialize_command_timer() {
294 let command = SignalingCommand {
295 transaction_id: None,
296 payload: SignalingModuleCommand::Timer(TimerCommand::Start {
297 kind: Kind::Stopwatch,
298 style: None,
299 title: None,
300 enable_ready_check: false,
301 }),
302 };
303 let raw = serde_json::to_string_pretty(&command).unwrap();
304
305 assert_snapshot!(raw, @r#"
306 {
307 "namespace": "timer",
308 "payload": {
309 "action": "start",
310 "kind": "stopwatch",
311 "style": null,
312 "title": null,
313 "enable_ready_check": false
314 }
315 }
316 "#);
317
318 let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
320 assert_eq!(namespace_only.namespace, command.namespace());
321 }
322
323 #[test]
324 fn serialize_command_polls() {
325 let command = SignalingCommand {
326 transaction_id: None,
327 payload: SignalingModuleCommand::Polls(PollsCommand::Vote(Vote {
328 poll_id: PollId::nil(),
329 choices: Choices::Single {
330 choice_id: ChoiceId::from_u32(0),
331 },
332 })),
333 };
334 let raw = serde_json::to_string_pretty(&command).unwrap();
335
336 assert_snapshot!(raw, @r#"
337 {
338 "namespace": "polls",
339 "payload": {
340 "action": "vote",
341 "poll_id": "00000000-0000-0000-0000-000000000000",
342 "choice_id": 0
343 }
344 }
345 "#);
346
347 let namespace_only: NamespaceOnly = serde_json::from_str(&raw).unwrap();
349 assert_eq!(namespace_only.namespace, command.namespace());
350 }
351
352 #[test]
353 fn serialize_command_meeting_report() {
354 let command = SignalingCommand {
355 transaction_id: None,
356 payload: SignalingModuleCommand::MeetingReport(
357 MeetingReportCommand::GenerateAttendanceReport {
358 include_email_addresses: false,
359 },
360 ),
361 };
362 let raw = serde_json::to_string_pretty(&command).unwrap();
363
364 assert_snapshot!(raw, @r#"
365 {
366 "namespace": "meeting_report",
367 "payload": {
368 "action": "generate_attendance_report",
369 "include_email_addresses": false
370 }
371 }
372 "#);
373 }
374
375 #[test]
376 fn serialize_command_moderation() {
377 let command = SignalingCommand {
378 transaction_id: None,
379 payload: SignalingModuleCommand::Moderation(ModerationCommand::Accept {
380 target: ParticipantId::nil(),
381 }),
382 };
383 let raw = serde_json::to_string_pretty(&command).unwrap();
384
385 assert_snapshot!(raw, @r#"
386 {
387 "namespace": "moderation",
388 "payload": {
389 "action": "accept",
390 "target": "00000000-0000-0000-0000-000000000000"
391 }
392 }
393 "#);
394 }
395
396 #[test]
397 fn serialize_command_raise_hands() {
398 let command = SignalingCommand {
399 transaction_id: None,
400 payload: SignalingModuleCommand::RaiseHands(RaiseHandsCommand::RaiseHand),
401 };
402 let raw = serde_json::to_string_pretty(&command).unwrap();
403
404 assert_snapshot!(raw, @r#"
405 {
406 "namespace": "raise_hands",
407 "payload": {
408 "action": "raise_hand"
409 }
410 }
411 "#);
412 }
413
414 #[test]
415 fn serialize_command_meeting_notes() {
416 let command = SignalingCommand {
417 transaction_id: None,
418 payload: SignalingModuleCommand::MeetingNotes(MeetingNotesCommand::GrantWriteAccess {
419 participant_ids: BTreeSet::new(),
420 }),
421 };
422 let raw = serde_json::to_string_pretty(&command).unwrap();
423
424 assert_snapshot!(raw, @r#"
425 {
426 "namespace": "meeting_notes",
427 "payload": {
428 "action": "grant_write_access",
429 "participant_ids": []
430 }
431 }
432 "#);
433 }
434
435 #[test]
436 fn serialize_command_whiteboard() {
437 let command = SignalingCommand {
438 transaction_id: None,
439 payload: SignalingModuleCommand::Whiteboard(WhiteboardCommand::Initialize),
440 };
441 let raw = serde_json::to_string_pretty(&command).unwrap();
442
443 assert_snapshot!(raw, @r#"
444 {
445 "namespace": "whiteboard",
446 "payload": {
447 "action": "initialize"
448 }
449 }
450 "#);
451 }
452
453 #[test]
454 fn serialize_command_excalidraw() {
455 let command = SignalingCommand {
456 transaction_id: None,
457 payload: SignalingModuleCommand::Excalidraw(ExcalidrawCommand::Follow {
458 participant_id: ParticipantId::nil(),
459 }),
460 };
461 let raw = serde_json::to_string_pretty(&command).unwrap();
462
463 assert_snapshot!(raw, @r#"
464 {
465 "namespace": "excalidraw",
466 "payload": {
467 "action": "follow",
468 "participant_id": "00000000-0000-0000-0000-000000000000"
469 }
470 }
471 "#);
472 }
473
474 #[test]
475 fn serialize_command_legal_vote() {
476 let command = SignalingCommand {
477 transaction_id: None,
478 payload: SignalingModuleCommand::LegalVote(LegalVoteCommand::Cancel {
479 legal_vote_id: LegalVoteId::from_u128(1),
480 reason: CustomCancelReason::try_from("Test Reason").unwrap(),
481 }),
482 };
483 let raw = serde_json::to_string_pretty(&command).unwrap();
484
485 assert_snapshot!(raw, @r#"
486 {
487 "namespace": "legal_vote",
488 "payload": {
489 "action": "cancel",
490 "legal_vote_id": "00000000-0000-0000-0000-000000000001",
491 "reason": "Test Reason"
492 }
493 }
494 "#);
495 }
496
497 #[test]
498 fn serialize_training_participation_report() {
499 let command = SignalingCommand {
500 transaction_id: None,
501 payload: SignalingModuleCommand::TrainingParticipationReport(
502 TrainingParticipationReportCommand::ConfirmPresence,
503 ),
504 };
505 let raw = serde_json::to_string_pretty(&command).unwrap();
506
507 assert_snapshot!(raw, @r#"
508 {
509 "namespace": "training_participation_report",
510 "payload": {
511 "action": "confirm_presence"
512 }
513 }
514 "#);
515 }
516
517 #[test]
518 fn serialize_recording() {
519 let command = SignalingCommand {
520 transaction_id: None,
521 payload: SignalingModuleCommand::Recording(RecordingCommand::StartRecording),
522 };
523 let raw = serde_json::to_string_pretty(&command).unwrap();
524
525 assert_snapshot!(raw, @r#"
526 {
527 "namespace": "recording",
528 "payload": {
529 "action": "start_recording"
530 }
531 }
532 "#);
533 }
534
535 #[test]
536 fn serialize_transcription() {
537 let command = SignalingCommand {
538 transaction_id: None,
539 payload: SignalingModuleCommand::Transcription(TranscriptionCommand::Start {
540 language: None,
541 }),
542 };
543
544 let raw = serde_json::to_string_pretty(&command).unwrap();
545
546 assert_snapshot!(raw, @r#"
547 {
548 "namespace": "transcription",
549 "payload": {
550 "action": "start"
551 }
552 }
553 "#);
554 }
555
556 #[test]
557 fn serialize_reaction() {
558 let command = SignalingCommand {
559 transaction_id: None,
560 payload: SignalingModuleCommand::Reaction(ReactionCommand::React {
561 reaction: opentalk_roomserver_types_reaction::Reaction::ThumbsUp,
562 }),
563 };
564
565 let raw = serde_json::to_string_pretty(&command).unwrap();
566
567 assert_snapshot!(raw, @r#"
568 {
569 "namespace": "reaction",
570 "payload": {
571 "action": "react",
572 "reaction": "thumbs_up"
573 }
574 }
575 "#);
576 }
577}