Files
AstralRinth/apps/labrinth/src/models/v3/notifications.rs
Josiah Glosson 650ab71a83 Commonized networking (#3310)
* Fix not being able to connect to local friends socket

* Start basic work on tunneling protocol and move some code into a common crate

* Commonize message serialization logic

* Serialize Base62Ids as u64 when human-readability is not required

* Move ActiveSockets tuple into struct

* Make CI run when rust-common is updated

CI is currently broken for labrinth, however

* Fix theseus-release.yml to reference itself correctly

* Implement Labrinth side of tunneling

* Implement non-friend part of theseus tunneling

* Implement client-side except for socket loop

* Implement the socket loop

Doesn't work though. Debugging time!

* Fix config.rs

* Fix deadlock in labrinth socket handling

* Update dockerfile

* switch to workspace prepare at root level

* Wait for connection before tunneling in playground

* Move rust-common into labrinth

* Remove rust-common references from Actions

* Revert "Update dockerfile"

This reverts commit 3caad59bb474ce425d0b8928d7cee7ae1a5011bd.

* Fix Docker build

* Rebuild Theseus if common code changes

* Allow multiple connections from the same user

* Fix test building

* Move FriendSocketListening and FriendSocketStoppedListening to non-panicking TODO for now

* Make message_serialization macro take varargs for binary messages

* Improve syntax of message_serialization macro

* Remove the ability to connect to a virtual socket, and disable the ability to listen on one

* Allow the app to compile without running labrinth

* Clippy fix

* Update Rust and Clippy fix again

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2025-02-28 10:52:47 -08:00

219 lines
7.3 KiB
Rust

use super::ids::Base62Id;
use super::ids::OrganizationId;
use super::users::UserId;
use crate::database::models::notification_item::Notification as DBNotification;
use crate::database::models::notification_item::NotificationAction as DBNotificationAction;
use crate::models::ids::{
ProjectId, ReportId, TeamId, ThreadId, ThreadMessageId, VersionId,
};
use crate::models::projects::ProjectStatus;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(from = "Base62Id")]
#[serde(into = "Base62Id")]
pub struct NotificationId(pub u64);
#[derive(Serialize, Deserialize)]
pub struct Notification {
pub id: NotificationId,
pub user_id: UserId,
pub read: bool,
pub created: DateTime<Utc>,
pub body: NotificationBody,
pub name: String,
pub text: String,
pub link: String,
pub actions: Vec<NotificationAction>,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum NotificationBody {
ProjectUpdate {
project_id: ProjectId,
version_id: VersionId,
},
TeamInvite {
project_id: ProjectId,
team_id: TeamId,
invited_by: UserId,
role: String,
},
OrganizationInvite {
organization_id: OrganizationId,
invited_by: UserId,
team_id: TeamId,
role: String,
},
StatusChange {
project_id: ProjectId,
old_status: ProjectStatus,
new_status: ProjectStatus,
},
ModeratorMessage {
thread_id: ThreadId,
message_id: ThreadMessageId,
project_id: Option<ProjectId>,
report_id: Option<ReportId>,
},
LegacyMarkdown {
notification_type: Option<String>,
name: String,
text: String,
link: String,
actions: Vec<NotificationAction>,
},
Unknown,
}
impl From<DBNotification> for Notification {
fn from(notif: DBNotification) -> Self {
let (name, text, link, actions) = {
match &notif.body {
NotificationBody::ProjectUpdate {
project_id,
version_id,
} => (
"A project you follow has been updated!".to_string(),
format!(
"The project {} has released a new version: {}",
project_id, version_id
),
format!("/project/{}/version/{}", project_id, version_id),
vec![],
),
NotificationBody::TeamInvite {
project_id,
role,
team_id,
..
} => (
"You have been invited to join a team!".to_string(),
format!("An invite has been sent for you to be {} of a team", role),
format!("/project/{}", project_id),
vec![
NotificationAction {
name: "Accept".to_string(),
action_route: ("POST".to_string(), format!("team/{team_id}/join")),
},
NotificationAction {
name: "Deny".to_string(),
action_route: (
"DELETE".to_string(),
format!("team/{team_id}/members/{}", UserId::from(notif.user_id)),
),
},
],
),
NotificationBody::OrganizationInvite {
organization_id,
role,
team_id,
..
} => (
"You have been invited to join an organization!".to_string(),
format!(
"An invite has been sent for you to be {} of an organization",
role
),
format!("/organization/{}", organization_id),
vec![
NotificationAction {
name: "Accept".to_string(),
action_route: ("POST".to_string(), format!("team/{team_id}/join")),
},
NotificationAction {
name: "Deny".to_string(),
action_route: (
"DELETE".to_string(),
format!(
"organization/{organization_id}/members/{}",
UserId::from(notif.user_id)
),
),
},
],
),
NotificationBody::StatusChange {
old_status,
new_status,
project_id,
} => (
"Project status has changed".to_string(),
format!(
"Status has changed from {} to {}",
old_status.as_friendly_str(),
new_status.as_friendly_str()
),
format!("/project/{}", project_id),
vec![],
),
NotificationBody::ModeratorMessage {
project_id,
report_id,
..
} => (
"A moderator has sent you a message!".to_string(),
"Click on the link to read more.".to_string(),
if let Some(project_id) = project_id {
format!("/project/{}", project_id)
} else if let Some(report_id) = report_id {
format!("/project/{}", report_id)
} else {
"#".to_string()
},
vec![],
),
NotificationBody::LegacyMarkdown {
name,
text,
link,
actions,
..
} => (
name.clone(),
text.clone(),
link.clone(),
actions.clone().into_iter().collect(),
),
NotificationBody::Unknown => {
("".to_string(), "".to_string(), "#".to_string(), vec![])
}
}
};
Self {
id: notif.id.into(),
user_id: notif.user_id.into(),
body: notif.body,
read: notif.read,
created: notif.created,
name,
text,
link,
actions,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct NotificationAction {
pub name: String,
/// The route to call when this notification action is called. Formatted HTTP Method, route
pub action_route: (String, String),
}
impl From<DBNotificationAction> for NotificationAction {
fn from(act: DBNotificationAction) -> Self {
Self {
name: act.name,
action_route: (act.action_route_method, act.action_route),
}
}
}