Merge branch 'main' into feat/admin-api

# Conflicts:
#	crates/core/database/src/util/bridge/v0.rs
#	crates/core/result/src/axum.rs
#	crates/core/result/src/lib.rs
#	crates/core/result/src/rocket.rs
This commit is contained in:
ispik
2026-04-11 00:49:36 +03:00
264 changed files with 38905 additions and 2041 deletions

View File

@@ -1,10 +1,11 @@
[package]
name = "revolt-models"
version = "0.8.9"
version = "0.12.0"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
description = "Revolt Backend: API Models"
repository = "https://github.com/stoatchat/stoatchat"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -20,8 +21,8 @@ default = ["serde", "partials", "rocket"]
[dependencies]
# Core
revolt-config = { version = "0.8.9", path = "../config" }
revolt-permissions = { version = "0.8.9", path = "../permissions" }
revolt-config = { version = "0.12.0", path = "../config" }
revolt-permissions = { version = "0.12.0", path = "../permissions" }
# Utility
regex = "1.11"

View File

@@ -1,4 +1,5 @@
use super::File;
#![allow(deprecated)]
use super::{File, UserVoiceState};
use revolt_permissions::{Override, OverrideField};
use std::collections::{HashMap, HashSet};
@@ -107,44 +108,25 @@ auto_derived!(
serde(skip_serializing_if = "crate::if_false", default)
)]
nsfw: bool,
/// Voice Information for when this channel is also a voice channel
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
voice: Option<VoiceInformation>,
/// The channel's slowmode delay in seconds
#[serde(skip_serializing_if = "Option::is_none")]
slowmode: Option<u64>,
},
/// Voice channel belonging to a server
VoiceChannel {
/// Unique Id
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
id: String,
/// Id of the server this channel belongs to
server: String,
}
/// Display name of the channel
name: String,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
/// Channel description
description: Option<String>,
/// Custom icon attachment
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
icon: Option<File>,
/// Default permissions assigned to users in this channel
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
default_permissions: Option<OverrideField>,
/// Permissions assigned based on role to this channel
#[cfg_attr(
feature = "serde",
serde(
default = "HashMap::<String, OverrideField>::new",
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
)
)]
role_permissions: HashMap<String, OverrideField>,
/// Whether this channel is marked as not safe for work
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "crate::if_false", default)
)]
nsfw: bool,
},
/// Voice information for a channel
#[derive(Default)]
#[cfg_attr(feature = "validator", derive(validator::Validate))]
pub struct VoiceInformation {
/// Maximium amount of users allowed in the voice channel at once
#[cfg_attr(feature = "validator", validate(range(min = 1)))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub max_users: Option<usize>,
}
/// Partial representation of a channel
@@ -170,6 +152,10 @@ auto_derived!(
pub default_permissions: Option<OverrideField>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub last_message_id: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub voice: Option<VoiceInformation>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub slowmode: Option<u64>,
}
/// Optional fields on channel object
@@ -177,6 +163,7 @@ auto_derived!(
Description,
Icon,
DefaultPermissions,
Voice,
}
/// New webhook information
@@ -205,6 +192,13 @@ auto_derived!(
/// Whether this channel is archived
pub archived: Option<bool>,
/// Voice Information for voice channels
pub voice: Option<VoiceInformation>,
/// The channel's slow mode delay in seconds, up to 6 hours
#[cfg_attr(feature = "validator", validate(range(min = 0, max = 21600)))]
pub slowmode: Option<u64>,
/// Fields to remove from channel
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsChannel>,
@@ -260,6 +254,10 @@ auto_derived!(
/// Whether this channel is age restricted
#[serde(skip_serializing_if = "Option::is_none")]
pub nsfw: Option<bool>,
/// Voice Information for when this channel is also a voice channel
#[serde(skip_serializing_if = "Option::is_none")]
pub voice: Option<VoiceInformation>,
}
/// New default permissions
@@ -270,7 +268,7 @@ auto_derived!(
permissions: u64,
},
Field {
/// Allow / deny values to set for members in this `TextChannel` or `VoiceChannel`
/// Allow / deny values to set for members in this server channel
permissions: Override,
},
}
@@ -289,9 +287,32 @@ auto_derived!(
}
/// Voice server token response
pub struct LegacyCreateVoiceUserResponse {
pub struct CreateVoiceUserResponse {
/// Token for authenticating with the voice server
token: String,
pub token: String,
/// Url of the livekit server to connect to
pub url: String,
}
/// Voice state for a channel
pub struct ChannelVoiceState {
pub id: String,
/// The states of the users who are connected to the channel
pub participants: Vec<UserVoiceState>,
}
/// Join a voice channel
pub struct DataJoinCall {
/// Name of the node to join
pub node: Option<String>,
/// Whether to force disconnect any other existing voice connections
///
/// Useful for disconnecting on another device and joining on a new.
pub force_disconnect: Option<bool>,
/// Users which should be notified of the call starting
///
/// Only used when the user is the first one connected.
pub recipients: Option<Vec<String>>,
}
);
@@ -302,8 +323,7 @@ impl Channel {
Channel::DirectMessage { id, .. }
| Channel::Group { id, .. }
| Channel::SavedMessages { id, .. }
| Channel::TextChannel { id, .. }
| Channel::VoiceChannel { id, .. } => id,
| Channel::TextChannel { id, .. } => id,
}
}
@@ -315,9 +335,7 @@ impl Channel {
match self {
Channel::DirectMessage { .. } => None,
Channel::SavedMessages { .. } => Some("Saved Messages"),
Channel::TextChannel { name, .. }
| Channel::Group { name, .. }
| Channel::VoiceChannel { name, .. } => Some(name),
Channel::TextChannel { name, .. } | Channel::Group { name, .. } => Some(name),
}
}
}

