lamprey_backend/types/
data.rs

1use common::v1::types::Mentions;
2use common::v1::types::{
3    util::Time, Channel, ChannelId, ChannelType, ChannelVerId, Embed, MediaId, MessageId,
4    MessageType, MessageVerId, Permission, Puppet, RoleId, Room, RoomId, RoomMembership, RoomType,
5    Session, SessionStatus, SessionToken, SessionType, ThreadMembership, UserId,
6};
7use serde::{Deserialize, Serialize};
8use std::str::FromStr;
9use time::PrimitiveDateTime;
10use uuid::Uuid;
11
12pub use common::v1::types::ids::*;
13pub use common::v1::types::misc::{SessionIdReq, UserIdReq};
14
15pub struct DbRoom {
16    pub id: Uuid,
17    pub version_id: Uuid,
18    pub owner_id: Option<Uuid>,
19    pub name: String,
20    pub description: Option<String>,
21    pub icon: Option<Uuid>,
22    pub archived_at: Option<PrimitiveDateTime>,
23    pub public: bool,
24    pub ty: DbRoomType,
25    pub welcome_channel_id: Option<Uuid>,
26    pub member_count: i64,
27    pub channel_count: i64,
28    pub quarantined: bool,
29}
30
31pub struct DbRoomCreate {
32    pub id: Option<RoomId>,
33    pub ty: RoomType,
34    pub welcome_channel_id: Option<ChannelId>,
35}
36
37pub struct DbUserCreate {
38    pub id: Option<UserId>,
39    pub parent_id: Option<UserId>,
40    pub name: String,
41    pub description: Option<String>,
42    pub puppet: Option<Puppet>,
43    pub registered_at: Option<Time>,
44    pub system: bool,
45}
46
47#[derive(sqlx::Type, PartialEq)]
48#[sqlx(type_name = "membership")]
49pub enum DbMembership {
50    Join,
51    Leave,
52    Ban, // unused
53}
54
55impl From<DbRoom> for Room {
56    fn from(row: DbRoom) -> Self {
57        #[allow(deprecated)]
58        Room {
59            id: row.id.into(),
60            version_id: row.version_id,
61            owner_id: row.owner_id.map(Into::into),
62            name: row.name,
63            description: row.description,
64            icon: row.icon.map(|i| i.into()),
65            room_type: row.ty.into(),
66            archived_at: row.archived_at.map(|t| Time::from(t.assume_utc())),
67            public: row.public,
68            welcome_channel_id: row.welcome_channel_id.map(|i| i.into()),
69            quarantined: row.quarantined,
70            member_count: row.member_count as u64,
71            online_count: Default::default(),
72            channel_count: row.channel_count as u64,
73            user_config: None,
74        }
75    }
76}
77
78#[derive(Debug, Deserialize)]
79pub struct DbChannel {
80    pub id: ChannelId,
81    pub room_id: Option<Uuid>,
82    pub creator_id: UserId,
83    pub owner_id: Option<Uuid>,
84    pub version_id: ChannelVerId,
85    pub name: String,
86    pub description: Option<String>,
87    pub icon: Option<Uuid>,
88    pub ty: DbChannelType,
89    pub last_version_id: Option<Uuid>,
90    pub message_count: i64,
91    pub member_count: i64,
92    pub permission_overwrites: serde_json::Value,
93    pub nsfw: bool,
94    pub locked: bool,
95    pub archived_at: Option<PrimitiveDateTime>,
96    pub deleted_at: Option<PrimitiveDateTime>,
97    pub parent_id: Option<Uuid>,
98    pub position: Option<i32>,
99    pub bitrate: Option<i32>,
100    pub user_limit: Option<i32>,
101}
102
103#[derive(Debug, Deserialize, Clone)]
104pub struct DbChannelPrivate {
105    pub id: ChannelId,
106    pub ty: DbChannelType,
107    pub last_read_id: Option<Uuid>,
108    pub is_unread: bool,
109}
110
111pub struct DbChannelCreate {
112    pub room_id: Option<Uuid>,
113    pub creator_id: UserId,
114    pub owner_id: Option<Uuid>,
115    pub name: String,
116    pub description: Option<String>,
117    pub icon: Option<Uuid>,
118    pub ty: DbChannelType,
119    pub nsfw: bool,
120    pub bitrate: Option<i32>,
121    pub user_limit: Option<i32>,
122    pub parent_id: Option<Uuid>,
123}
124
125#[derive(sqlx::Type, Debug, Deserialize, PartialEq, Eq, Clone, Copy)]
126#[sqlx(type_name = "channel_type")]
127pub enum DbChannelType {
128    Text,
129    Forum,
130    Voice,
131    Dm,
132    Gdm,
133    Category,
134    ThreadPublic,
135    ThreadPrivate,
136}
137
138impl From<DbChannelType> for ChannelType {
139    fn from(value: DbChannelType) -> Self {
140        match value {
141            DbChannelType::Text => ChannelType::Text,
142            DbChannelType::Forum => ChannelType::Forum,
143            DbChannelType::Voice => ChannelType::Voice,
144            DbChannelType::Dm => ChannelType::Dm,
145            DbChannelType::Gdm => ChannelType::Gdm,
146            DbChannelType::Category => ChannelType::Category,
147            DbChannelType::ThreadPublic => ChannelType::ThreadPublic,
148            DbChannelType::ThreadPrivate => ChannelType::ThreadPrivate,
149        }
150    }
151}
152
153impl From<DbChannel> for Channel {
154    fn from(row: DbChannel) -> Self {
155        Channel {
156            id: row.id,
157            room_id: row.room_id.map(Into::into),
158            creator_id: row.creator_id,
159            version_id: row.version_id,
160            name: row.name,
161            description: row.description,
162            icon: row.icon.map(|i| i.into()),
163            nsfw: row.nsfw,
164            locked: row.locked,
165            member_count: row.member_count.try_into().expect("count is negative?"),
166            permission_overwrites: serde_json::from_value(row.permission_overwrites).unwrap(),
167            archived_at: row.archived_at.map(|t| t.into()),
168            deleted_at: row.deleted_at.map(|t| t.into()),
169            ty: row.ty.into(),
170            last_version_id: row.last_version_id.map(|i| i.into()),
171            message_count: Some(row.message_count.try_into().expect("count is negative?")),
172            parent_id: row.parent_id.map(|i| i.into()),
173            position: row
174                .position
175                .map(|p| p.try_into().expect("position is negative or overflows?")),
176            bitrate: row.bitrate.map(|i| i as u64),
177            user_limit: row.user_limit.map(|i| i as u64),
178            owner_id: row.owner_id.map(|i| i.into()),
179
180            // these fields get filled in later
181            is_unread: None,
182            last_read_id: None,
183            mention_count: None,
184            recipients: vec![],
185            user_config: None,
186            online_count: 0,
187
188            // TODO: store or calculate the fields below
189            tags: Default::default(),
190            root_message_count: None,
191        }
192    }
193}
194
195pub struct DbSession {
196    pub id: Uuid,
197    pub user_id: Option<Uuid>,
198    pub token: SessionToken,
199    pub status: DbSessionStatus,
200    pub name: Option<String>,
201    pub expires_at: Option<PrimitiveDateTime>,
202    pub ty: String,
203    pub application_id: Option<Uuid>,
204    pub last_seen_at: PrimitiveDateTime,
205}
206
207pub struct DbSessionCreate {
208    pub token: SessionToken,
209    pub name: Option<String>,
210    pub expires_at: Option<Time>,
211    pub ty: SessionType,
212    pub application_id: Option<ApplicationId>,
213}
214
215#[derive(sqlx::Type)]
216#[sqlx(type_name = "session_status")]
217pub enum DbSessionStatus {
218    Unauthorized,
219    Authorized,
220    Sudo,
221}
222
223impl From<DbSession> for Session {
224    fn from(row: DbSession) -> Self {
225        Session {
226            id: row.id.into(),
227            status: match row.status {
228                DbSessionStatus::Unauthorized => SessionStatus::Unauthorized,
229                DbSessionStatus::Authorized => SessionStatus::Authorized {
230                    user_id: row.user_id.expect("invalid data in db!").into(),
231                },
232                DbSessionStatus::Sudo => SessionStatus::Sudo {
233                    user_id: row.user_id.expect("invalid data in db!").into(),
234                    sudo_expires_at: Time::now_utc(),
235                },
236            },
237            name: row.name,
238            expires_at: row.expires_at.map(|t| t.into()),
239            ty: SessionType::from_str(&row.ty).unwrap_or(SessionType::User),
240            app_id: row.application_id.map(Into::into),
241            last_seen_at: row.last_seen_at.into(),
242        }
243    }
244}
245
246impl From<SessionStatus> for DbSessionStatus {
247    fn from(value: SessionStatus) -> Self {
248        match value {
249            SessionStatus::Unauthorized => DbSessionStatus::Unauthorized,
250            SessionStatus::Authorized { .. } => DbSessionStatus::Authorized,
251            SessionStatus::Sudo { .. } => DbSessionStatus::Sudo,
252        }
253    }
254}
255
256pub struct DbRoleCreate {
257    pub id: RoleId,
258    pub room_id: RoomId,
259    pub name: String,
260    pub description: Option<String>,
261    pub permissions: Vec<Permission>,
262    pub is_self_applicable: bool,
263    pub is_mentionable: bool,
264}
265
266pub struct DbMessageCreate {
267    pub channel_id: ChannelId,
268    pub attachment_ids: Vec<MediaId>,
269    pub author_id: UserId,
270    pub embeds: Vec<Embed>,
271    pub message_type: MessageType,
272    pub edited_at: Option<time::PrimitiveDateTime>,
273    pub created_at: Option<time::PrimitiveDateTime>,
274    pub mentions: Mentions,
275}
276
277// TODO: move to types?
278impl DbMessageCreate {
279    pub fn content(&self) -> Option<String> {
280        match &self.message_type {
281            MessageType::DefaultMarkdown(msg) => msg.content.clone(),
282            _ => None,
283        }
284    }
285
286    pub fn metadata(&self) -> Option<serde_json::Value> {
287        match &self.message_type {
288            MessageType::DefaultMarkdown(msg) => msg.metadata.clone(),
289            MessageType::ThreadRename(patch) => Some(serde_json::to_value(patch).ok()?),
290            MessageType::MemberAdd(patch) => Some(serde_json::to_value(patch).ok()?),
291            MessageType::MemberRemove(patch) => Some(serde_json::to_value(patch).ok()?),
292            MessageType::MemberJoin => None,
293            MessageType::MessagePinned(pinned) => Some(serde_json::to_value(pinned).ok()?),
294            _ => None,
295        }
296    }
297
298    pub fn reply_id(&self) -> Option<MessageId> {
299        match &self.message_type {
300            MessageType::DefaultMarkdown(msg) => msg.reply_id,
301            _ => None,
302        }
303    }
304
305    pub fn override_name(&self) -> Option<String> {
306        match &self.message_type {
307            MessageType::DefaultMarkdown(msg) => msg.override_name.clone(),
308            _ => None,
309        }
310    }
311}
312
313macro_rules! impl_perms {
314    ($($e:ident,)*) => {
315        #[derive(Debug, sqlx::Type, PartialEq, Eq)]
316        #[sqlx(type_name = "permission")]
317        pub enum DbPermission {
318            $($e,)*
319        }
320
321        impl From<DbPermission> for Permission {
322            fn from(value: DbPermission) -> Self {
323                match value {
324                    $(DbPermission::$e => Permission::$e,)*
325                }
326            }
327        }
328
329        impl From<Permission> for DbPermission {
330            fn from(value: Permission) -> Self {
331                match value {
332                    $(Permission::$e => DbPermission::$e,)*
333                }
334            }
335        }
336    }
337}
338
339// surely there's a better way without copypasta
340impl_perms!(
341    Admin,
342    IntegrationsManage,
343    EmojiManage,
344    EmojiUseExternal,
345    InviteCreate,
346    InviteManage,
347    MemberBan,
348    MemberBridge,
349    MemberKick,
350    MemberNicknameManage,
351    MemberTimeout,
352    MessageCreate,
353    MessageDelete,
354    MessageRemove,
355    MessageEmbeds,
356    MessageMassMention,
357    MessageAttachments,
358    MessageMove,
359    MessagePin,
360    ReactionAdd,
361    ReactionPurge,
362    MemberNickname,
363    RoleApply,
364    RoleManage,
365    RoomManage,
366    ServerMetrics,
367    ServerOversee,
368    ServerReports,
369    TagApply,
370    TagManage,
371    ThreadCreatePublic,
372    ThreadCreatePrivate,
373    ThreadEdit,
374    ThreadLock,
375    ThreadManage,
376    ViewChannel,
377    ViewAuditLog,
378    VoiceConnect,
379    VoiceDeafen,
380    VoiceDisconnect,
381    VoiceMove,
382    VoiceMute,
383    VoicePriority,
384    VoiceSpeak,
385    VoiceVideo,
386    ChannelManage,
387    ChannelEdit,
388    CalendarEventManage,
389);
390
391impl From<RoomMembership> for DbMembership {
392    fn from(value: RoomMembership) -> Self {
393        match value {
394            RoomMembership::Join => DbMembership::Join,
395            RoomMembership::Leave => DbMembership::Leave,
396        }
397    }
398}
399
400impl From<ThreadMembership> for DbMembership {
401    fn from(value: ThreadMembership) -> Self {
402        match value {
403            ThreadMembership::Join => DbMembership::Join,
404            ThreadMembership::Leave => DbMembership::Leave,
405        }
406    }
407}
408
409pub struct DbInvite {
410    pub code: String,
411    pub target_type: String,
412    pub target_id: Option<Uuid>,
413    pub creator_id: Uuid,
414    pub max_uses: Option<i32>,
415    pub uses: i32,
416    pub created_at: time::PrimitiveDateTime,
417    pub expires_at: Option<time::PrimitiveDateTime>,
418    pub description: Option<String>,
419}
420
421#[derive(Deserialize)]
422pub struct RoleDeleteQuery {
423    #[serde(default)]
424    pub force: bool,
425}
426
427#[derive(sqlx::Type, PartialEq, Eq)]
428#[sqlx(type_name = "media_link_type")]
429pub enum MediaLinkType {
430    Message,
431    MessageVersion,
432    AvatarUser,
433    BannerUser,
434    // TODO: rename to IconChannel
435    IconThread,
436    AvatarRoom,
437    Embed,
438    CustomEmoji,
439}
440
441// TODO: surely there's a better way than manually managing media links/references
442pub struct MediaLink {
443    pub media_id: MediaId,
444    pub target_id: Uuid,
445    pub link_type: MediaLinkType,
446}
447
448#[derive(Debug, sqlx::FromRow)]
449pub struct UrlEmbedQueue {
450    pub id: Uuid,
451    pub message_ref: Option<serde_json::Value>,
452    pub user_id: Uuid,
453    pub url: String,
454    pub created_at: PrimitiveDateTime,
455    pub claimed_at: Option<PrimitiveDateTime>,
456    pub finished_at: Option<PrimitiveDateTime>,
457}
458
459#[derive(Debug, sqlx::FromRow)]
460pub struct DbNotification {
461    pub id: Uuid,
462    pub channel_id: Uuid,
463    pub message_id: Uuid,
464    pub reason: String,
465    pub added_at: PrimitiveDateTime,
466    pub read_at: Option<PrimitiveDateTime>,
467}
468
469#[derive(Debug, Serialize, Deserialize)]
470pub struct MessageRef {
471    pub message_id: MessageId,
472    pub version_id: MessageVerId,
473    pub thread_id: ChannelId,
474}
475
476#[derive(sqlx::FromRow)]
477pub struct DbEmailQueue {
478    pub id: Uuid,
479    pub to_addr: String,
480    pub from_addr: String,
481    pub subject: String,
482    pub plain_text_body: String,
483    pub html_body: Option<String>,
484}
485
486pub enum EmailPurpose {
487    /// log in ("magic link")
488    Authn,
489
490    /// reset password
491    Reset,
492}
493
494#[derive(sqlx::Type)]
495#[sqlx(type_name = "room_type")]
496pub enum DbRoomType {
497    Default,
498    Server,
499}
500
501impl Into<DbRoomType> for RoomType {
502    fn into(self) -> DbRoomType {
503        match self {
504            RoomType::Default => DbRoomType::Default,
505            RoomType::Server => DbRoomType::Server,
506        }
507    }
508}
509
510impl Into<RoomType> for DbRoomType {
511    fn into(self) -> RoomType {
512        match self {
513            DbRoomType::Default => RoomType::Default,
514            DbRoomType::Server => RoomType::Server,
515        }
516    }
517}