Skip to main content

opentalk_roomserver_types/
client_parameters.rs

1// SPDX-License-Identifier: EUPL-1.2
2// SPDX-FileCopyrightText: OpenTalk Team <mail@opentalk.eu>
3
4use opentalk_types_common::{
5    roomserver::DeviceSecret,
6    time::TimeZone,
7    users::{UserId, UserInfo},
8    utils::ExampleData,
9};
10use opentalk_types_signaling::ParticipationVisibility;
11use serde::{Deserialize, Serialize};
12
13use crate::{public_user_profile::PublicUserProfile, room_kind::RoomKind};
14
15/// Client context required for a signaling connection
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(example = json!(ClientParameters::example_data())))]
18pub struct ClientParameters {
19    /// Identifier to maintain participant state across reconnections, is generated by the client
20    pub device_secret: DeviceSecret,
21
22    /// Information about a registered user
23    #[serde(flatten)]
24    pub kind: ClientKind,
25
26    /// The role that the client assumes in the meeting.
27    pub role: Role,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
32#[serde(rename_all = "snake_case", tag = "kind")]
33pub enum ClientKind {
34    /// A registered user
35    Registered { profile: PublicUserProfile },
36
37    /// A guest participant
38    Guest,
39
40    /// Invisible recording participant
41    Recorder {
42        /// Room for which the recording was requested from
43        ///
44        /// Recorders may only access the recording-service commands & events in this room
45        room: RoomKind,
46    },
47
48    /// Invisible service participant used by the transcription service
49    Transcription {
50        /// Room for which the transcription was requested from
51        room: RoomKind,
52    },
53
54    /// Telephone call-In participant which identifies via a meeting id & pin
55    CallIn,
56
57    /// Same as `ClientKind::CallIn` but the controller may associate a user to the call-in
58    /// participant if phonenumber matching is enabled
59    RegisteredCallIn { profile: PublicUserProfile },
60}
61
62impl ClientKind {
63    pub fn user_id(&self) -> Option<UserId> {
64        match self {
65            ClientKind::Registered { profile } | ClientKind::RegisteredCallIn { profile } => {
66                Some(profile.id)
67            }
68            ClientKind::Guest
69            | ClientKind::Recorder { .. }
70            | ClientKind::CallIn
71            | ClientKind::Transcription { .. } => None,
72        }
73    }
74
75    pub fn email(&self) -> Option<&str> {
76        match self {
77            ClientKind::Registered { profile } | ClientKind::RegisteredCallIn { profile } => {
78                Some(&profile.email)
79            }
80            ClientKind::Guest
81            | ClientKind::Recorder { .. }
82            | ClientKind::CallIn
83            | ClientKind::Transcription { .. } => None,
84        }
85    }
86
87    pub fn avatar_url(&self) -> Option<&str> {
88        match self {
89            ClientKind::Registered { profile } | ClientKind::RegisteredCallIn { profile } => {
90                Some(&profile.user_info.avatar_url)
91            }
92            ClientKind::CallIn
93            | ClientKind::Guest
94            | ClientKind::Recorder { .. }
95            | ClientKind::Transcription { .. } => None,
96        }
97    }
98
99    pub fn user_info(&self) -> Option<&UserInfo> {
100        match self {
101            ClientKind::Registered { profile } | ClientKind::RegisteredCallIn { profile } => {
102                Some(&profile.user_info)
103            }
104            ClientKind::CallIn
105            | ClientKind::Guest
106            | ClientKind::Recorder { .. }
107            | ClientKind::Transcription { .. } => None,
108        }
109    }
110
111    pub fn participant_kind(&self) -> ParticipationKind {
112        match &self {
113            ClientKind::Registered { .. } => ParticipationKind::Registered,
114            ClientKind::Guest => ParticipationKind::Guest,
115            ClientKind::Recorder { .. } => ParticipationKind::Recorder,
116            ClientKind::Transcription { .. } => ParticipationKind::Transcription,
117            ClientKind::CallIn => ParticipationKind::CallIn,
118            ClientKind::RegisteredCallIn { .. } => ParticipationKind::RegisteredCallIn,
119        }
120    }
121
122    pub fn is_service(&self) -> bool {
123        match self {
124            ClientKind::Registered { .. }
125            | ClientKind::Guest
126            | ClientKind::CallIn
127            | ClientKind::RegisteredCallIn { .. } => false,
128            ClientKind::Recorder { .. } | ClientKind::Transcription { .. } => true,
129        }
130    }
131
132    /// Returns `true` if the participant is a registered, non call-in user, `false` otherwise.
133    pub fn is_registered_non_callin_user(&self) -> bool {
134        match self {
135            ClientKind::Registered { .. } => true,
136            ClientKind::Guest
137            | ClientKind::Recorder { .. }
138            | ClientKind::Transcription { .. }
139            | ClientKind::CallIn
140            | ClientKind::RegisteredCallIn { .. } => false,
141        }
142    }
143
144    pub fn is_guest_or_callin(&self) -> bool {
145        match self {
146            ClientKind::Registered { .. }
147            | ClientKind::Recorder { .. }
148            | ClientKind::Transcription { .. } => false,
149            ClientKind::Guest | ClientKind::CallIn | ClientKind::RegisteredCallIn { .. } => true,
150        }
151    }
152
153    /// Returns `true` if the participant is a call-in user, otherwise return `false`.
154    pub fn is_call_in(&self) -> bool {
155        match self {
156            ClientKind::Registered { .. }
157            | ClientKind::Guest
158            | ClientKind::Recorder { .. }
159            | ClientKind::Transcription { .. } => false,
160            ClientKind::CallIn | ClientKind::RegisteredCallIn { .. } => true,
161        }
162    }
163
164    pub fn time_zone(&self) -> Option<TimeZone> {
165        match self {
166            ClientKind::Registered { profile } | ClientKind::RegisteredCallIn { profile } => {
167                Some(profile.timezone)
168            }
169            ClientKind::Recorder { .. }
170            | ClientKind::CallIn
171            | ClientKind::Guest
172            | ClientKind::Transcription { .. } => None,
173        }
174    }
175}
176
177impl ClientKind {
178    pub fn visibility(&self) -> ParticipationVisibility {
179        match self {
180            ClientKind::Registered { .. }
181            | ClientKind::Guest
182            | ClientKind::CallIn
183            | ClientKind::RegisteredCallIn { .. } => ParticipationVisibility::Visible,
184            ClientKind::Recorder { .. } | ClientKind::Transcription { .. } => {
185                ParticipationVisibility::Hidden
186            }
187        }
188    }
189}
190
191impl ExampleData for ClientParameters {
192    fn example_data() -> Self {
193        Self {
194            device_secret: DeviceSecret::example_data(),
195            kind: ClientKind::Registered {
196                profile: PublicUserProfile::example_data(),
197            },
198            role: Role::Moderator,
199        }
200    }
201}
202
203#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
204#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
205#[serde(rename_all = "snake_case")]
206pub enum Role {
207    User,
208    Moderator,
209}
210
211impl Role {
212    /// Returns `true` if the role is [`Moderator`].
213    ///
214    /// [`Moderator`]: Role::Moderator
215    #[must_use]
216    pub fn is_moderator(&self) -> bool {
217        matches!(self, Self::Moderator)
218    }
219
220    pub fn to_opentalk_types_signaling_role(self) -> opentalk_types_signaling::Role {
221        match self {
222            Self::User => opentalk_types_signaling::Role::User,
223            Self::Moderator => opentalk_types_signaling::Role::Moderator,
224        }
225    }
226}
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
229#[serde(rename_all = "snake_case")]
230pub enum ParticipationKind {
231    Registered,
232    Guest,
233    Recorder,
234    Transcription,
235    CallIn,
236    RegisteredCallIn,
237}
238
239impl From<&ClientKind> for ParticipationKind {
240    fn from(value: &ClientKind) -> Self {
241        match value {
242            ClientKind::Registered { .. } => Self::Registered,
243            ClientKind::Guest => Self::Guest,
244            ClientKind::Recorder { .. } => Self::Recorder,
245            ClientKind::Transcription { .. } => Self::Transcription,
246            ClientKind::CallIn => Self::CallIn,
247            ClientKind::RegisteredCallIn { .. } => Self::RegisteredCallIn,
248        }
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use opentalk_types_common::{roomserver::DeviceSecret, utils::ExampleData};
255    use pretty_assertions::assert_eq;
256    use serde_json::json;
257
258    use super::ClientKind;
259    use crate::{
260        client_parameters::{ClientParameters, ParticipationKind, Role},
261        public_user_profile::PublicUserProfile,
262        room_kind::RoomKind,
263    };
264
265    #[test]
266    fn guest() {
267        let client_parameter = ClientParameters {
268            device_secret: DeviceSecret::example_data(),
269            kind: ClientKind::Guest,
270            role: Role::User,
271        };
272        assert_eq!(
273            client_parameter.kind.participant_kind(),
274            ParticipationKind::Guest
275        );
276
277        let value = serde_json::to_value(client_parameter).unwrap();
278
279        assert_eq!(
280            value,
281            json!(
282                {
283                    "role": "user",
284                    "device_secret": "v3rys3cr3tD3v1ce5tr1ng",
285                    "kind": "guest",
286                }
287            )
288        );
289    }
290
291    #[test]
292    fn registered() {
293        let client_parameter = ClientParameters {
294            device_secret: DeviceSecret::example_data(),
295            kind: ClientKind::Registered {
296                profile: PublicUserProfile::example_data(),
297            },
298            role: Role::User,
299        };
300        assert_eq!(
301            client_parameter.kind.participant_kind(),
302            ParticipationKind::Registered
303        );
304
305        let value = serde_json::to_value(client_parameter).unwrap();
306
307        assert_eq!(
308            value,
309            json!(
310                {
311                    "device_secret": "v3rys3cr3tD3v1ce5tr1ng",
312                    "kind": "registered",
313                    "role": "user",
314                    "profile": {
315                        "avatar_url": "https://gravatar.com/avatar/c160f8cc69a4f0bf2b0362752353d060",
316                        "display_name": "Alice Adams",
317                        "email": "alice@example.com",
318                        "firstname": "Alice",
319                        "lastname": "Adams",
320                        "id": "00000000-0000-0000-0000-0000000a11c3",
321                        "title": "",
322                        "timezone": "Europe/Berlin",
323                    }
324                }
325            )
326        )
327    }
328
329    #[test]
330    fn service() {
331        let client_parameter = ClientParameters {
332            device_secret: DeviceSecret::example_data(),
333            kind: ClientKind::Recorder {
334                room: RoomKind::Main,
335            },
336            role: Role::User,
337        };
338        assert_eq!(
339            client_parameter.kind.participant_kind(),
340            ParticipationKind::Recorder
341        );
342
343        let value = serde_json::to_value(client_parameter).unwrap();
344
345        assert_eq!(
346            value,
347            json!({
348                "role": "user",
349                "device_secret": "v3rys3cr3tD3v1ce5tr1ng",
350                "kind": "recorder",
351                "room": {
352                    "kind": "main",
353                }
354            })
355        );
356    }
357
358    #[test]
359    fn call_in() {
360        let client_parameter = ClientParameters {
361            device_secret: DeviceSecret::example_data(),
362            kind: ClientKind::CallIn,
363            role: Role::User,
364        };
365        assert_eq!(
366            client_parameter.kind.participant_kind(),
367            ParticipationKind::CallIn
368        );
369
370        let value = serde_json::to_value(client_parameter).unwrap();
371
372        assert_eq!(
373            value,
374            json!({
375                "role": "user",
376                "device_secret": "v3rys3cr3tD3v1ce5tr1ng",
377                "kind": "call_in",
378            })
379        );
380    }
381
382    #[test]
383    fn registered_call_in() {
384        let client_parameter = ClientParameters {
385            device_secret: DeviceSecret::example_data(),
386            kind: ClientKind::RegisteredCallIn {
387                profile: PublicUserProfile::example_data(),
388            },
389            role: Role::User,
390        };
391        assert_eq!(
392            client_parameter.kind.participant_kind(),
393            ParticipationKind::RegisteredCallIn
394        );
395
396        let value = serde_json::to_value(client_parameter).unwrap();
397
398        assert_eq!(
399            value,
400            json!({
401                "role": "user",
402                "device_secret": "v3rys3cr3tD3v1ce5tr1ng",
403                "kind": "registered_call_in",
404                "profile": {
405                    "avatar_url": "https://gravatar.com/avatar/c160f8cc69a4f0bf2b0362752353d060",
406                    "display_name": "Alice Adams",
407                    "email": "alice@example.com",
408                    "firstname": "Alice",
409                    "lastname": "Adams",
410                    "id": "00000000-0000-0000-0000-0000000a11c3",
411                    "title": "",
412                    "timezone": "Europe/Berlin",
413                }
414            })
415        );
416    }
417}