View File

@@ -46,8 +46,13 @@ auto_derived!(
File,
/// File contains textual data and should be displayed as such
Text,
/// File is an image with specific dimensions
Image { width: usize, height: usize },
/// File is an image with specific dimensions, and may be animated
Image {
width: usize,
height: usize,
thumbhash: Option<Vec<u8>>,
animated: Option<bool>,
},
/// File is a video with specific dimensions
Video { width: usize, height: usize },
/// File is audio

View File

@@ -132,6 +132,11 @@ auto_derived!(
MessagePinned { id: String, by: String },
#[serde(rename = "message_unpinned")]
MessageUnpinned { id: String, by: String },
#[serde(rename = "call_started")]
CallStarted {
by: String,
finished_at: Option<Timestamp>,
},
}
/// Name and / or avatar override information
@@ -199,6 +204,9 @@ auto_derived!(
pub image: Option<String>,
/// Message content or system message information
pub body: String,
/// The raw body, if the body has been rendered
#[serde(skip_serializing_if = "Option::is_none")]
pub raw_body: Option<String>,
/// Unique tag, usually the channel ID
pub tag: String,
/// Timestamp at which this notification was created
@@ -383,6 +391,7 @@ auto_derived!(
);
/// Message Author Abstraction
#[derive(Clone)]
pub enum MessageAuthor<'a> {
User(&'a User),
Webhook(&'a Webhook),
@@ -445,6 +454,7 @@ impl From<SystemMessage> for String {
}
SystemMessage::MessagePinned { .. } => "Message pinned.".to_string(),
SystemMessage::MessageUnpinned { .. } => "Message unpinned.".to_string(),
SystemMessage::CallStarted { .. } => "Call started.".to_string(),
}
}
}
@@ -505,6 +515,7 @@ impl PushNotification {
icon,
image,
body,
raw_body: None,
tag: channel.id().to_string(),
timestamp,
url: format!("{}/channel/{}/{}", config.hosts.app, channel.id(), msg.id),

View File

@@ -19,6 +19,9 @@ auto_derived!(
/// Ban reason
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
pub reason: Option<String>,
/// Messages to delete in seconds
#[cfg_attr(feature = "validator", validate(range(min = 0, max = 604800)))]
pub delete_message_seconds: Option<i64>,
}
/// Just enough information to list a ban

View File

@@ -31,6 +31,14 @@ pub static RE_COLOUR: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)^(?:[a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+|(repeating-)?(linear|conic|radial)-gradient\(([a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+|\d+deg)([ ]+(\d{1,3}%|0))?(,[ ]*([a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+)([ ]+(\d{1,3}%|0))?)+\))$").unwrap()
});
fn default_true() -> bool {
true
}
fn is_true(x: &bool) -> bool {
*x
}
auto_derived_partial!(
/// Server Member
pub struct Member {
@@ -57,6 +65,13 @@ auto_derived_partial!(
/// Timestamp this member is timed out until
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub timeout: Option<Timestamp>,
/// Whether the member is server-wide voice muted
#[serde(skip_serializing_if = "is_true", default = "default_true")]
pub can_publish: bool,
/// Whether the member is server-wide voice deafened
#[serde(skip_serializing_if = "is_true", default = "default_true")]
pub can_receive: bool,
},
"PartialMember"
);
@@ -77,7 +92,10 @@ auto_derived!(
Avatar,
Roles,
Timeout,
CanReceive,
CanPublish,
JoinedAt,
VoiceChannel,
}
/// Member removal intention
@@ -124,6 +142,12 @@ auto_derived!(
pub roles: Option<Vec<String>>,
/// Timestamp this member is timed out until
pub timeout: Option<Timestamp>,
/// server-wide voice muted
pub can_publish: Option<bool>,
/// server-wide voice deafened
pub can_receive: Option<bool>,
/// voice channel to move to if already in a voice channel
pub voice_channel: Option<String>,
/// Fields to remove from channel object
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsMember>,

View File

@@ -85,6 +85,9 @@ auto_derived_partial!(
auto_derived_partial!(
/// Role
pub struct Role {
/// Unique Id
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
pub id: String,
/// Role name
pub name: String,
/// Permissions available to this role
@@ -181,6 +184,7 @@ auto_derived!(
}
/// Response after creating new role
// TODO: remove this in favor of just Role
pub struct NewRoleResponse {
/// Id of the role
pub id: String,
@@ -248,6 +252,9 @@ auto_derived!(
/// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg).
pub analytics: Option<bool>,
/// User id of the new owner
pub owner: Option<String>,
/// Fields to remove from server object
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsServer>,

View File

@@ -1,3 +1,4 @@
use iso8601_timestamp::Timestamp;
use once_cell::sync::Lazy;
use regex::Regex;
@@ -281,6 +282,19 @@ auto_derived!(
}
);
auto_derived_partial!(
/// Voice State information for a user
pub struct UserVoiceState {
pub id: String,
pub joined_at: Timestamp,
pub is_receiving: bool,
pub is_publishing: bool,
pub screensharing: bool,
pub camera: bool,
},
"PartialUserVoiceState"
);
pub trait CheckRelationship {
fn with(&self, user: &str) -> RelationshipStatus;
}