mirror of
https://github.com/stoatchat/stoatchat.git
synced 2026-07-06 03:06:04 +00:00
Compare commits
7 Commits
v0.13.6
...
feat/error
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a60aecdb3 | ||
|
|
789dedd9f1 | ||
|
|
d7cf809424 | ||
|
|
249a4818fc | ||
|
|
46e127ccd2 | ||
|
|
cf4fe859bf | ||
|
|
3d6f39a0eb |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -6511,6 +6511,7 @@ dependencies = [
|
||||
"revolt_rocket_okapi",
|
||||
"rocket",
|
||||
"schemars",
|
||||
"sentry",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa",
|
||||
|
||||
@@ -26,3 +26,7 @@ disallowed-methods = [
|
||||
# Prefer to use Object::delete(&self)
|
||||
"revolt_database::models::bots::ops::AbstractBots::delete_bot",
|
||||
]
|
||||
|
||||
disallowed-types = [
|
||||
"rocket::serde::json::Json",
|
||||
]
|
||||
@@ -37,7 +37,7 @@ async-std = { version = "1.8.0", features = [
|
||||
|
||||
# core
|
||||
authifier = { version = "1.0.15" }
|
||||
revolt-result = { path = "../core/result" }
|
||||
revolt-result = { path = "../core/result", features = ["sentry"] }
|
||||
revolt-models = { path = "../core/models" }
|
||||
revolt-config = { path = "../core/config" }
|
||||
revolt-database = { path = "../core/database" }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use async_tungstenite::tungstenite::{handshake, Message};
|
||||
use futures::channel::oneshot::Sender;
|
||||
use revolt_database::events::client::ReadyPayloadFields;
|
||||
use revolt_result::{create_error, Result};
|
||||
use revolt_result::{create_error, Result, ToRevoltError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Enumeration of supported protocol formats
|
||||
@@ -38,16 +38,22 @@ impl ProtocolConfiguration {
|
||||
match self.format {
|
||||
ProtocolFormat::Json => {
|
||||
if let Message::Text(text) = msg {
|
||||
serde_json::from_str(text).map_err(|_| create_error!(InternalError))
|
||||
// Log the error in-case we make a breaking change to the payload
|
||||
|
||||
serde_json::from_str(text)
|
||||
.capture_error()
|
||||
.map_err(|_| create_error!(UnprocessableEntity))
|
||||
} else {
|
||||
Err(create_error!(InternalError))
|
||||
Err(create_error!(UnprocessableEntity))
|
||||
}
|
||||
}
|
||||
ProtocolFormat::Msgpack => {
|
||||
if let Message::Binary(buf) = msg {
|
||||
rmp_serde::from_slice(buf).map_err(|_| create_error!(InternalError))
|
||||
rmp_serde::from_slice(buf)
|
||||
.capture_error()
|
||||
.map_err(|_| create_error!(UnprocessableEntity))
|
||||
} else {
|
||||
Err(create_error!(InternalError))
|
||||
Err(create_error!(UnprocessableEntity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use futures::{
|
||||
FutureExt, SinkExt, StreamExt, TryStreamExt,
|
||||
};
|
||||
use redis_kiss::{PayloadType, REDIS_PAYLOAD_TYPE, REDIS_URI};
|
||||
use revolt_config::report_internal_error;
|
||||
use revolt_database::{
|
||||
events::{client::EventV1, server::ClientMessage},
|
||||
iso8601_timestamp::Timestamp,
|
||||
@@ -27,7 +26,7 @@ use async_std::{
|
||||
sync::{Mutex, RwLock},
|
||||
task::spawn,
|
||||
};
|
||||
use revolt_result::create_error;
|
||||
use revolt_result::{create_error, ToRevoltError};
|
||||
use sentry::Level;
|
||||
|
||||
use crate::config::{ProtocolConfiguration, WebsocketHandshakeCallback};
|
||||
@@ -110,21 +109,21 @@ pub async fn client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
|
||||
let user_id = state.cache.user_id.clone();
|
||||
|
||||
// Notify socket we have authenticated.
|
||||
if report_internal_error!(write.send(config.encode(&EventV1::Authenticated)).await).is_err() {
|
||||
if write.send(config.encode(&EventV1::Authenticated)).await.to_internal_error().is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Download required data to local cache and send Ready payload.
|
||||
let ready_payload = match report_internal_error!(
|
||||
state
|
||||
let ready_payload = match state
|
||||
.generate_ready_payload(db, config.get_ready_payload_fields())
|
||||
.await
|
||||
) {
|
||||
.to_internal_error()
|
||||
{
|
||||
Ok(ready_payload) => ready_payload,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if report_internal_error!(write.send(config.encode(&ready_payload)).await).is_err() {
|
||||
if write.send(config.encode(&ready_payload)).await.to_internal_error().is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -219,14 +218,15 @@ async fn listener(
|
||||
write: &Mutex<WsWriter>,
|
||||
) {
|
||||
let redis_config = RedisConfig::from_url(&REDIS_URI).unwrap();
|
||||
let subscriber = match report_internal_error!(
|
||||
fred::types::Builder::from_config(redis_config).build_subscriber_client()
|
||||
) {
|
||||
let subscriber = match fred::types::Builder::from_config(redis_config)
|
||||
.build_subscriber_client()
|
||||
.to_internal_error()
|
||||
{
|
||||
Ok(subscriber) => subscriber,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if report_internal_error!(subscriber.init().await).is_err() {
|
||||
if subscriber.init().await.to_internal_error().is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -249,13 +249,13 @@ async fn listener(
|
||||
// Check for state changes for subscriptions.
|
||||
match state.apply_state().await {
|
||||
SubscriptionStateChange::Reset => {
|
||||
if report_internal_error!(subscriber.unsubscribe_all().await).is_err() {
|
||||
if subscriber.unsubscribe_all().await.to_internal_error().is_err() {
|
||||
break 'out;
|
||||
}
|
||||
|
||||
let subscribed = state.subscribed.read().await;
|
||||
for id in subscribed.iter() {
|
||||
if report_internal_error!(subscriber.subscribe(id).await).is_err() {
|
||||
if subscriber.subscribe(id).await.to_internal_error().is_err() {
|
||||
break 'out;
|
||||
}
|
||||
}
|
||||
@@ -268,7 +268,7 @@ async fn listener(
|
||||
#[cfg(debug_assertions)]
|
||||
info!("{addr:?} unsubscribing from {id}");
|
||||
|
||||
if report_internal_error!(subscriber.unsubscribe(id).await).is_err() {
|
||||
if subscriber.unsubscribe(id).await.to_internal_error().is_err() {
|
||||
break 'out;
|
||||
}
|
||||
}
|
||||
@@ -277,7 +277,7 @@ async fn listener(
|
||||
#[cfg(debug_assertions)]
|
||||
info!("{addr:?} subscribing to {id}");
|
||||
|
||||
if report_internal_error!(subscriber.subscribe(id).await).is_err() {
|
||||
if subscriber.subscribe(id).await.to_internal_error().is_err() {
|
||||
break 'out;
|
||||
}
|
||||
}
|
||||
@@ -302,7 +302,7 @@ async fn listener(
|
||||
_ = t2 => {},
|
||||
message = t1 => {
|
||||
// Handle incoming events.
|
||||
let message = match report_internal_error!(message) {
|
||||
let message = match message.to_internal_error() {
|
||||
Ok(message) => message,
|
||||
Err(_) => break 'out
|
||||
};
|
||||
@@ -311,15 +311,15 @@ async fn listener(
|
||||
PayloadType::Json => message
|
||||
.value
|
||||
.as_str()
|
||||
.and_then(|s| report_internal_error!(serde_json::from_str::<EventV1>(s.as_ref())).ok()),
|
||||
.and_then(|s| serde_json::from_str::<EventV1>(s.as_ref()).to_internal_error().ok()),
|
||||
PayloadType::Msgpack => message
|
||||
.value
|
||||
.as_bytes()
|
||||
.and_then(|b| report_internal_error!(rmp_serde::from_slice::<EventV1>(b)).ok()),
|
||||
.and_then(|b| rmp_serde::from_slice::<EventV1>(b).to_internal_error().ok()),
|
||||
PayloadType::Bincode => message
|
||||
.value
|
||||
.as_bytes()
|
||||
.and_then(|b| report_internal_error!(bincode::deserialize::<EventV1>(b)).ok()),
|
||||
.and_then(|b| bincode::deserialize::<EventV1>(b).to_internal_error().ok()),
|
||||
};
|
||||
|
||||
let Some(mut event) = event else {
|
||||
@@ -379,7 +379,7 @@ async fn listener(
|
||||
}
|
||||
}
|
||||
|
||||
report_internal_error!(subscriber.quit().await).ok();
|
||||
subscriber.quit().await.to_internal_error().ok();
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use bson::to_document;
|
||||
use bson::Document;
|
||||
use revolt_config::report_internal_error;
|
||||
use revolt_result::Result;
|
||||
use revolt_result::ToRevoltError;
|
||||
|
||||
use crate::File;
|
||||
use crate::FileUsedFor;
|
||||
@@ -106,7 +106,7 @@ impl AbstractAttachments for MongoDb {
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"used_for": report_internal_error!(to_document(&used_for))?,
|
||||
"used_for": to_document(&used_for).to_internal_error()?,
|
||||
"uploader_id": uploader_id
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,7 +10,6 @@ use revolt_models::v0::{
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, PermissionValue};
|
||||
use revolt_result::{ErrorType, Result};
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
@@ -489,31 +488,28 @@ impl Message {
|
||||
| Channel::VoiceChannel { ref server, .. } => {
|
||||
let mentions_vec = Vec::from_iter(user_mentions.iter().cloned());
|
||||
|
||||
let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await;
|
||||
if let Ok(valid_members) = valid_members {
|
||||
let valid_mentions = HashSet::<&String, RandomState>::from_iter(
|
||||
valid_members.iter().map(|m| &m.id.user),
|
||||
);
|
||||
let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await?;
|
||||
|
||||
user_mentions.retain(|m| valid_mentions.contains(m)); // quick pass, validate mentions are in the server
|
||||
let valid_mentions = HashSet::<&String, RandomState>::from_iter(
|
||||
valid_members.iter().map(|m| &m.id.user),
|
||||
);
|
||||
|
||||
if !user_mentions.is_empty() {
|
||||
// if there are still mentions, drill down to a channel-level
|
||||
let member_channel_view_perms =
|
||||
BulkDatabasePermissionQuery::from_server_id(db, server)
|
||||
.await
|
||||
.channel(&channel)
|
||||
.members(&valid_members)
|
||||
.members_can_see_channel()
|
||||
.await;
|
||||
user_mentions.retain(|m| valid_mentions.contains(m)); // quick pass, validate mentions are in the server
|
||||
|
||||
user_mentions
|
||||
.retain(|m| *member_channel_view_perms.get(m).unwrap_or(&false));
|
||||
}
|
||||
} else {
|
||||
revolt_config::capture_error(&valid_members.unwrap_err());
|
||||
return Err(create_error!(InternalError));
|
||||
if !user_mentions.is_empty() {
|
||||
// if there are still mentions, drill down to a channel-level
|
||||
let member_channel_view_perms =
|
||||
BulkDatabasePermissionQuery::from_server_id(db, server)
|
||||
.await
|
||||
.channel(&channel)
|
||||
.members(&valid_members)
|
||||
.members_can_see_channel()
|
||||
.await;
|
||||
|
||||
user_mentions
|
||||
.retain(|m| *member_channel_view_perms.get(m).unwrap_or(&false));
|
||||
}
|
||||
|
||||
}
|
||||
Channel::SavedMessages { .. } => {
|
||||
user_mentions.clear();
|
||||
@@ -717,12 +713,6 @@ impl Message {
|
||||
|
||||
/// Create text embed from sendable embed
|
||||
pub async fn create_embed(&self, db: &Database, embed: SendableEmbed) -> Result<Embed> {
|
||||
embed.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
let media = if let Some(id) = embed.media {
|
||||
Some(File::use_attachment(db, &id, &self.id, &self.author).await?)
|
||||
} else {
|
||||
|
||||
@@ -705,6 +705,8 @@ impl User {
|
||||
duration_days: Option<usize>,
|
||||
reason: Option<Vec<String>>,
|
||||
) -> Result<()> {
|
||||
// TODO: authifier Error should implement Error
|
||||
|
||||
let authifier = db.clone().to_authifier().await;
|
||||
let mut account = authifier
|
||||
.database
|
||||
|
||||
@@ -38,6 +38,7 @@ impl<'r> FromRequest<'r> for User {
|
||||
if let Some(user) = user {
|
||||
Outcome::Success(user.clone())
|
||||
} else {
|
||||
request.local_cache(|| Some(create_error!(InvalidSession)));
|
||||
Outcome::Error((Status::Unauthorized, authifier::Error::InvalidSession))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@ impl<'r> FromRequest<'r> for IdempotencyKey {
|
||||
let idempotency = IdempotencyKey { key };
|
||||
let mut cache = TOKEN_CACHE.lock().await;
|
||||
if cache.get(&idempotency.key).is_some() {
|
||||
request.local_cache(|| Some(create_error!(DuplicateNonce)));
|
||||
|
||||
return Outcome::Error((Status::Conflict, create_error!(DuplicateNonce)));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ imagesize = "0.13.0"
|
||||
tempfile = "3.12.0"
|
||||
|
||||
base64 = "0.22.1"
|
||||
aes-gcm = "0.10.3"
|
||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
||||
typenum = "1.17.0"
|
||||
|
||||
aws-config = "1.5.5"
|
||||
@@ -23,7 +23,7 @@ aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
|
||||
revolt-config = { version = "0.8.8", path = "../config", features = [
|
||||
"report-macros",
|
||||
] }
|
||||
revolt-result = { version = "0.8.8", path = "../result" }
|
||||
revolt-result = { version = "0.8.8", path = "../result", features = ["sentry"] }
|
||||
|
||||
# image processing
|
||||
jxl-oxide = "0.8.1"
|
||||
|
||||
@@ -6,7 +6,7 @@ use aes_gcm::{
|
||||
};
|
||||
use image::{DynamicImage, ImageBuffer};
|
||||
use revolt_config::{config, report_internal_error, FilesS3};
|
||||
use revolt_result::{create_error, Result};
|
||||
use revolt_result::{create_error, Result, ToRevoltError};
|
||||
|
||||
use aws_sdk_s3::{
|
||||
config::{Credentials, Region},
|
||||
@@ -55,13 +55,12 @@ pub async fn fetch_from_s3(bucket_id: &str, path: &str, nonce: &str) -> Result<V
|
||||
|
||||
// Send a request for the file
|
||||
let mut obj =
|
||||
report_internal_error!(client.get_object().bucket(bucket_id).key(path).send().await)?;
|
||||
client.get_object().bucket(bucket_id).key(path).send().await.to_internal_error()?;
|
||||
|
||||
// Read the file from remote
|
||||
let mut buf = vec![];
|
||||
while let Some(bytes) = obj.body.next().await {
|
||||
let data = report_internal_error!(bytes)?;
|
||||
report_internal_error!(buf.write_all(&data))?;
|
||||
buf.write_all(&bytes.to_internal_error()?).to_internal_error()?;
|
||||
// is there a more efficient way to do this?
|
||||
// we just want the Vec<u8>
|
||||
}
|
||||
@@ -78,7 +77,7 @@ pub async fn fetch_from_s3(bucket_id: &str, path: &str, nonce: &str) -> Result<V
|
||||
// Decrypt the file
|
||||
create_cipher(&config.files.encryption_key)
|
||||
.decrypt_in_place(nonce, b"", &mut buf)
|
||||
.map_err(|_| create_error!(InternalError))?;
|
||||
.to_internal_error()?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
@@ -97,18 +96,17 @@ pub async fn upload_to_s3(bucket_id: &str, path: &str, buf: &[u8]) -> Result<Str
|
||||
// Encrypt the file in place
|
||||
create_cipher(&config.files.encryption_key)
|
||||
.encrypt_in_place(&nonce, b"", &mut buf)
|
||||
.map_err(|_| create_error!(InternalError))?;
|
||||
.to_internal_error()?;
|
||||
|
||||
// Upload the file to remote
|
||||
report_internal_error!(
|
||||
client
|
||||
.put_object()
|
||||
.bucket(bucket_id)
|
||||
.key(path)
|
||||
.body(buf.into())
|
||||
.send()
|
||||
.await
|
||||
)?;
|
||||
client
|
||||
.put_object()
|
||||
.bucket(bucket_id)
|
||||
.key(path)
|
||||
.body(buf.into())
|
||||
.send()
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
Ok(BASE64_STANDARD.encode(nonce))
|
||||
}
|
||||
@@ -118,14 +116,13 @@ pub async fn delete_from_s3(bucket_id: &str, path: &str) -> Result<()> {
|
||||
let config = config().await;
|
||||
let client = create_client(config.files.s3);
|
||||
|
||||
report_internal_error!(
|
||||
client
|
||||
.delete_object()
|
||||
.bucket(bucket_id)
|
||||
.key(path)
|
||||
.send()
|
||||
.await
|
||||
)?;
|
||||
client
|
||||
.delete_object()
|
||||
.bucket(bucket_id)
|
||||
.key(path)
|
||||
.send()
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -145,8 +142,7 @@ pub fn image_size(f: &NamedTempFile) -> Option<(usize, usize)> {
|
||||
pub fn image_size_vec(v: &[u8], mime: &str) -> Option<(usize, usize)> {
|
||||
match mime {
|
||||
"image/svg+xml" => {
|
||||
let tree =
|
||||
report_internal_error!(usvg::Tree::from_data(v, &Default::default())).ok()?;
|
||||
let tree = usvg::Tree::from_data(v, &Default::default()).to_internal_error().ok()?;
|
||||
|
||||
let size = tree.size();
|
||||
Some((size.width() as usize, size.height() as usize))
|
||||
@@ -221,9 +217,9 @@ pub fn decode_image<R: Read + BufRead + Seek>(reader: &mut R, mime: &str) -> Res
|
||||
"image/svg+xml" => {
|
||||
// usvg doesn't support Read trait so copy to buffer
|
||||
let mut buf = Vec::new();
|
||||
report_internal_error!(reader.read_to_end(&mut buf))?;
|
||||
reader.read_to_end(&mut buf).to_internal_error()?;
|
||||
|
||||
let tree = report_internal_error!(usvg::Tree::from_data(&buf, &Default::default()))?;
|
||||
let tree = usvg::Tree::from_data(&buf, &Default::default()).to_internal_error()?;
|
||||
let size = tree.size();
|
||||
let mut pixmap = Pixmap::new(size.width() as u32, size.height() as u32)
|
||||
.ok_or_else(|| create_error!(ImageProcessingFailed))?;
|
||||
@@ -241,10 +237,11 @@ pub fn decode_image<R: Read + BufRead + Seek>(reader: &mut R, mime: &str) -> Res
|
||||
))
|
||||
}
|
||||
// Check if we can read using image-rs crate
|
||||
_ => report_internal_error!(report_internal_error!(
|
||||
image::ImageReader::new(reader).with_guessed_format()
|
||||
)?
|
||||
.decode()),
|
||||
_ => image::ImageReader::new(reader)
|
||||
.with_guessed_format()
|
||||
.to_internal_error()?
|
||||
.decode()
|
||||
.to_internal_error()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ mod embeds;
|
||||
mod emojis;
|
||||
mod files;
|
||||
mod messages;
|
||||
mod onboard;
|
||||
mod policy_changes;
|
||||
mod safety_reports;
|
||||
mod server_bans;
|
||||
@@ -24,6 +25,7 @@ pub use embeds::*;
|
||||
pub use emojis::*;
|
||||
pub use files::*;
|
||||
pub use messages::*;
|
||||
pub use onboard::*;
|
||||
pub use policy_changes::*;
|
||||
pub use safety_reports::*;
|
||||
pub use server_bans::*;
|
||||
|
||||
15
crates/core/models/src/v0/onboard.rs
Normal file
15
crates/core/models/src/v0/onboard.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
auto_derived!(
|
||||
/// # New User Data
|
||||
#[derive(validator::Validate)]
|
||||
pub struct DataOnboard {
|
||||
/// New username which will be used to identify the user on the platform
|
||||
#[validate(length(min = 2, max = 32), regex = "super::RE_USERNAME")]
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
/// # Onboarding Status
|
||||
pub struct DataHello {
|
||||
/// Whether onboarding is required
|
||||
pub onboarding: bool,
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,17 @@
|
||||
use iso8601_timestamp::Timestamp;
|
||||
|
||||
auto_derived!(
|
||||
/// # Report Data
|
||||
#[derive(validator::Validate)]
|
||||
pub struct DataReportContent {
|
||||
/// Content being reported
|
||||
pub content: ReportedContent,
|
||||
/// Additional report description
|
||||
#[validate(length(min = 0, max = 1000))]
|
||||
#[serde(default)]
|
||||
pub additional_context: String,
|
||||
}
|
||||
|
||||
/// User-generated platform moderation report
|
||||
pub struct Report {
|
||||
/// Unique Id
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Channel, File, RE_COLOUR};
|
||||
use super::{Channel, File, Member, User, RE_COLOUR};
|
||||
|
||||
use revolt_permissions::{Override, OverrideField};
|
||||
use std::collections::HashMap;
|
||||
@@ -293,4 +293,22 @@ auto_derived!(
|
||||
pub struct DataEditRoleRanks {
|
||||
pub ranks: Vec<String>,
|
||||
}
|
||||
|
||||
/// # Query Parameters
|
||||
#[derive(FromForm)]
|
||||
pub struct OptionsQueryMembers {
|
||||
/// String to search for
|
||||
pub query: String,
|
||||
|
||||
/// Discourage use of this API
|
||||
pub experimental_api: bool,
|
||||
}
|
||||
|
||||
/// # Query members by name
|
||||
pub struct MemberQueryResponse {
|
||||
/// List of members
|
||||
pub members: Vec<Member>,
|
||||
/// List of users
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -275,6 +275,17 @@ auto_derived!(
|
||||
/// Username and discriminator combo separated by #
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
/// # Username Information
|
||||
#[derive(Validate)]
|
||||
pub struct DataChangeUsername {
|
||||
/// New username
|
||||
#[validate(length(min = 2, max = 32), regex = "super::RE_USERNAME")]
|
||||
pub username: String,
|
||||
/// Current account password
|
||||
#[validate(length(min = 8, max = 1024))]
|
||||
pub password: String,
|
||||
}
|
||||
);
|
||||
|
||||
pub trait CheckRelationship {
|
||||
|
||||
@@ -15,6 +15,7 @@ utoipa = ["dep:utoipa"]
|
||||
rocket = ["dep:rocket", "dep:serde_json"]
|
||||
axum = ["dep:axum", "dep:serde_json"]
|
||||
okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi", "schemas"]
|
||||
sentry = ["dep:sentry"]
|
||||
|
||||
default = ["serde"]
|
||||
|
||||
@@ -34,3 +35,6 @@ revolt_okapi = { version = "0.9.1", optional = true }
|
||||
|
||||
# Axum
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
|
||||
# Sentry
|
||||
sentry = { version = "0.31.5", optional = true }
|
||||
@@ -2,10 +2,9 @@ use axum::{http::StatusCode, response::IntoResponse, Json};
|
||||
|
||||
use crate::{Error, ErrorType};
|
||||
|
||||
/// HTTP response builder for Error enum
|
||||
impl IntoResponse for Error {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let status = match self.error_type {
|
||||
impl Error {
|
||||
pub fn axum_status(&self) -> StatusCode {
|
||||
match self.error_type {
|
||||
ErrorType::LabelMe => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
||||
ErrorType::AlreadyOnboarded => StatusCode::FORBIDDEN,
|
||||
@@ -70,11 +69,15 @@ impl IntoResponse for Error {
|
||||
ErrorType::InvalidProperty => StatusCode::BAD_REQUEST,
|
||||
ErrorType::InvalidSession => StatusCode::UNAUTHORIZED,
|
||||
ErrorType::NotAuthenticated => StatusCode::UNAUTHORIZED,
|
||||
ErrorType::Conflict => StatusCode::CONFLICT,
|
||||
ErrorType::DuplicateNonce => StatusCode::CONFLICT,
|
||||
ErrorType::VosoUnavailable => StatusCode::BAD_REQUEST,
|
||||
ErrorType::NotFound => StatusCode::NOT_FOUND,
|
||||
ErrorType::NoEffect => StatusCode::OK,
|
||||
ErrorType::FailedValidation { .. } => StatusCode::BAD_REQUEST,
|
||||
ErrorType::IOError => StatusCode::BAD_REQUEST,
|
||||
ErrorType::UnprocessableEntity => StatusCode::UNPROCESSABLE_ENTITY,
|
||||
ErrorType::DeserializationError { .. } => StatusCode::UNPROCESSABLE_ENTITY,
|
||||
ErrorType::FailedValidation { .. } => StatusCode::UNPROCESSABLE_ENTITY,
|
||||
ErrorType::InvalidFlagValue => StatusCode::BAD_REQUEST,
|
||||
ErrorType::FeatureDisabled { .. } => StatusCode::BAD_REQUEST,
|
||||
|
||||
@@ -84,8 +87,13 @@ impl IntoResponse for Error {
|
||||
ErrorType::FileTypeNotAllowed => StatusCode::BAD_REQUEST,
|
||||
ErrorType::ImageProcessingFailed => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ErrorType::NoEmbedData => StatusCode::BAD_REQUEST,
|
||||
};
|
||||
|
||||
(status, Json(&self)).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP response builder for Error enum
|
||||
impl IntoResponse for Error {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
(self.axum_status(), Json(&self)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::panic::Location;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -151,9 +152,15 @@ pub enum ErrorType {
|
||||
InvalidSession,
|
||||
InvalidFlagValue,
|
||||
NotAuthenticated,
|
||||
Conflict,
|
||||
DuplicateNonce,
|
||||
NotFound,
|
||||
NoEffect,
|
||||
IOError,
|
||||
UnprocessableEntity,
|
||||
DeserializationError {
|
||||
error: String,
|
||||
},
|
||||
FailedValidation {
|
||||
error: String,
|
||||
},
|
||||
@@ -174,7 +181,7 @@ pub enum ErrorType {
|
||||
// ? Feature flag disabled in the config
|
||||
FeatureDisabled {
|
||||
feature: String,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -197,6 +204,62 @@ macro_rules! create_database_error {
|
||||
};
|
||||
}
|
||||
|
||||
pub trait ToRevoltError<T>: Sized {
|
||||
fn capture_error(self) -> Self;
|
||||
|
||||
#[track_caller]
|
||||
fn to_internal_error(self) -> Result<T, Error>;
|
||||
}
|
||||
|
||||
impl<T, E: std::error::Error> ToRevoltError<T> for Result<T, E> {
|
||||
fn capture_error(self) -> Self {
|
||||
|
||||
#[allow(unused_variables)]
|
||||
self.inspect_err(|e| {
|
||||
#[cfg(feature = "sentry")]
|
||||
sentry::capture_error(e);
|
||||
})
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn to_internal_error(self) -> Result<T, Error> {
|
||||
let loc = Location::caller();
|
||||
|
||||
self
|
||||
.capture_error()
|
||||
.map_err(|_| {
|
||||
Error {
|
||||
error_type: ErrorType::InternalError,
|
||||
location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::error::Error> ToRevoltError<T> for Option<T> {
|
||||
fn capture_error(self) -> Self {
|
||||
#[allow(unused_variables)]
|
||||
self.inspect(|e| {
|
||||
#[cfg(feature = "sentry")]
|
||||
sentry::capture_error(e);
|
||||
})
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn to_internal_error(self) -> Result<T, Error> {
|
||||
let loc = Location::caller();
|
||||
|
||||
self
|
||||
.capture_error()
|
||||
.ok_or_else(|| {
|
||||
Error {
|
||||
error_type: ErrorType::InternalError,
|
||||
location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ErrorType;
|
||||
|
||||
@@ -8,10 +8,9 @@ use rocket::{
|
||||
|
||||
use crate::{Error, ErrorType};
|
||||
|
||||
/// HTTP response builder for Error enum
|
||||
impl<'r> Responder<'r, 'static> for Error {
|
||||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
||||
let status = match self.error_type {
|
||||
impl Error {
|
||||
pub fn rocket_status(&self) -> Status {
|
||||
match self.error_type {
|
||||
ErrorType::LabelMe => Status::InternalServerError,
|
||||
|
||||
ErrorType::AlreadyOnboarded => Status::Forbidden,
|
||||
@@ -77,11 +76,15 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
ErrorType::InvalidProperty => Status::BadRequest,
|
||||
ErrorType::InvalidSession => Status::Unauthorized,
|
||||
ErrorType::NotAuthenticated => Status::Unauthorized,
|
||||
ErrorType::Conflict => Status::Conflict,
|
||||
ErrorType::DuplicateNonce => Status::Conflict,
|
||||
ErrorType::VosoUnavailable => Status::BadRequest,
|
||||
ErrorType::NotFound => Status::NotFound,
|
||||
ErrorType::NoEffect => Status::Ok,
|
||||
ErrorType::FailedValidation { .. } => Status::BadRequest,
|
||||
ErrorType::IOError => Status::BadRequest,
|
||||
ErrorType::UnprocessableEntity => Status::UnprocessableEntity,
|
||||
ErrorType::DeserializationError { .. } => Status::UnprocessableEntity,
|
||||
ErrorType::FailedValidation { .. } => Status::UnprocessableEntity,
|
||||
ErrorType::FeatureDisabled { .. } => Status::BadRequest,
|
||||
|
||||
ErrorType::ProxyError => Status::BadRequest,
|
||||
@@ -90,8 +93,13 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
ErrorType::FileTypeNotAllowed => Status::BadRequest,
|
||||
ErrorType::ImageProcessingFailed => Status::InternalServerError,
|
||||
ErrorType::NoEmbedData => Status::BadRequest,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP response builder for Error enum
|
||||
impl<'r> Responder<'r, 'static> for Error {
|
||||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
||||
// Serialize the error data structure into JSON.
|
||||
let string = serde_json::to_string(&self).unwrap();
|
||||
|
||||
@@ -99,7 +107,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
Response::build()
|
||||
.sized_body(string.len(), Cursor::new(string))
|
||||
.header(ContentType::new("application", "json"))
|
||||
.status(status)
|
||||
.status(self.rocket_status())
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
use revolt_config::configure;
|
||||
use revolt_database::DatabaseInfo;
|
||||
use std::{future::Future, time::Duration};
|
||||
|
||||
use revolt_config::{configure, capture_error};
|
||||
use revolt_database::{Database, DatabaseInfo};
|
||||
use revolt_result::Result;
|
||||
use tasks::{file_deletion, prune_dangling_files};
|
||||
use tokio::try_join;
|
||||
use tokio::{join, time::sleep};
|
||||
|
||||
pub mod tasks;
|
||||
|
||||
pub async fn cron_task_wrapper<Fut: Future<Output = Result<()>>>(func: fn(Database) -> Fut, db: Database) {
|
||||
loop {
|
||||
if let Err(error) = func(db.clone()).await {
|
||||
log::error!("cron task failed unexpectidly: {error:?}\nRetrying after 60s");
|
||||
capture_error(&error);
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
async fn main() {
|
||||
configure!(crond);
|
||||
|
||||
let db = DatabaseInfo::Auto.connect().await.expect("database");
|
||||
try_join!(
|
||||
file_deletion::task(db.clone()),
|
||||
prune_dangling_files::task(db)
|
||||
)
|
||||
.map(|_| ())
|
||||
|
||||
join!(
|
||||
cron_task_wrapper(file_deletion::task, db.clone()),
|
||||
cron_task_wrapper(prune_dangling_files::task, db.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,24 +123,26 @@ impl AsyncConsumer for AckConsumer {
|
||||
token: session.subscription.as_ref().unwrap().auth.clone(),
|
||||
extras: Default::default(),
|
||||
};
|
||||
let raw_service_payload = serde_json::to_string(&service_payload);
|
||||
|
||||
if let Ok(p) = raw_service_payload {
|
||||
let args = BasicPublishArguments::new(
|
||||
config.pushd.exchange.as_str(),
|
||||
config.pushd.apn.queue.as_str(),
|
||||
)
|
||||
.finish();
|
||||
match serde_json::to_string(&service_payload) {
|
||||
Ok(p) => {
|
||||
let args = BasicPublishArguments::new(
|
||||
config.pushd.exchange.as_str(),
|
||||
config.pushd.apn.queue.as_str(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
log::debug!(
|
||||
"Publishing ack to apn session {}",
|
||||
session.subscription.as_ref().unwrap().auth
|
||||
);
|
||||
log::debug!(
|
||||
"Publishing ack to apn session {}",
|
||||
session.subscription.as_ref().unwrap().auth
|
||||
);
|
||||
|
||||
publish_message(self, p.into(), args).await;
|
||||
} else {
|
||||
log::warn!("Failed to serialize ack badge update payload!");
|
||||
revolt_config::capture_error(&raw_service_payload.unwrap_err());
|
||||
publish_message(self, p.into(), args).await;
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to serialize ack badge update payload!");
|
||||
revolt_config::capture_error(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ revolt-models = { path = "../core/models", features = [
|
||||
"rocket",
|
||||
] }
|
||||
revolt-presence = { path = "../core/presence" }
|
||||
revolt-result = { path = "../core/result", features = ["rocket", "okapi"] }
|
||||
revolt-result = { path = "../core/result", features = ["rocket", "okapi", "sentry"] }
|
||||
revolt-permissions = { path = "../core/permissions", features = ["schemas"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -135,6 +135,7 @@ pub async fn web() -> Rocket<Build> {
|
||||
.manage(cors.clone())
|
||||
.attach(util::ratelimiter::RatelimitFairing)
|
||||
.attach(cors)
|
||||
.register("/", util::catchers::all_catchers())
|
||||
.configure(rocket::Config {
|
||||
limits: rocket::data::Limits::default().limit("string", 5.megabytes()),
|
||||
address: Ipv4Addr::new(0, 0, 0, 0).into(),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::util::json::{Json, Validate};
|
||||
use revolt_database::{Bot, Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use revolt_result::Result;
|
||||
use rocket::State;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Create Bot
|
||||
///
|
||||
@@ -13,14 +12,9 @@ use validator::Validate;
|
||||
pub async fn create_bot(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
info: Json<v0::DataCreateBot>,
|
||||
info: Validate<Json<v0::DataCreateBot>>,
|
||||
) -> Result<Json<v0::BotWithUserResponse>> {
|
||||
let info = info.into_inner();
|
||||
info.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let info = info.into_inner().into_inner();
|
||||
|
||||
let (bot, user) = Bot::create(db, info.name, &user, None).await?;
|
||||
Ok(Json(v0::BotWithUserResponse {
|
||||
|
||||
@@ -3,8 +3,7 @@ use revolt_models::v0::{self, DataEditBot};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::State;
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use validator::Validate;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Edit Bot
|
||||
///
|
||||
@@ -15,14 +14,9 @@ pub async fn edit_bot(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
data: Json<DataEditBot>,
|
||||
data: Validate<Json<DataEditBot>>,
|
||||
) -> Result<Json<v0::BotWithUserResponse>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let mut bot = target.as_bot(db).await?;
|
||||
if bot.owner != user.id {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use revolt_database::{util::reference::Reference, Database, User};
|
||||
use revolt_models::v0::FetchBotResponse;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Bot
|
||||
///
|
||||
|
||||
@@ -2,7 +2,7 @@ use futures::future::join_all;
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0::OwnedBotsResponse;
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Fetch Owned Bots
|
||||
|
||||
@@ -2,7 +2,7 @@ use revolt_database::{util::reference::Reference, Database, User};
|
||||
use revolt_models::v0::PublicBot;
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Fetch Public Bot
|
||||
|
||||
@@ -8,7 +8,7 @@ use revolt_permissions::{
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::State;
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
/// # Invite Bot
|
||||
|
||||
@@ -5,8 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Edit Channel
|
||||
///
|
||||
@@ -18,14 +18,9 @@ pub async fn edit(
|
||||
amqp: &State<AMQP>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataEditChannel>,
|
||||
data: Validate<Json<v0::DataEditChannel>>,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let mut channel = target.as_channel(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
|
||||
|
||||
@@ -6,7 +6,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Channel
|
||||
///
|
||||
|
||||
@@ -2,9 +2,8 @@ use revolt_database::{Channel, Database, RelationshipStatus, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Create Group
|
||||
///
|
||||
@@ -14,18 +13,13 @@ use validator::Validate;
|
||||
pub async fn create_group(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
data: Json<v0::DataCreateGroup>,
|
||||
data: Validate<Json<v0::DataCreateGroup>>,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(create_error!(IsBot));
|
||||
}
|
||||
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
for target in &data.users {
|
||||
match user.relationship_with(target) {
|
||||
|
||||
@@ -6,7 +6,8 @@ use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Create Invite
|
||||
///
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Group Members
|
||||
///
|
||||
|
||||
@@ -6,9 +6,9 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
use validator::Validate;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Bulk Delete Messages
|
||||
///
|
||||
@@ -23,14 +23,9 @@ pub async fn bulk_delete_messages(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
options: Json<v0::OptionsBulkDelete>,
|
||||
options: Validate<Json<v0::OptionsBulkDelete>>,
|
||||
) -> Result<EmptyResponse> {
|
||||
let options = options.into_inner();
|
||||
options.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let options = options.into_inner().into_inner();
|
||||
|
||||
for id in &options.ids {
|
||||
if ulid::Ulid::from_string(id)
|
||||
|
||||
@@ -7,8 +7,8 @@ use revolt_database::{
|
||||
use revolt_models::v0::{self, Embed};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Edit Message
|
||||
///
|
||||
@@ -20,14 +20,9 @@ pub async fn edit(
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
msg: Reference<'_>,
|
||||
edit: Json<v0::DataEditMessage>,
|
||||
edit: Validate<Json<v0::DataEditMessage>>,
|
||||
) -> Result<Json<v0::Message>> {
|
||||
let edit = edit.into_inner();
|
||||
edit.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let edit = edit.into_inner().into_inner();
|
||||
|
||||
Message::validate_sum(
|
||||
&edit.content,
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Message
|
||||
///
|
||||
|
||||
@@ -5,8 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0::{self, MessageSort};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Fetch Messages
|
||||
///
|
||||
@@ -17,14 +17,8 @@ pub async fn query(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
options: v0::OptionsQueryMessages,
|
||||
options: Validate<v0::OptionsQueryMessages>,
|
||||
) -> Result<Json<v0::BulkMessageResponse>> {
|
||||
options.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
if let Some(MessageSort::Relevance) = options.sort {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
@@ -43,7 +37,7 @@ pub async fn query(
|
||||
sort,
|
||||
nearby,
|
||||
include_users,
|
||||
} = options;
|
||||
} = options.into_inner();
|
||||
|
||||
Message::fetch_with_users(
|
||||
db,
|
||||
|
||||
@@ -5,8 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Search for Messages
|
||||
///
|
||||
@@ -17,18 +17,13 @@ pub async fn search(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
options: Json<v0::DataMessageSearch>,
|
||||
options: Validate<Json<v0::DataMessageSearch>>,
|
||||
) -> Result<Json<v0::BulkMessageResponse>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(create_error!(IsBot));
|
||||
}
|
||||
|
||||
let options = options.into_inner();
|
||||
options.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let options = options.into_inner().into_inner();
|
||||
|
||||
if options.query.is_some() && options.pinned.is_some() {
|
||||
return Err(create_error!(InvalidOperation))
|
||||
|
||||
@@ -7,10 +7,9 @@ use revolt_database::{Interactions, Message, AMQP};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::PermissionQuery;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use revolt_result::Result;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Send Message
|
||||
///
|
||||
@@ -22,15 +21,10 @@ pub async fn message_send(
|
||||
amqp: &State<AMQP>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataMessageSend>,
|
||||
data: Validate<Json<v0::DataMessageSend>>,
|
||||
idempotency: IdempotencyKey,
|
||||
) -> Result<Json<v0::Message>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
// Ensure we have permissions to send a message
|
||||
let channel = target.as_channel(db).await?;
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, Override};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Set Role Permission
|
||||
///
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0::{self, DataDefaultChannelPermissions};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Set Default Permission
|
||||
///
|
||||
|
||||
@@ -6,7 +6,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Join Call
|
||||
///
|
||||
|
||||
@@ -7,9 +7,9 @@ use revolt_permissions::{
|
||||
calculate_channel_permissions, ChannelPermission, DEFAULT_WEBHOOK_PERMISSIONS,
|
||||
};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Creates a webhook
|
||||
///
|
||||
@@ -20,14 +20,9 @@ pub async fn create_webhook(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::CreateWebhookBody>,
|
||||
data: Validate<Json<v0::CreateWebhookBody>>,
|
||||
) -> Result<Json<v0::Webhook>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0::Webhook;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Gets all webhooks
|
||||
///
|
||||
|
||||
@@ -3,9 +3,8 @@ use revolt_database::{util::permissions::DatabasePermissionQuery, Database, Emoj
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use validator::Validate;
|
||||
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Create New Emoji
|
||||
///
|
||||
@@ -16,16 +15,11 @@ pub async fn create_emoji(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
id: String,
|
||||
data: Json<v0::DataCreateEmoji>,
|
||||
data: Validate<Json<v0::DataCreateEmoji>>,
|
||||
) -> Result<Json<v0::Emoji>> {
|
||||
let config = config().await;
|
||||
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
// Validate we have permission to write into parent
|
||||
match &data.parent {
|
||||
|
||||
@@ -2,7 +2,8 @@ use revolt_database::{util::reference::Reference, Database};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Emoji
|
||||
///
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use revolt_database::{util::reference::Reference, Channel, Database, Invite};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Invite
|
||||
///
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use revolt_database::{util::reference::Reference, Channel, Database, Invite, Member, User, AMQP};
|
||||
use revolt_models::v0::{self, InviteJoinResponse};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Join Invite
|
||||
///
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
use authifier::models::Session;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
use rocket::{serde::json::Json, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// Regex for valid usernames
|
||||
///
|
||||
/// Block zero width space
|
||||
/// Block lookalike characters
|
||||
pub static RE_USERNAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\p{L}|[\d_.-])+$").unwrap());
|
||||
|
||||
/// # New User Data
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataOnboard {
|
||||
/// New username which will be used to identify the user on the platform
|
||||
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
|
||||
username: String,
|
||||
}
|
||||
|
||||
/// # Complete Onboarding
|
||||
///
|
||||
@@ -32,18 +16,13 @@ pub async fn complete(
|
||||
db: &State<Database>,
|
||||
session: Session,
|
||||
user: Option<User>,
|
||||
data: Json<DataOnboard>,
|
||||
data: Validate<Json<v0::DataOnboard>>,
|
||||
) -> Result<Json<v0::User>> {
|
||||
if user.is_some() {
|
||||
return Err(create_error!(AlreadyOnboarded));
|
||||
}
|
||||
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
Ok(Json(
|
||||
User::create(db, data.username, session.user_id, None)
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
use authifier::models::Session;
|
||||
use revolt_database::User;
|
||||
use revolt_models::v0;
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Serialize;
|
||||
|
||||
/// # Onboarding Status
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct DataHello {
|
||||
/// Whether onboarding is required
|
||||
onboarding: bool,
|
||||
}
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Check Onboarding Status
|
||||
///
|
||||
/// This will tell you whether the current account requires onboarding or whether you can continue to send requests as usual. You may skip calling this if you're restoring an existing session.
|
||||
#[openapi(tag = "Onboarding")]
|
||||
#[get("/hello")]
|
||||
pub async fn hello(_session: Session, user: Option<User>) -> Json<DataHello> {
|
||||
Json(DataHello {
|
||||
pub async fn hello(_session: Session, user: Option<User>) -> Json<v0::DataHello> {
|
||||
Json(v0::DataHello {
|
||||
onboarding: user.is_none(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ use authifier::{
|
||||
Authifier,
|
||||
};
|
||||
use revolt_result::{create_database_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
/// # Push Subscribe
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::util::json::Json;
|
||||
use revolt_config::config;
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Serialize;
|
||||
|
||||
/// # hCaptcha Configuration
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
use revolt_database::{events::client::EventV1, Database, Report, Snapshot, SnapshotContent, User};
|
||||
use revolt_models::v0::{ReportStatus, ReportedContent};
|
||||
use revolt_models::v0::{DataReportContent, ReportStatus, ReportedContent};
|
||||
use revolt_result::{create_error, Result};
|
||||
use serde::Deserialize;
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
/// # Report Data
|
||||
#[derive(Validate, Deserialize, JsonSchema)]
|
||||
pub struct DataReportContent {
|
||||
/// Content being reported
|
||||
content: ReportedContent,
|
||||
/// Additional report description
|
||||
#[validate(length(min = 0, max = 1000))]
|
||||
#[serde(default)]
|
||||
additional_context: String,
|
||||
}
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Report Content
|
||||
///
|
||||
@@ -26,14 +13,9 @@ pub struct DataReportContent {
|
||||
pub async fn report_content(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
data: Json<DataReportContent>,
|
||||
data: Validate<Json<DataReportContent>>,
|
||||
) -> Result<()> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
// Bots cannot create reports
|
||||
if user.bot.is_some() {
|
||||
|
||||
@@ -6,8 +6,8 @@ use revolt_models::v0;
|
||||
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use rocket::State;
|
||||
use crate::util::json::{Json, Validate};
|
||||
|
||||
/// # Ban User
|
||||
///
|
||||
@@ -19,14 +19,9 @@ pub async fn ban(
|
||||
user: User,
|
||||
server: Reference<'_>,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataBanCreate>,
|
||||
data: Validate<Json<v0::DataBanCreate>>,
|
||||
) -> Result<Json<v0::ServerBan>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let server = server.as_server(db).await?;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use revolt_models::v0;
|
||||
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Fetch Bans
|
||||
|
||||
@@ -2,11 +2,10 @@ use revolt_database::util::permissions::DatabasePermissionQuery;
|
||||
use revolt_database::{util::reference::Reference, Channel, Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use revolt_result::Result;
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Create Channel
|
||||
///
|
||||
@@ -17,14 +16,9 @@ pub async fn create_server_channel(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
server: Reference<'_>,
|
||||
data: Json<v0::DataCreateServerChannel>,
|
||||
data: Validate<Json<v0::DataCreateServerChannel>>,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let mut server = server.as_server(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::PermissionQuery;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Server Emoji
|
||||
///
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Invites
|
||||
///
|
||||
|
||||
@@ -8,8 +8,8 @@ use revolt_models::v0;
|
||||
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
|
||||
/// # Edit Member
|
||||
///
|
||||
@@ -21,14 +21,9 @@ pub async fn edit(
|
||||
user: User,
|
||||
server: Reference<'_>,
|
||||
member: Reference<'_>,
|
||||
data: Json<v0::DataMemberEdit>,
|
||||
data: Validate<Json<v0::DataMemberEdit>>,
|
||||
) -> Result<Json<v0::Member>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
// Fetch server and member
|
||||
let mut server = server.as_server(db).await?;
|
||||
|
||||
@@ -6,27 +6,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::PermissionQuery;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// # Query Parameters
|
||||
#[derive(Deserialize, JsonSchema, FromForm)]
|
||||
pub struct OptionsQueryMembers {
|
||||
/// String to search for
|
||||
query: String,
|
||||
|
||||
/// Discourage use of this API
|
||||
experimental_api: bool,
|
||||
}
|
||||
|
||||
/// # Query members by name
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct MemberQueryResponse {
|
||||
/// List of members
|
||||
members: Vec<v0::Member>,
|
||||
/// List of users
|
||||
users: Vec<v0::User>,
|
||||
}
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Query members by name
|
||||
///
|
||||
@@ -37,8 +18,8 @@ pub async fn member_experimental_query(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
options: OptionsQueryMembers,
|
||||
) -> Result<Json<MemberQueryResponse>> {
|
||||
options: v0::OptionsQueryMembers,
|
||||
) -> Result<Json<v0::MemberQueryResponse>> {
|
||||
if !options.experimental_api {
|
||||
return Err(create_error!(InternalError));
|
||||
}
|
||||
@@ -84,7 +65,7 @@ pub async fn member_experimental_query(
|
||||
|
||||
// Take the first ten and return them
|
||||
let (members, users): (Vec<Member>, Vec<User>) = zipped_vec.into_iter().take(10).unzip();
|
||||
Ok(Json(MemberQueryResponse {
|
||||
Ok(Json(v0::MemberQueryResponse {
|
||||
members: members.into_iter().map(Into::into).collect(),
|
||||
users: join_all(
|
||||
users
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::util::json::Json;
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Database, User,
|
||||
@@ -5,7 +6,7 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::PermissionQuery;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
|
||||
/// # Fetch Member
|
||||
///
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::PermissionQuery;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Members
|
||||
///
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::util::json::Json;
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Database, User,
|
||||
@@ -5,7 +6,7 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission, Override};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
|
||||
/// # Set Role Permission
|
||||
///
|
||||
|
||||
@@ -7,7 +7,8 @@ use revolt_permissions::{
|
||||
calculate_server_permissions, ChannelPermission, DataPermissionsValue, Override,
|
||||
};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Set Default Permission
|
||||
///
|
||||
|
||||
@@ -6,8 +6,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
|
||||
/// # Create Role
|
||||
///
|
||||
@@ -18,14 +18,9 @@ pub async fn create(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataCreateRole>,
|
||||
data: Validate<Json<v0::DataCreateRole>>,
|
||||
) -> Result<Json<v0::NewRoleResponse>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let server = target.as_server(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
|
||||
@@ -5,8 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
|
||||
/// # Edit Role
|
||||
///
|
||||
@@ -18,14 +18,9 @@ pub async fn edit(
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
role_id: String,
|
||||
data: Json<v0::DataEditRole>,
|
||||
data: Validate<Json<v0::DataEditRole>>,
|
||||
) -> Result<Json<v0::Role>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Edits server roles ranks
|
||||
///
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::PermissionQuery;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Role
|
||||
///
|
||||
|
||||
@@ -2,9 +2,8 @@ use revolt_database::{Database, Member, Server, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Create Server
|
||||
///
|
||||
@@ -14,18 +13,13 @@ use validator::Validate;
|
||||
pub async fn create_server(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
data: Json<v0::DataCreateServer>,
|
||||
data: Validate<Json<v0::DataCreateServer>>,
|
||||
) -> Result<Json<v0::CreateServerLegacyResponse>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(create_error!(IsBot));
|
||||
}
|
||||
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
user.can_acquire_server(db).await?;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
|
||||
/// # Edit Server
|
||||
///
|
||||
@@ -19,14 +19,9 @@ pub async fn edit(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataEditServer>,
|
||||
data: Validate<Json<v0::DataEditServer>>,
|
||||
) -> Result<Json<v0::Server>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, PermissionQuery};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Server
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Fetch Settings
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Fetch Unreads
|
||||
|
||||
@@ -3,7 +3,8 @@ use revolt_models::v0;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
use rocket_empty::EmptyResponse;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use revolt_database::util::reference::Reference;
|
||||
use revolt_database::{Database, User, AMQP};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Accept Friend Request
|
||||
|
||||
@@ -2,7 +2,7 @@ use revolt_database::util::reference::Reference;
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Block User
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
use authifier::models::Account;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// Regex for valid usernames
|
||||
///
|
||||
/// Block zero width space
|
||||
/// Block lookalike characters
|
||||
pub static RE_USERNAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\p{L}|[\d_.-])+$").unwrap());
|
||||
|
||||
/// # Username Information
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataChangeUsername {
|
||||
/// New username
|
||||
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
|
||||
username: String,
|
||||
/// Current account password
|
||||
#[validate(length(min = 8, max = 1024))]
|
||||
password: String,
|
||||
}
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
|
||||
/// # Change Username
|
||||
///
|
||||
@@ -34,14 +14,9 @@ pub async fn change_username(
|
||||
db: &State<Database>,
|
||||
account: Account,
|
||||
mut user: User,
|
||||
data: Json<DataChangeUsername>,
|
||||
data: Validate<Json<v0::DataChangeUsername>>,
|
||||
) -> Result<Json<v0::User>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
account
|
||||
.verify_password(&data.password)
|
||||
|
||||
@@ -2,9 +2,8 @@ use revolt_database::FieldsUser;
|
||||
use revolt_database::{util::reference::Reference, Database, File, PartialUser, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Edit User
|
||||
///
|
||||
@@ -15,14 +14,9 @@ pub async fn edit(
|
||||
db: &State<Database>,
|
||||
mut user: User,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataEditUser>,
|
||||
data: Validate<Json<v0::DataEditUser>>,
|
||||
) -> Result<Json<v0::User>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
// Filter out invalid edit fields
|
||||
if !user.privileged && (data.badges.is_some() || data.flags.is_some()) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Direct Message Channels
|
||||
///
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch User Profile
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use revolt_database::User;
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch Self
|
||||
///
|
||||
|
||||
@@ -6,7 +6,8 @@ use revolt_models::v0;
|
||||
|
||||
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch User
|
||||
///
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use revolt_database::{util::reference::Reference, Database};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Fetch User Flags
|
||||
///
|
||||
|
||||
@@ -5,7 +5,7 @@ use revolt_models::v0;
|
||||
|
||||
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Fetch Mutual Friends And Servers
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Open Direct Message
|
||||
///
|
||||
|
||||
@@ -2,7 +2,7 @@ use revolt_database::util::reference::Reference;
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Deny Friend Request / Remove Friend
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use revolt_database::{Database, User, AMQP};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Send Friend Request
|
||||
|
||||
@@ -2,7 +2,7 @@ use revolt_database::util::reference::Reference;
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
use crate::util::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Unblock User
|
||||
|
||||
@@ -4,9 +4,9 @@ use revolt_database::{
|
||||
};
|
||||
use revolt_models::v0::{DataEditWebhook, Webhook};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
use revolt_result::Result;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
|
||||
/// # Edits a webhook
|
||||
///
|
||||
@@ -17,14 +17,9 @@ pub async fn webhook_edit(
|
||||
db: &State<Database>,
|
||||
webhook_id: Reference<'_>,
|
||||
user: User,
|
||||
data: Json<DataEditWebhook>,
|
||||
data: Validate<Json<DataEditWebhook>>,
|
||||
) -> Result<Json<Webhook>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let mut webhook = webhook_id.as_webhook(db).await?;
|
||||
let channel = db.fetch_channel(&webhook.channel_id).await?;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use revolt_database::util::reference::Reference;
|
||||
use revolt_database::{Database, File, PartialWebhook};
|
||||
use revolt_models::v0::{DataEditWebhook, Webhook};
|
||||
use revolt_models::validator::Validate;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use revolt_result::Result;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
|
||||
/// # Edits a webhook
|
||||
///
|
||||
@@ -14,14 +14,9 @@ pub async fn webhook_edit_token(
|
||||
db: &State<Database>,
|
||||
webhook_id: Reference<'_>,
|
||||
token: String,
|
||||
data: Json<DataEditWebhook>,
|
||||
data: Validate<Json<DataEditWebhook>>,
|
||||
) -> Result<Json<Webhook>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let mut webhook = webhook_id.as_webhook(db).await?;
|
||||
webhook.assert_token(&token)?;
|
||||
|
||||
@@ -5,10 +5,9 @@ use revolt_database::{
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{ChannelPermission, PermissionValue};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
use validator::Validate;
|
||||
use revolt_result::Result;
|
||||
use crate::util::json::{Json, Validate};
|
||||
use rocket::State;
|
||||
|
||||
/// # Executes a webhook
|
||||
///
|
||||
@@ -20,15 +19,10 @@ pub async fn webhook_execute(
|
||||
amqp: &State<AMQP>,
|
||||
webhook_id: Reference<'_>,
|
||||
token: String,
|
||||
data: Json<v0::DataMessageSend>,
|
||||
data: Validate<Json<v0::DataMessageSend>>,
|
||||
idempotency: IdempotencyKey,
|
||||
) -> Result<Json<v0::Message>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
let data = data.into_inner().into_inner();
|
||||
|
||||
let webhook = webhook_id.as_webhook(db).await?;
|
||||
webhook.assert_token(&token)?;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_database::{util::reference::Reference, Database, Message, AMQP};
|
||||
use revolt_models::v0::{MessageAuthor, SendableEmbed, Webhook};
|
||||
use revolt_result::{create_error, Error, Result};
|
||||
use revolt_result::{create_error, Error, Result, ToRevoltError};
|
||||
use revolt_rocket_okapi::{
|
||||
gen::OpenApiGenerator,
|
||||
request::{OpenApiFromRequest, RequestHeaderInput},
|
||||
@@ -636,6 +636,8 @@ impl<'r> FromRequest<'r> for EventHeader<'r> {
|
||||
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
|
||||
let headers = request.headers();
|
||||
let Some(event) = headers.get_one("X-GitHub-Event") else {
|
||||
request.local_cache(|| Some(create_error!(InvalidOperation)));
|
||||
|
||||
return rocket::request::Outcome::Error((
|
||||
Status::BadRequest,
|
||||
create_error!(InvalidOperation),
|
||||
@@ -701,7 +703,7 @@ fn safe_from_str<T: for<'de> Deserialize<'de>>(data: &str) -> Result<T> {
|
||||
match serde_json::from_str(data) {
|
||||
Ok(output) => Ok(output),
|
||||
Err(err) => {
|
||||
log::error!("{err:?}");
|
||||
revolt_config::capture_error(&err);
|
||||
Err(create_error!(InvalidOperation))
|
||||
}
|
||||
}
|
||||
@@ -1061,11 +1063,13 @@ pub async fn webhook_execute_github(
|
||||
},
|
||||
};
|
||||
|
||||
sendable_embed.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
sendable_embed.validate()
|
||||
.capture_error()
|
||||
.map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
let message_id = Ulid::new().to_string();
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ use revolt_database::{
|
||||
use revolt_models::v0::{ResponseWebhook, Webhook};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Gets a webhook
|
||||
///
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use revolt_database::{util::reference::Reference, Database};
|
||||
use revolt_models::v0::Webhook;
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket::State;
|
||||
use crate::util::json::Json;
|
||||
|
||||
/// # Gets a webhook
|
||||
///
|
||||
|
||||
35
crates/delta/src/util/catchers.rs
Normal file
35
crates/delta/src/util/catchers.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use rocket::{catch, Catcher, Request};
|
||||
use revolt_result::{create_error, Error, Result};
|
||||
|
||||
#[catch(404)]
|
||||
pub fn not_found() -> Result<()> {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
|
||||
#[catch(422)]
|
||||
pub fn unprocessable_entity(req: &Request) -> Result<()> {
|
||||
match req.local_cache(|| None::<Error>) {
|
||||
Some(e) => Err(e.clone()),
|
||||
None => Err(create_error!(UnprocessableEntity))
|
||||
}
|
||||
}
|
||||
|
||||
#[catch(401)]
|
||||
pub fn unauthorized(req: &Request) -> Result<()> {
|
||||
match req.local_cache(|| None::<Error>) {
|
||||
Some(e) => Err(e.clone()),
|
||||
None => Err(create_error!(NotAuthenticated))
|
||||
}
|
||||
}
|
||||
|
||||
#[catch(409)]
|
||||
pub fn conflict(req: &Request) -> Result<()> {
|
||||
match req.local_cache(|| None::<Error>) {
|
||||
Some(e) => Err(e.clone()),
|
||||
None => Err(create_error!(Conflict))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_catchers() -> Vec<Catcher> {
|
||||
catchers![not_found, unprocessable_entity, unauthorized, conflict]
|
||||
}
|
||||
101
crates/delta/src/util/json/json.rs
Normal file
101
crates/delta/src/util/json/json.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use revolt_rocket_okapi::{
|
||||
r#gen::OpenApiGenerator,
|
||||
request::OpenApiFromData,
|
||||
response::OpenApiResponderInner,
|
||||
revolt_okapi::openapi3::{RequestBody, Responses}, util::add_schema_response
|
||||
};
|
||||
use rocket::{data::{Data, FromData, Limits, Outcome}};
|
||||
use rocket::response::{self, Responder, content};
|
||||
use rocket::request::{local_cache, Request};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use revolt_result::{create_error, Error, ToRevoltError};
|
||||
|
||||
// A lot of this code is modified versions of rocket::serde::json so we can store
|
||||
// the error so it can be passed to the error catcher.
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Json<T>(pub T);
|
||||
|
||||
impl<'r, T: Deserialize<'r>> Json<T> {
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn from_str(s: &'r str) -> Result<Self, Error> {
|
||||
serde_json::from_str(s)
|
||||
.map(Json)
|
||||
.map_err(|e| create_error!(DeserializationError { error: e.to_string() }))
|
||||
}
|
||||
|
||||
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Result<Self, Error> {
|
||||
let limit = req.limits().get("json").unwrap_or(Limits::JSON);
|
||||
let string = match data.open(limit).into_string().await {
|
||||
Ok(s) if s.is_complete() => s.into_inner(),
|
||||
Ok(_) => {
|
||||
return Err(create_error!(PayloadTooLarge));
|
||||
},
|
||||
Err(_) => return Err(create_error!(IOError)),
|
||||
};
|
||||
|
||||
Self::from_str(local_cache!(req, string))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Json<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'r, T: Deserialize<'r> + std::fmt::Debug> FromData<'r> for Json<T> {
|
||||
type Error = Error;
|
||||
|
||||
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
|
||||
let r = Self::from_data(req, data).await;
|
||||
|
||||
match r {
|
||||
Ok(value) => Outcome::Success(value),
|
||||
Err(e) => {
|
||||
req.local_cache(|| Some(e.clone()));
|
||||
rocket::outcome::Outcome::Error((e.rocket_status(), e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, T: Serialize> Responder<'r, 'static> for Json<T> {
|
||||
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
|
||||
match serde_json::to_string(&self.0).capture_error() {
|
||||
Ok(string) => content::RawJson(string).respond_to(req),
|
||||
Err(_) => create_error!(InternalError).respond_to(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, T: JsonSchema + Deserialize<'r> + Debug> OpenApiFromData<'r> for Json<T> {
|
||||
fn request_body(gen: &mut OpenApiGenerator) -> revolt_rocket_okapi::Result<RequestBody> {
|
||||
crate::fn_request_body!(gen, T, "application/json")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JsonSchema + Serialize> OpenApiResponderInner for Json<T> {
|
||||
fn responses(gen: &mut OpenApiGenerator) -> revolt_rocket_okapi::Result<Responses> {
|
||||
let mut responses = Responses::default();
|
||||
let schema = gen.json_schema::<T>();
|
||||
add_schema_response(&mut responses, 200, "application/json", schema)?;
|
||||
Ok(responses)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: validator::Validate> validator::Validate for Json<T> {
|
||||
fn validate(&self) -> Result<(), validator::ValidationErrors> {
|
||||
self.0.validate()
|
||||
}
|
||||
}
|
||||
27
crates/delta/src/util/json/mod.rs
Normal file
27
crates/delta/src/util/json/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
pub mod json;
|
||||
pub mod validator;
|
||||
|
||||
pub use json::Json;
|
||||
pub use validator::Validate;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fn_request_body {
|
||||
($gen:ident, $ty:path, $mime_type:expr) => {{
|
||||
let schema = $gen.json_schema::<$ty>();
|
||||
Ok(revolt_rocket_okapi::revolt_okapi::openapi3::RequestBody {
|
||||
content: {
|
||||
let mut map = revolt_rocket_okapi::revolt_okapi::Map::new();
|
||||
map.insert(
|
||||
$mime_type.to_owned(),
|
||||
revolt_rocket_okapi::revolt_okapi::openapi3::MediaType {
|
||||
schema: Some(schema),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
map
|
||||
},
|
||||
required: true,
|
||||
..Default::default()
|
||||
})
|
||||
}};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user