feat: server invite notification type

This commit is contained in:
sychic
2026-06-02 17:09:50 +01:00
committed by Calum H. (IMB11)
parent 5a31f35f3e
commit e41d116213
4 changed files with 115 additions and 0 deletions
@@ -0,0 +1,28 @@
INSERT INTO notifications_types
(name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications)
VALUES ('server_invite', 1, FALSE, TRUE);
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
VALUES (NULL, 'email', 'server_invite', FALSE);
INSERT INTO notifications_templates
(channel, notification_type, subject_line, body_fetch_url, plaintext_fallback)
VALUES
(
'email',
'server_invite',
'You''ve been invited to a server',
'https://modrinth.com/_internal/templates/email/server-invited',
CONCAT(
'Hi {user.name},',
CHR(10),
CHR(10),
'Modrinth user {inviter.name} has invited you to help manage {server.name} on Modrinth Hosting with the {server.role} role.',
CHR(10),
CHR(10),
'To accept or reject this invitation, open your Modrinth notifications: https://modrinth.com/dashboard/notifications',
CHR(10),
CHR(10),
'If you were not expecting this invitation, contact the server owner or reach out to Modrinth Support at https://support.modrinth.com'
)
);
@@ -11,6 +11,7 @@ use crate::models::{
use ariadne::ids::UserId; use ariadne::ids::UserId;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct LegacyNotification { pub struct LegacyNotification {
@@ -66,6 +67,12 @@ pub enum LegacyNotificationBody {
team_id: TeamId, team_id: TeamId,
role: String, role: String,
}, },
ServerInvite {
server_id: Uuid,
server_name: String,
invited_by: UserId,
role: String,
},
StatusChange { StatusChange {
project_id: ProjectId, project_id: ProjectId,
old_status: ProjectStatus, old_status: ProjectStatus,
@@ -166,6 +173,9 @@ impl LegacyNotification {
NotificationBody::OrganizationInvite { .. } => { NotificationBody::OrganizationInvite { .. } => {
Some("organization_invite".to_string()) Some("organization_invite".to_string())
} }
NotificationBody::ServerInvite { .. } => {
Some("server_invite".to_string())
}
NotificationBody::StatusChange { .. } => { NotificationBody::StatusChange { .. } => {
Some("status_change".to_string()) Some("status_change".to_string())
} }
@@ -269,6 +279,17 @@ impl LegacyNotification {
team_id, team_id,
role, role,
}, },
NotificationBody::ServerInvite {
server_id,
server_name,
invited_by,
role,
} => LegacyNotificationBody::ServerInvite {
server_id,
server_name,
invited_by,
role,
},
NotificationBody::StatusChange { NotificationBody::StatusChange {
project_id, project_id,
old_status, old_status,
@@ -12,6 +12,7 @@ use crate::routes::ApiError;
use ariadne::ids::UserId; use ariadne::ids::UserId;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Notification { pub struct Notification {
@@ -34,6 +35,7 @@ pub enum NotificationType {
ProjectUpdate, ProjectUpdate,
TeamInvite, TeamInvite,
OrganizationInvite, OrganizationInvite,
ServerInvite,
StatusChange, StatusChange,
ModeratorMessage, ModeratorMessage,
LegacyMarkdown, LegacyMarkdown,
@@ -67,6 +69,7 @@ impl NotificationType {
NotificationType::ProjectUpdate => "project_update", NotificationType::ProjectUpdate => "project_update",
NotificationType::TeamInvite => "team_invite", NotificationType::TeamInvite => "team_invite",
NotificationType::OrganizationInvite => "organization_invite", NotificationType::OrganizationInvite => "organization_invite",
NotificationType::ServerInvite => "server_invite",
NotificationType::StatusChange => "status_change", NotificationType::StatusChange => "status_change",
NotificationType::ModeratorMessage => "moderator_message", NotificationType::ModeratorMessage => "moderator_message",
NotificationType::LegacyMarkdown => "legacy_markdown", NotificationType::LegacyMarkdown => "legacy_markdown",
@@ -104,6 +107,7 @@ impl NotificationType {
"project_update" => NotificationType::ProjectUpdate, "project_update" => NotificationType::ProjectUpdate,
"team_invite" => NotificationType::TeamInvite, "team_invite" => NotificationType::TeamInvite,
"organization_invite" => NotificationType::OrganizationInvite, "organization_invite" => NotificationType::OrganizationInvite,
"server_invite" => NotificationType::ServerInvite,
"status_change" => NotificationType::StatusChange, "status_change" => NotificationType::StatusChange,
"moderator_message" => NotificationType::ModeratorMessage, "moderator_message" => NotificationType::ModeratorMessage,
"legacy_markdown" => NotificationType::LegacyMarkdown, "legacy_markdown" => NotificationType::LegacyMarkdown,
@@ -156,6 +160,12 @@ pub enum NotificationBody {
team_id: TeamId, team_id: TeamId,
role: String, role: String,
}, },
ServerInvite {
server_id: Uuid,
server_name: String,
invited_by: UserId,
role: String,
},
StatusChange { StatusChange {
project_id: ProjectId, project_id: ProjectId,
old_status: ProjectStatus, old_status: ProjectStatus,
@@ -267,6 +277,9 @@ impl NotificationBody {
NotificationBody::OrganizationInvite { .. } => { NotificationBody::OrganizationInvite { .. } => {
NotificationType::OrganizationInvite NotificationType::OrganizationInvite
} }
NotificationBody::ServerInvite { .. } => {
NotificationType::ServerInvite
}
NotificationBody::StatusChange { .. } => { NotificationBody::StatusChange { .. } => {
NotificationType::StatusChange NotificationType::StatusChange
} }
@@ -418,6 +431,34 @@ impl From<DBNotification> for Notification {
}, },
], ],
), ),
NotificationBody::ServerInvite {
server_id,
server_name,
role,
..
} => (
"You have been invited to join a server!".to_string(),
format!(
"An invite has been sent for you to be {role} of {server_name}"
),
"#".to_string(),
vec![
NotificationAction {
name: "Accept".to_string(),
action_route: (
"POST".to_string(),
format!("hosting/servers/{server_id}/invite/accept"),
),
},
NotificationAction {
name: "Deny".to_string(),
action_route: (
"POST".to_string(),
format!("hosting/servers/{server_id}/invite/deny"),
),
},
],
),
NotificationBody::StatusChange { NotificationBody::StatusChange {
old_status, old_status,
new_status, new_status,
@@ -61,6 +61,10 @@ const ORGINVITE_INVITER_NAME: &str = "organizationinvite.inviter.name";
const ORGINVITE_ORG_NAME: &str = "organizationinvite.organization.name"; const ORGINVITE_ORG_NAME: &str = "organizationinvite.organization.name";
const ORGINVITE_ROLE_NAME: &str = "organizationinvite.role.name"; const ORGINVITE_ROLE_NAME: &str = "organizationinvite.role.name";
const SERVERINVITE_INVITER_NAME: &str = "inviter.name";
const SERVERINVITE_SERVER_NAME: &str = "server.name";
const SERVERINVITE_ROLE_NAME: &str = "server.role";
const STATUSCHANGE_PROJECT_NAME: &str = "statuschange.project.name"; const STATUSCHANGE_PROJECT_NAME: &str = "statuschange.project.name";
const STATUSCHANGE_OLD_STATUS: &str = "statuschange.old.status"; const STATUSCHANGE_OLD_STATUS: &str = "statuschange.old.status";
const STATUSCHANGE_NEW_STATUS: &str = "statuschange.new.status"; const STATUSCHANGE_NEW_STATUS: &str = "statuschange.new.status";
@@ -735,6 +739,27 @@ async fn collect_template_variables(
title: title.to_string(), title: title.to_string(),
}), }),
NotificationBody::ServerInvite {
server_name,
invited_by,
role,
..
} => {
let inviter = DBUser::get_id(
DBUserId(invited_by.0 as i64),
&mut *exec,
redis,
)
.await?
.ok_or_else(|| DatabaseError::Database(sqlx::Error::RowNotFound))?;
map.insert(SERVERINVITE_INVITER_NAME, inviter.username);
map.insert(SERVERINVITE_SERVER_NAME, server_name.clone());
map.insert(SERVERINVITE_ROLE_NAME, role.clone());
Ok(EmailTemplate::Static(map))
}
NotificationBody::ProjectUpdate { .. } NotificationBody::ProjectUpdate { .. }
| NotificationBody::ModeratorMessage { .. } | NotificationBody::ModeratorMessage { .. }
| NotificationBody::LegacyMarkdown { .. } | NotificationBody::LegacyMarkdown { .. }