Compare commits

..
10 Commits
Author SHA1 Message Date
Zomatree 1fc5711338 Merge branch 'main' into feat/sso 2025-11-18 21:38:43 +00:00
Zomatree 2bbd702ec2 feat: sso 2025-11-18 21:33:21 +00:00
Paul Makles 24fedf8c4d fix: respond with 201 if no body in requests (#465)
Signed-off-by: izzy <me@insrt.uk>
2025-11-16 15:24:39 +00:00
Paul Makles dda20f44ae ci: correct build to ./docs/build (#463) 2025-11-14 22:11:21 +00:00
Paul Makles 0b7c132ace docs: initialise new docs with docusaurus (#460)
* docs: initialise new docs with docusaurus

* chore: sign off
Signed-off-by: izzy <me@insrt.uk>

* chore: lock pnpm version in package.json

Signed-off-by: izzy <me@insrt.uk>

* ci: specify package json to load pnpm version from

Signed-off-by: izzy <me@insrt.uk>

* docs: update repo. lifecycle details

Signed-off-by: izzy <me@insrt.uk>

* docs: add external links to for-web, for-android
chore: add default.nix to docs

* docs: correct headings in contrib

* ci: use cache dependency path

* fix: docusaurus configuration

---------

Signed-off-by: izzy <me@insrt.uk>
2025-11-14 22:06:20 +00:00
Paul Makles e006cdd051 ci: prevent concurrency on docker/rust actions (#461) 2025-11-14 21:41:22 +00:00
stoat-tofu[bot] ac2971a78d chore: modify .github/workflows/validate-pr-title.yml 2025-11-14 20:25:09 +00:00
Paul Makles b7c2ca84ab chore: update branding in transactional emails (#458)
Signed-off-by: izzy <me@insrt.uk>
2025-11-12 22:19:34 +00:00
Paul Makles 82b492e529 ci: normalise workflow triggers (#459)
Signed-off-by: izzy <me@insrt.uk>
2025-11-12 22:17:11 +00:00
Angelo Kontaxis d567155f12 feat: voice chats v2 (#414)
* initial livekit support

fix up code

undo changes to compose file

add back .env.example

* move voice states to global

* track current state

* more stuff

* fix redis key inconsistancy

* split voice ops into its own library

* feat(livekit): more permission handling

* feat(livekit): push unfinished code

* fix: include voice states in servercreate event
feat: call started system message in dms

* feat: implement missing permission syncs

* fix: remove locked rocket version

* fix: remove local testing values from config

* chore: switch to ids for parameter

* feat: make Channel::server return a reference

* feat: support multiple voice nodes

* fix: expose list of nodes

* fix: respond with the url when joining a voice channel

* feat: being moved between voice channels

* fix: use existing node if someone is already in the voice channel

* refactor: Remove VoiceChannel

* chore: add pushd to debug image script

* refactor: add error messages for Rabbit issues

* docs: add some notes on overrides; fix defaults

* fix: ensure limit is always at least 1 when sent to database

* fix: change permission check for fetching channel webhooks

* chore: mount API at /0.8/ as well

* fix: ensure ratelimiter adjusts for version prefix

* chore: bump version to 0.8.2

* chore: disable LTO because it likely overloads GitHub worker

* fix: don't allow users to time themselves out
closes #376

* fix: match new error type for status code

* feat: init crond crate

* feat: file deletion implementation

* chore: fix version references

* feat: add crond container definitions

* chore: clarify cargo deny / tomls

* feat: add company information to email footers

* chore: bump version to 0.8.3

* fix: don't bump the lockfile version

* fix: include `production` default value for config

* chore: ignore python venv and dev script for user generation

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* test: Add tests for invite/fetch

Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>

* test: add utilities for common setup

Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>

* test: add delete server channel test

Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>

* feat: allow to set an icon when creating a group

Signed-off-by: Nils Ponsard <nilsponsard@gmail.com>

* fix: allow message pinning and unpinning for DM-like channels

Signed-off-by: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com>

* fix: remove SavedMessages and Group due to given permissions

Signed-off-by: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com>

* feat: allow bots to manage emojis (#407)

* Remove bot check on emoji create

Signed-off-by: Builderb <builderbgamer@gmail.com>

* Remove bot check on emoji delete

Signed-off-by: Builderb <builderbgamer@gmail.com>

---------

Signed-off-by: Builderb <builderbgamer@gmail.com>

* feat: add option to send message with missing replies

Signed-off-by: ShaksterNano <54268387+shaksternano@users.noreply.github.com>

* chore: bump version to 0.8.4
chore: enable webhooks in test builds

* ci: downgrade lockfile

* feat: Add Mass Mentions to the backend (#394)

* feat: create base of push daemon

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* Add outbound senders

* Make web_push send to rabbit instead (temp stuff)

* feat: stability and friend requests

* make vapid fr stuff not suck

* swap naming of queue

* move pushd into daemons folder

* fix cargo file for move into daemons folder

* feat: probably working fcm push notifs

* comment out fcm webpush stuff since the config keys dont exist

* fix fcm, name queues according to their prod status and configure routing keys

* add pushd to docker

* mix: Remove old code, add stuff to pushd

* fix: lockfile

* feat: update rocket to 5.0.1

* fix: fix queues and ack bugs

* Move rabbit messsage processing into ack queue

* chore: update readme

* chore: optimizations for ack database hits

* pushd flowchart

* misc: update flowchart

* exit dependancy hell

* add rocket_impl flag to authifier

* make the tests file of delta actually compile

* fix: don't silence every push message

* fix: don't silence all messages

* add debug logging for sending data to rabbit from message events

* validate mentions at a server membership level

* put back that import that was actually important

* minor fix to lockfile

* update delta authifier

* feat: proper permissions for push notifications

* add unit test for mention sanitization

* remove local file dependancy on authifier

* update ports to proper defaults

* fixTM the node bindings

* Theoretically configure docker releases for pushd

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* declare exchange in pushd and delta

* fix createbuckets script

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: reference db implementation

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: remove finally redundant code

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: changes

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: other changes

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: make channel name return result

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* Add role mention parsing

* feat: update to mongo 3.1, add member generator.

* integrate mass mentions into pushd

* patch redis-rs with updated versions

* feat: chunk role mentions

* move permission bits to 37/38 to avoid livekit conflict

* change role mention format to <%id>

* fix the lockfile from merge

* fix: PR change requests

* feat: add tests

* fix: i am a dumbass

* fix: tests, again

---------

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: show full branch name on github webhook messages

* fix: dont leak invisible presence to others

* fix: add back missing early adopter badge

* fix: update branch from main

* fix: update branch from main

* Merge remote-tracking branch 'origin/main' into livekit

* feat: private livekit nodes

* fix: remove node from channel voice state

* fix: get_voice_state has incorrect unique_key and allowed_sources should be lowercase

* chore: bump livekit dep

* feat: ability to force disconnect existing voice connection

* chore: cleanup code

* feat: - track call length
- move voice and video limits to config
- seperate VoiceInformation into model and db model
- fix build scripts

* fix: move call system message to daemon,
check max participants when creating a token to avoid giving tokens but erroring when attempting to join,
check if the channel actually supports voice

* fix: dont set max participants in livekit

* fix: remove VoiceChannel channel type
fix: calculate user overwrites correctly
fix: dont include personal info in livekit user metadata
fix: revoke video permissons on denied publish
fix: add video to default permissions

* fix: update migration

* feat: Send push notifications for dm call start/end

* chore: cleanup

* feat: allow users to specify who is notified when starting a call

* feat: track join time

* merge: branch 'main' into feat/livekit

* fix: include voice-ingress in build

* fix: dont presume channel has a node

* chore: add error logging to internal errors

* chore: add debug logging to voice ingress

* refactor: seperate out disconnect logic

* fix: return correct error if user is already in a voice channel

* fix: dont break on user not still being in channel and force disconnecting

* fix: add Speak and Listen to default permissions

* fix: include channel ids in UserMoveVoiceChannel

* chore: add temp sentry logging system

* fix: handle track_muted and track_unmuted

* fix: dont set notification recipients when empty vec is passed

* fix: only send UserMoveVoiceChannel to the user

* fix: preserve order of replies in message (#447)

Signed-off-by: Aeledfyr <aeledfyr@gmail.com>

* fix: prevent timing out members which have TimeoutMembers permission

* chore: update ci

* fix: temporarily disable call started system messages
See #457

* fix: update code from review
2025-11-12 21:27:04 +00:00
168 changed files with 1541 additions and 2348 deletions

No files matched your search

Generated
+318 -313
View File
File diff suppressed because it is too large. Load diff
+2 -3
View File
@@ -11,9 +11,8 @@ members = [
[patch.crates-io]
redis23 = { package = "redis", version = "0.23.3", git = "https://github.com/revoltchat/redis-rs", rev = "523b2937367e17bd0073722bf6e23d06042cb4e4" }
authifier = { package = "authifier", version = "1.0.10", path = "../authifier/crates/authifier" }
rocket_authifier = { package = "rocket_authifier", version = "1.0.10", path = "../authifier/crates/rocket_authifier" }
iso8601-timestamp = { path = "../iso8601-timestamp" }
#authifier = { package = "authifier", version = "1.0.10", path = "../authifier/crates/authifier" }
#rocket_authifier = { package = "rocket_authifier", version = "1.0.10", path = "../authifier/crates/rocket_authifier" }
# I'm 99% sure this is overloading the GitHub worker
# hence builds have been failing since, let's just
+1 -1
View File
@@ -15,7 +15,7 @@ use crate::{CoalescionServiceConfig, Error};
#[derive(Debug, Clone)]
#[allow(clippy::type_complexity)]
/// Coalescion service
/// # Coalescion service
///
/// See module description for example usage.
pub struct CoalescionService<Id: Hash + Clone + Eq> {
+3
View File
@@ -38,3 +38,6 @@ sentry-anyhow = { version = "0.38.1", optional = true }
# Core
revolt-result = { version = "0.8.9", path = "../result", optional = true }
# Authifier
authifier = "1.0.15"
+2
View File
@@ -74,6 +74,8 @@ max_concurrent_connections = 50
# How long to ring devices for when calling in dms/groups, in seconds
call_ring_duration = 30
[api.sso]
[api.livekit.nodes]
[api.users]
+2
View File
@@ -5,6 +5,7 @@ use config::{Config, File, FileFormat};
use futures_locks::RwLock;
use once_cell::sync::Lazy;
use serde::Deserialize;
use authifier::config::SSO;
#[cfg(feature = "sentry")]
pub use sentry::{capture_error, capture_message, Level};
@@ -229,6 +230,7 @@ pub struct Api {
pub workers: ApiWorkers,
pub livekit: ApiLiveKit,
pub users: ApiUsers,
pub sso: SSO,
}
#[derive(Deserialize, Debug, Clone)]
+10 -10
View File
@@ -15,7 +15,7 @@ mongodb = ["dep:mongodb", "bson", "authifier/database-mongodb"]
# ... Other
tasks = ["isahc", "linkify", "url-escape"]
async-std-runtime = ["async-std", "authifier/async-std-runtime"]
rocket-impl = ["rocket"]
rocket-impl = ["rocket", "schemars", "revolt_okapi", "revolt_rocket_okapi", "authifier/rocket_impl"]
axum-impl = ["axum"]
redis-is-patched = ["revolt-presence/redis-is-patched"]
voice = ["livekit-api", "livekit-protocol", "livekit-runtime"]
@@ -47,7 +47,7 @@ ulid = "1.0.0"
nanoid = "0.4.0"
base64 = "0.21.3"
once_cell = "1.17"
indexmap = "2.12.0"
indexmap = "1.9.1"
decancer = "1.6.2"
deadqueue = "0.2.4"
linkify = { optional = true, version = "0.8.1" }
@@ -59,7 +59,7 @@ isahc = { optional = true, version = "1.7", features = ["json"] }
serde_json = "1"
revolt_optional_struct = "0.2.0"
serde = { version = "1", features = ["derive"] }
iso8601-timestamp = { version = "0.4.0", features = ["serde", "bson"] }
iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
# Events
redis-kiss = { version = "0.1.4" }
@@ -82,15 +82,15 @@ async-recursion = "1.0.4"
async-std = { version = "1.8.0", features = ["attributes"], optional = true }
# Axum Impl
axum = { version = "0.8.6", optional = true }
axum = { version = "0.7.5", optional = true }
# Rocket Impl
schemars = { version = "0.8.8", optional = true }
rocket = { version = "0.5.1", default-features = false, features = [
"json",
], optional = true }
# Openapi Schema
utoipa = { version = "5.4.0", optional = true }
revolt_okapi = { version = "0.9.1", optional = true }
revolt_rocket_okapi = { version = "0.10.0", optional = true }
# Authifier
authifier = { version = "1.0.15" }
@@ -99,6 +99,6 @@ authifier = { version = "1.0.15" }
amqprs = { version = "1.7.0" }
# Voice
livekit-api = { version = "=0.4.4", optional = true}
livekit-protocol = { version = "=0.4.0", optional = true }
livekit-runtime = { version = "=0.3.1", features = ["tokio"], optional = true }
livekit-api = { version = "0.4.4", optional = true}
livekit-protocol = { version = "0.4.0", optional = true }
livekit-runtime = { version = "0.3.1", features = ["tokio"], optional = true }
+3
View File
@@ -209,6 +209,8 @@ impl Database {
} else {
EmailVerificationConfig::Disabled
},
sso: config.api.sso.clone(),
server_url: Some(config.hosts.api.parse().expect("Failed to parse API host url.")),
..Default::default()
};
@@ -244,6 +246,7 @@ impl Database {
event_channel: Some(crate::tasks::authifier_relay::sender()),
#[cfg(not(feature = "tasks"))]
event_channel: None,
..Default::default()
}
}
}
+3 -7
View File
@@ -25,10 +25,6 @@ pub use mongodb;
#[macro_use]
extern crate bson;
#[cfg(feature = "utoipa")]
#[macro_use]
extern crate utoipa;
#[cfg(not(feature = "async-std-runtime"))]
compile_error!("async-std-runtime feature must be enabled.");
@@ -64,7 +60,7 @@ macro_rules! database_derived {
macro_rules! auto_derived {
( $( $item:item )+ ) => {
$(
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
$item
)+
};
@@ -72,8 +68,8 @@ macro_rules! auto_derived {
macro_rules! auto_derived_partial {
( $item:item, $name:expr ) => {
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, PartialEq)]
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
#[optional_name = $name]
#[opt_skip_serializing_none]
#[opt_some_priority]
@@ -14,7 +14,7 @@ auto_derived!(
}
/// Composite primary key consisting of channel and user id
#[derive(Hash, Eq)]
#[derive(Hash)]
pub struct ChannelCompositeKey {
/// Channel Id
pub channel: String,
@@ -247,7 +247,7 @@ impl AbstractMessages for ReferenceDb {
let mut messages = self.messages.lock().await;
if let Some(message) = messages.get_mut(id) {
if let Some(users) = message.reactions.get_mut(emoji) {
users.swap_remove(&user.to_string());
users.remove(&user.to_string());
}
Ok(())
@@ -260,7 +260,7 @@ impl AbstractMessages for ReferenceDb {
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
let mut messages = self.messages.lock().await;
if let Some(message) = messages.get_mut(id) {
message.reactions.swap_remove(emoji);
message.reactions.remove(emoji);
Ok(())
} else {
Err(create_error!(NotFound))
@@ -55,7 +55,7 @@ auto_derived_partial!(
auto_derived!(
/// Composite primary key consisting of server and user id
#[derive(Hash, Default, Eq)]
#[derive(Hash, Default)]
pub struct MemberCompositeKey {
/// Server Id
pub server: String,
@@ -4,6 +4,7 @@ use revolt_result::{create_error, Error, Result};
use crate::{Database, User};
#[async_trait::async_trait]
impl<S> FromRequestParts<S> for User
where
Database: FromRef<S>,
@@ -4,6 +4,8 @@ mod model;
mod ops;
#[cfg(feature = "rocket-impl")]
mod rocket;
#[cfg(feature = "rocket-impl")]
mod schema;
pub use model::*;
pub use ops::*;
@@ -15,7 +15,7 @@ use serde_json::json;
use ulid::Ulid;
auto_derived_partial!(
/// User
/// # User
pub struct User {
/// Unique Id
#[serde(rename = "_id")]
@@ -0,0 +1,31 @@
use revolt_okapi::openapi3::{SecurityScheme, SecuritySchemeData};
use revolt_rocket_okapi::{
gen::OpenApiGenerator,
request::{OpenApiFromRequest, RequestHeaderInput},
};
use crate::User;
impl OpenApiFromRequest<'_> for User {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
let mut requirements = schemars::Map::new();
requirements.insert("Session Token".to_owned(), vec![]);
Ok(RequestHeaderInput::Security(
"Session Token".to_owned(),
SecurityScheme {
data: SecuritySchemeData::ApiKey {
name: "x-session-token".to_owned(),
location: "header".to_owned(),
},
description: Some("Used to authenticate as a user.".to_owned()),
extensions: schemars::Map::new(),
},
requirements,
))
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ use super::DelayedTask;
use crate::Channel::TextChannel;
/// Enumeration of possible events
#[derive(Debug, PartialEq)]
#[derive(Debug, Eq, PartialEq)]
pub enum AckEvent {
/// Add mentions for a channel
ProcessMessage {
+49 -47
View File
@@ -10,16 +10,16 @@ use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
#[cfg_attr(feature = "utoipa", into_params(names("Idempotency-Key"), parameter_in=Header))]
pub struct IdempotencyKey(String);
pub struct IdempotencyKey {
key: String,
}
static TOKEN_CACHE: Lazy<Mutex<lru::LruCache<String, ()>>> =
Lazy::new(|| Mutex::new(lru::LruCache::new(NonZeroUsize::new(1000).unwrap())));
impl IdempotencyKey {
pub fn unchecked_from_string(key: String) -> Self {
Self(key)
Self { key }
}
// Backwards compatibility.
@@ -32,56 +32,56 @@ impl IdempotencyKey {
}
cache.put(v.clone(), ());
self.0 = v;
self.key = v;
}
Ok(())
}
pub fn into_key(self) -> String {
self.0
self.key
}
}
// #[cfg(feature = "rocket-impl")]
// use revolt_rocket_okapi::{
// gen::OpenApiGenerator,
// request::{OpenApiFromRequest, RequestHeaderInput},
// revolt_okapi::openapi3::{Parameter, ParameterValue},
// };
#[cfg(feature = "rocket-impl")]
use revolt_rocket_okapi::{
gen::OpenApiGenerator,
request::{OpenApiFromRequest, RequestHeaderInput},
revolt_okapi::openapi3::{Parameter, ParameterValue},
};
// #[cfg(feature = "rocket-impl")]
// use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
#[cfg(feature = "rocket-impl")]
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
// #[cfg(feature = "rocket-impl")]
// impl OpenApiFromRequest<'_> for IdempotencyKey {
// fn from_request_input(
// _gen: &mut OpenApiGenerator,
// _name: String,
// _required: bool,
// ) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
// Ok(RequestHeaderInput::Parameter(Parameter {
// name: "Idempotency-Key".to_string(),
// description: Some("Unique key to prevent duplicate requests".to_string()),
// allow_empty_value: false,
// required: false,
// deprecated: false,
// extensions: schemars::Map::new(),
// location: "header".to_string(),
// value: ParameterValue::Schema {
// allow_reserved: false,
// example: None,
// examples: None,
// explode: None,
// style: None,
// schema: SchemaObject {
// instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
// ..Default::default()
// },
// },
// }))
// }
// }
#[cfg(feature = "rocket-impl")]
impl OpenApiFromRequest<'_> for IdempotencyKey {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::Parameter(Parameter {
name: "Idempotency-Key".to_string(),
description: Some("Unique key to prevent duplicate requests".to_string()),
allow_empty_value: false,
required: false,
deprecated: false,
extensions: schemars::Map::new(),
location: "header".to_string(),
value: ParameterValue::Schema {
allow_reserved: false,
example: None,
examples: None,
explode: None,
style: None,
schema: SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
..Default::default()
},
},
}))
}
}
#[cfg(feature = "rocket-impl")]
use rocket::{
@@ -110,16 +110,18 @@ impl<'r> FromRequest<'r> for IdempotencyKey {
));
}
let idempotency = IdempotencyKey(key);
let idempotency = IdempotencyKey { key };
let mut cache = TOKEN_CACHE.lock().await;
if cache.get(&idempotency.0).is_some() {
if cache.get(&idempotency.key).is_some() {
return Outcome::Error((Status::Conflict, create_error!(DuplicateNonce)));
}
cache.put(idempotency.0.clone(), ());
cache.put(idempotency.key.clone(), ());
return Outcome::Success(idempotency);
}
Outcome::Success(IdempotencyKey(ulid::Ulid::new().to_string()))
Outcome::Success(IdempotencyKey {
key: ulid::Ulid::new().to_string(),
})
}
}
-2
View File
@@ -5,7 +5,5 @@ pub mod idempotency;
pub mod permissions;
pub mod reference;
pub mod test_fixtures;
#[cfg(feature = "utoipa")]
pub mod utoipa;
pub use funcs::*;
@@ -3,6 +3,11 @@ use std::str::FromStr;
use revolt_result::Result;
#[cfg(feature = "rocket-impl")]
use rocket::request::FromParam;
#[cfg(feature = "rocket-impl")]
use schemars::{
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
JsonSchema,
};
use crate::{
Bot, Channel, Database, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook,
@@ -107,3 +112,17 @@ impl<'r> FromParam<'r> for Reference<'r> {
Ok(Reference::from_unchecked(param))
}
}
#[cfg(feature = "rocket-impl")]
impl<'a> JsonSchema for Reference<'a> {
fn schema_name() -> String {
"Id".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
..Default::default()
})
}
}
-49
View File
@@ -1,49 +0,0 @@
use utoipa::{
openapi::{
schema::SchemaType,
security::{ApiKey, ApiKeyValue, SecurityScheme},
ObjectBuilder, OpenApi, RefOr, Schema, Type,
},
Modify, PartialSchema, ToSchema,
};
use crate::util::reference::Reference;
pub struct TokenSecurity;
impl Modify for TokenSecurity {
fn modify(&self, openapi: &mut OpenApi) {
let components = openapi.components.get_or_insert_default();
components.add_security_scheme(
"Session-Token",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new(
"X-Session-Token".to_string(),
))),
);
components.add_security_scheme(
"Bot-Token",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("X-Bot-Ticket".to_string()))),
);
}
}
impl ToSchema for Reference<'_> {
fn name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("Reference")
}
}
impl PartialSchema for Reference<'_> {
fn schema() -> RefOr<Schema> {
RefOr::T(
ObjectBuilder::new()
.description(Some("An id referencing a stoat model."))
.schema_type(SchemaType::Type(Type::String))
.examples(["01FD58YK5W7QRV5H3D64KTQYX3"])
.build()
.into(),
)
}
}
+8 -7
View File
@@ -10,22 +10,22 @@ description = "Revolt Backend: API Models"
[features]
serde = ["dep:serde", "revolt-permissions/serde", "indexmap/serde"]
schemas = ["dep:schemars", "revolt-permissions/schemas"]
utoipa = ["dep:utoipa"]
validator = ["dep:validator"]
rocket = ["dep:rocket"]
partials = ["dep:revolt_optional_struct", "serde", "utoipa"]
partials = ["dep:revolt_optional_struct", "serde", "schemas", "utoipa"]
default = ["serde", "partials", "rocket"]
[dependencies]
# Core
revolt-config = { version = "0.8.9", path = "../config" }
revolt-permissions = { version = "0.8.9", path = "../permissions", features = [
"utoipa",
] }
revolt-permissions = { version = "0.8.9", path = "../permissions" }
# Utility
regex = "1.11"
indexmap = "2.12.0"
indexmap = "1.9.3"
once_cell = "1.17.1"
num_enum = "0.6.1"
@@ -35,10 +35,11 @@ rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
# Serialisation
revolt_optional_struct = { version = "0.2.0", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
iso8601-timestamp = { version = "0.4.0", features = ["utoipa", "bson"] }
iso8601-timestamp = { version = "0.2.11", features = ["schema", "bson"] }
# Spec Generation
utoipa = { version = "5.4.0", features = ["indexmap"], optional = true }
schemars = { version = "0.8.8", optional = true, features = ["indexmap1"] }
utoipa = { version = "4.2.3", optional = true }
# Validation
validator = { version = "0.16.0", optional = true, features = ["derive"] }
+20 -4
View File
@@ -2,6 +2,10 @@
#[macro_use]
extern crate serde;
#[cfg(feature = "schemas")]
#[macro_use]
extern crate schemars;
#[cfg(feature = "utoipa")]
#[macro_use]
extern crate utoipa;
@@ -17,8 +21,9 @@ macro_rules! auto_derived {
( $( $item:item )+ ) => {
$(
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq)]
$item
)+
};
@@ -27,8 +32,19 @@ macro_rules! auto_derived {
#[cfg(feature = "partials")]
macro_rules! auto_derived_partial {
( $item:item, $name:expr ) => {
#[derive(OptionalStruct, Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
#[optional_derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Default)]
#[derive(
OptionalStruct, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema,
)]
#[optional_derive(
Debug,
Clone,
Eq,
PartialEq,
Serialize,
Deserialize,
JsonSchema,
Default
)]
#[optional_name = $name]
#[opt_skip_serializing_none]
#[opt_some_priority]
@@ -39,7 +55,7 @@ macro_rules! auto_derived_partial {
#[cfg(not(feature = "partials"))]
macro_rules! auto_derived_partial {
( $item:item, $name:expr ) => {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
$item
};
}
@@ -86,10 +86,10 @@ auto_derived!(
/// Information for the webhook
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct CreateWebhookBody {
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
#[validate(length(min = 1, max = 32))]
pub name: String,
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
#[validate(length(min = 1, max = 128))]
pub avatar: Option<String>,
}
);
-1
View File
@@ -271,7 +271,6 @@ auto_derived!(
/// Options when deleting a channel
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsChannelDelete {
/// Whether to not send a leave message
pub leave_silently: Option<bool>,
-78
View File
@@ -1,78 +0,0 @@
auto_derived!(
/// hCaptcha Configuration
pub struct CaptchaFeature {
/// Whether captcha is enabled
pub enabled: bool,
/// Client key used for solving captcha
pub key: String,
}
/// Generic Service Configuration
pub struct Feature {
/// Whether the service is enabled
pub enabled: bool,
/// URL pointing to the service
pub url: String,
}
/// # Information about a livekit node
pub struct VoiceNode {
pub name: String,
pub lat: f64,
pub lon: f64,
pub public_url: String,
}
/// # Voice Server Configuration
pub struct VoiceFeature {
/// Whether voice is enabled
pub enabled: bool,
/// All livekit nodes
pub nodes: Vec<VoiceNode>,
}
/// Feature Configuration
pub struct RevoltFeatures {
/// hCaptcha configuration
pub captcha: CaptchaFeature,
/// Whether email verification is enabled
pub email: bool,
/// Whether this server is invite only
pub invite_only: bool,
/// File server service configuration
pub autumn: Feature,
/// Proxy service configuration
pub january: Feature,
/// Voice server configuration
pub livekit: VoiceFeature,
}
/// Build Information
pub struct BuildInformation {
/// Commit Hash
pub commit_sha: String,
/// Commit Timestamp
pub commit_timestamp: String,
/// Git Semver
pub semver: String,
/// Git Origin URL
pub origin_url: String,
/// Build Timestamp
pub timestamp: String,
}
/// Server Configuration
pub struct RevoltConfig {
/// Revolt API Version
pub revolt: String,
/// Features enabled on this Revolt node
pub features: RevoltFeatures,
/// WebSocket URL
pub ws: String,
/// URL pointing to the client serving this node
pub app: String,
/// Web Push VAPID public key
pub vapid: String,
/// Build information
pub build: BuildInformation,
}
);
+1 -1
View File
@@ -46,7 +46,7 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataCreateEmoji {
/// Server name
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32), regex = "RE_EMOJI"))]
#[validate(length(min = 1, max = 32), regex = "RE_EMOJI")]
pub name: String,
/// Parent information
pub parent: EmojiParent,
+4 -6
View File
@@ -141,17 +141,17 @@ auto_derived!(
pub struct Masquerade {
/// Replace the display name shown on this message
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
#[validate(length(min = 1, max = 32))]
pub name: Option<String>,
/// Replace the avatar shown on this message (URL to image file)
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
#[validate(length(min = 1, max = 256))]
pub avatar: Option<String>,
/// Replace the display role colour shown on this message
///
/// Must have `ManageRole` permission to use
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128), regex = "RE_COLOUR"))]
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
pub colour: Option<String>,
}
@@ -281,7 +281,6 @@ auto_derived!(
/// Options for querying messages
#[cfg_attr(feature = "validator", derive(Validate))]
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsQueryMessages {
/// Maximum number of messages to fetch
///
@@ -354,13 +353,12 @@ auto_derived!(
)]
pub struct OptionsBulkDelete {
/// Message IDs
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 100)))]
#[validate(length(min = 1, max = 100))]
pub ids: Vec<String>,
}
/// Options for removing reaction
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsUnreact {
/// Remove a specific user's reaction
pub user_id: Option<String>,
-2
View File
@@ -3,7 +3,6 @@ mod channel_invites;
mod channel_unreads;
mod channel_webhooks;
mod channels;
mod config;
mod embeds;
mod emojis;
mod files;
@@ -21,7 +20,6 @@ pub use channel_invites::*;
pub use channel_unreads::*;
pub use channel_webhooks::*;
pub use channels::*;
pub use config::*;
pub use embeds::*;
pub use emojis::*;
pub use files::*;
@@ -116,7 +116,6 @@ auto_derived!(
/// Options for fetching all members
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsFetchAllMembers {
/// Whether to exclude offline users
pub exclude_offline: Option<bool>,
-2
View File
@@ -198,7 +198,6 @@ auto_derived!(
/// Options when fetching server
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsFetchServer {
/// Whether to include channels
pub include_channels: Option<bool>,
@@ -285,7 +284,6 @@ auto_derived!(
/// Options when leaving a server
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsServerDelete {
/// Whether to not send a leave message
pub leave_silently: Option<bool>,
@@ -17,7 +17,6 @@ auto_derived!(
/// Additional options for inserting settings
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsSetSettings {
/// Timestamp of settings change.
///
+2 -2
View File
@@ -142,7 +142,7 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct UserStatus {
/// Custom status text
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 128)))]
#[validate(length(min = 0, max = 128))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub text: Option<String>,
/// Current presence option
@@ -155,7 +155,7 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct UserProfile {
/// Text content on user's profile
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))]
#[validate(length(min = 0, max = 2000))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub content: Option<String>,
/// Background visible on user's profile
+3 -1
View File
@@ -11,8 +11,10 @@ description = "Revolt Backend: Permission Logic"
[features]
bson = ["dep:bson"]
serde = ["dep:serde"]
schemas = ["dep:schemars"]
try-from-primitive = ["dep:num_enum"]
[dev-dependencies]
# Async
async-std = { version = "1.8.0", features = ["attributes"] }
@@ -34,4 +36,4 @@ serde = { version = "1", features = ["derive"], optional = true }
bson = { version = "2.1.0", optional = true }
# Spec Generation
utoipa = { version = "5.4.0", optional = true }
schemars = { version = "0.8.8", optional = true }
+7 -7
View File
@@ -1,10 +1,10 @@
#[cfg(feature = "utoipa")]
use utoipa::ToSchema;
#[cfg(feature = "schemas")]
use schemars::JsonSchema;
/// Representation of a single permission override
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
pub struct Override {
/// Allow bit flags
pub allow: u64,
@@ -15,7 +15,7 @@ pub struct Override {
/// Data permissions Field - contains both allow and deny
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
pub struct DataPermissionsField {
pub permissions: Override,
}
@@ -23,7 +23,7 @@ pub struct DataPermissionsField {
/// Data permissions Value - contains allow
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
pub struct DataPermissionsValue {
pub permissions: u64,
}
@@ -31,7 +31,7 @@ pub struct DataPermissionsValue {
/// Data permissions Poly - can contain either Value or Field
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum DataPermissionPoly {
Value {
@@ -48,7 +48,7 @@ pub enum DataPermissionPoly {
/// as it appears on models and in the database
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
pub struct OverrideField {
/// Allow bit flags
pub a: i64,
+4 -6
View File
@@ -4,22 +4,20 @@ version = "0.8.9"
edition = "2024"
[features]
rocket = [
"dep:rocket",
"revolt-database/rocket-impl",
]
rocket = ["dep:rocket", "dep:revolt_rocket_okapi", "revolt-database/rocket-impl"]
axum = ["dep:axum", "revolt-database/axum-impl"]
default = ["rocket", "axum"]
[dependencies]
revolt-database = { version = "0.8.9", path = "../database" }
revolt-database = { version = "0.8.9", path = "../database"}
revolt-result = { version = "0.8.9", path = "../result" }
revolt-config = { version = "0.8.9", path = "../config" }
rocket = { version = "0.5.1", optional = true }
revolt_rocket_okapi = { version = "0.10.0", optional = true }
axum = { version = "0.8.6", optional = true, features = ["macros"] }
axum = { version = "0.7.5", optional = true, features = ["macros"] }
serde = { version = "1", features = ["derive"] }
authifier = { version = "1.0.15" }
+3
View File
@@ -1,5 +1,6 @@
use std::net::SocketAddr;
use async_trait::async_trait;
use axum::{
Json, RequestPartsExt, Router,
body::Body,
@@ -43,6 +44,7 @@ async fn to_real_ip(parts: &Parts) -> String {
}
}
#[async_trait]
impl<S: Send + Sync> FromRequestParts<S> for Ratelimiter
where
Database: FromRef<S>,
@@ -80,6 +82,7 @@ where
}
}
#[async_trait]
impl<S: Send + Sync> FromRequestParts<S> for RatelimitInformation
where
Database: FromRef<S>,
+13
View File
@@ -8,6 +8,9 @@ use rocket::serde::json::Json;
use rocket::{Data, Request, Response, State};
use revolt_config::config;
use revolt_rocket_okapi::r#gen::OpenApiGenerator;
use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use authifier::models::Session;
use crate::ratelimiter::RequestKind;
@@ -75,6 +78,16 @@ impl<'r> FromRequest<'r> for Ratelimiter {
}
}
impl OpenApiFromRequest<'_> for Ratelimiter {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::None)
}
}
/// Attach ratelimiter to the Rocket application
pub struct RatelimitFairing;
+8 -3
View File
@@ -10,9 +10,11 @@ description = "Revolt Backend: Result and Error types"
[features]
serde = ["dep:serde"]
schemas = ["dep:schemars"]
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", "sentry"]
@@ -23,14 +25,17 @@ serde_json = { version = "1", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
# Spec Generation
utoipa = { version = "5.4.0", optional = true }
schemars = { version = "0.8.8", optional = true }
utoipa = { version = "4.2.3", optional = true }
# Rocket
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
revolt_rocket_okapi = { version = "0.10.0", optional = true }
revolt_okapi = { version = "0.9.1", optional = true }
# utilities
log = "0.4"
# Axum
axum = { version = "0.8.6", optional = true }
axum = { version = "0.7.5", optional = true }
sentry = { version = "0.31.5", optional = true }
sentry = { version = "0.31.5", optional = true }
+13 -5
View File
@@ -5,10 +5,13 @@ use std::fmt::Display;
#[macro_use]
extern crate serde;
#[cfg(feature = "schemas")]
#[macro_use]
extern crate schemars;
#[cfg(feature = "utoipa")]
mod utoipa_impl;
#[cfg(feature = "utoipa")]
pub use crate::utoipa_impl::*;
#[macro_use]
extern crate utoipa;
#[cfg(feature = "rocket")]
pub mod rocket;
@@ -16,12 +19,16 @@ pub mod rocket;
#[cfg(feature = "axum")]
pub mod axum;
#[cfg(feature = "okapi")]
pub mod okapi;
/// Result type with custom Error
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Error information
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[derive(Debug, Clone)]
pub struct Error {
/// Type of error and additional information
@@ -43,7 +50,8 @@ impl std::error::Error for Error {}
/// Possible error types
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[derive(Debug, Clone)]
pub enum ErrorType {
/// This error was not labeled :(
+49
View File
@@ -0,0 +1,49 @@
use revolt_okapi::openapi3::SchemaObject;
use revolt_rocket_okapi::revolt_okapi::openapi3;
use schemars::schema::Schema;
use crate::Error;
impl revolt_rocket_okapi::response::OpenApiResponderInner for Error {
fn responses(
gen: &mut revolt_rocket_okapi::gen::OpenApiGenerator,
) -> std::result::Result<openapi3::Responses, revolt_rocket_okapi::OpenApiError> {
let mut content = revolt_okapi::Map::new();
let settings = schemars::gen::SchemaSettings::default().with(|s| {
s.option_nullable = true;
s.option_add_null_type = false;
s.definitions_path = "#/components/schemas/".to_string();
});
let mut schema_generator = settings.into_generator();
let schema = schema_generator.root_schema_for::<Error>();
let definitions = gen.schema_generator().definitions_mut();
for (key, value) in schema.definitions {
definitions.insert(key, value);
}
definitions.insert("Error".to_string(), Schema::Object(schema.schema));
content.insert(
"application/json".to_string(),
openapi3::MediaType {
schema: Some(SchemaObject {
reference: Some("#/components/schemas/Error".to_string()),
..Default::default()
}),
..Default::default()
},
);
Ok(openapi3::Responses {
default: Some(openapi3::RefOr::Object(openapi3::Response {
content,
description: "An error occurred.".to_string(),
..Default::default()
})),
..Default::default()
})
}
}
-52
View File
@@ -1,52 +0,0 @@
use utoipa::{Modify, openapi::{Content, OpenApi, Ref, RefOr, ResponseBuilder}};
pub struct ErrorAddon;
impl Modify for ErrorAddon {
fn modify(&self, utoipa: &mut OpenApi) {
let response = ResponseBuilder::new()
.description("An error occurred")
.content(
"application/json",
Content::new(Some(RefOr::Ref(Ref::from_schema_name("Error")))),
)
.build();
for path in utoipa.paths.paths.values_mut() {
if let Some(route) = path.get.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
if let Some(route) = path.delete.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
if let Some(route) = path.patch.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
if let Some(route) = path.post.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
if let Some(route) = path.put.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
}
}
}
+1 -1
View File
@@ -38,5 +38,5 @@ pretty_env_logger = "0.4.0"
serde_json = "1"
revolt_optional_struct = "0.2.0"
serde = { version = "1", features = ["derive"] }
iso8601-timestamp = { version = "0.4.0", features = ["serde", "bson"] }
iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
base64 = "0.22.1"
+3 -3
View File
@@ -40,9 +40,9 @@ revolt-database = { path = "../../core/database", features = ["voice"] }
revolt-permissions = { path = "../../core/permissions" }
# Voice
livekit-api = "=0.4.4"
livekit-protocol = "=0.4.0"
livekit-runtime = { version = "=0.3.1", features = ["tokio"] }
livekit-api = "0.4.4"
livekit-protocol = "0.4.0"
livekit-runtime = { version = "0.3.1", features = ["tokio"] }
# RabbitMQ
amqprs = { version = "1.7.0" }
+9 -10
View File
@@ -35,7 +35,7 @@ nanoid = "0.4.0"
serde_json = "1.0.57"
serde = { version = "1.0.115", features = ["derive"] }
validator = { version = "0.16", features = ["derive"] }
iso8601-timestamp = { version = "0.4.0", features = [] }
iso8601-timestamp = { version = "0.2.11", features = [] }
# async
futures = "0.3.8"
@@ -55,13 +55,13 @@ lettre = "0.10.0-alpha.4"
rocket = { version = "0.5.1", default-features = false, features = ["json"] }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" }
rocket_empty = { version = "0.1.1" }
rocket_empty = { version = "0.1.1", features = ["schema"] }
rocket_authifier = { version = "1.0.15" }
rocket_prometheus = "0.10.0-rc.3"
# spec generation
utoipa = { version = "5.4.0", features = ["rocket_extras"] }
utoipa-scalar = { version = "0.3.0", features = ["rocket"] }
schemars = "0.8.8"
revolt_rocket_okapi = { version = "0.10.0", features = ["swagger"] }
# rabbit
amqprs = { version = "1.7.0" }
@@ -72,22 +72,21 @@ revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database", features = [
"rocket-impl",
"redis-is-patched",
"utoipa",
"voice",
] }
revolt-models = { path = "../core/models", features = [
"utoipa",
"schemas",
"validator",
"rocket",
] }
revolt-presence = { path = "../core/presence" }
revolt-result = { path = "../core/result", features = ["rocket", "utoipa"] }
revolt-permissions = { path = "../core/permissions", features = ["utoipa"] }
revolt-result = { path = "../core/result", features = ["rocket", "okapi"] }
revolt-permissions = { path = "../core/permissions", features = ["schemas"] }
revolt-ratelimits = { path = "../core/ratelimits", features = ["rocket"] }
# voice
livekit-api = "=0.4.4"
livekit-protocol = "=0.4.0"
livekit-api = "0.4.4"
livekit-protocol = "0.4.0"
[build-dependencies]
vergen = "7.5.0"
+28 -4
View File
@@ -1,7 +1,7 @@
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate utoipa;
extern crate revolt_rocket_okapi;
#[macro_use]
extern crate serde_json;
@@ -15,8 +15,6 @@ use revolt_ratelimits::rocket as ratelimiter;
use rocket::{Build, Rocket};
use rocket_cors::{AllowedOrigins, CorsOptions};
use rocket_prometheus::PrometheusMetrics;
use utoipa::OpenApi;
use utoipa_scalar::{Scalar, Servable};
use std::net::Ipv4Addr;
use std::str::FromStr;
@@ -77,6 +75,31 @@ pub async fn web() -> Rocket<Build> {
.to_cors()
.expect("Failed to create CORS.");
// Configure Swagger
let swagger = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "/openapi.json".to_owned(),
..Default::default()
},
)
.into();
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "/0.8/openapi.json".to_owned(),
..Default::default()
},
)
.into();
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "/0.8/openapi.json".to_owned(),
..Default::default()
},
)
.into();
// Voice handler
let voice_client = VoiceClient::new(config.api.livekit.nodes.clone());
// Configure Rabbit
@@ -120,7 +143,8 @@ pub async fn web() -> Rocket<Build> {
.mount("/metrics", prometheus)
.mount("/", rocket_cors::catch_all_options_routes())
.mount("/", ratelimiter::routes())
.mount("/", Scalar::with_url("/scalar", routes::ApiDoc::openapi()))
.mount("/swagger/", swagger)
.mount("/0.8/swagger/", swagger_0_8)
.manage(authifier)
.manage(db)
.manage(amqp)
+2 -8
View File
@@ -5,16 +5,10 @@ use rocket::serde::json::Json;
use rocket::State;
use validator::Validate;
/// Create Bot
/// # Create Bot
///
/// Create a new Revolt bot.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::BotWithUserResponse),
),
)]
#[openapi(tag = "Bots")]
#[post("/create", data = "<info>")]
pub async fn create_bot(
db: &State<Database>,
+2 -8
View File
@@ -3,16 +3,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Delete Bot
/// # Delete Bot
///
/// Delete a bot by its id.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 204),
),
)]
#[openapi(tag = "Bots")]
#[delete("/<target>")]
pub async fn delete_bot(
db: &State<Database>,
+11 -10
View File
@@ -6,16 +6,10 @@ use rocket::State;
use rocket::serde::json::Json;
use validator::Validate;
/// Edit Bot
/// # Edit Bot
///
/// Edit bot details by its id.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::BotWithUserResponse),
),
)]
#[openapi(tag = "Bots")]
#[patch("/<target>", data = "<data>")]
pub async fn edit_bot(
db: &State<Database>,
@@ -66,8 +60,15 @@ pub async fn edit_bot(
..Default::default()
};
bot.update(db, partial, remove.into_iter().map(|v| v.into()).collect())
.await?;
bot.update(
db,
partial,
remove
.into_iter()
.map(|v| v.into())
.collect(),
)
.await?;
Ok(Json(v0::BotWithUserResponse {
bot: bot.into(),
+2 -8
View File
@@ -3,16 +3,10 @@ use revolt_models::v0::FetchBotResponse;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Fetch Bot
/// # Fetch Bot
///
/// Fetch details of a bot you own by its id.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = FetchBotResponse),
),
)]
#[openapi(tag = "Bots")]
#[get("/<bot>")]
pub async fn fetch_bot(
db: &State<Database>,
+2 -8
View File
@@ -5,16 +5,10 @@ use revolt_result::Result;
use rocket::serde::json::Json;
use rocket::State;
/// Fetch Owned Bots
/// # Fetch Owned Bots
///
/// Fetch all of the bots that you have control over.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = OwnedBotsResponse),
),
)]
#[openapi(tag = "Bots")]
#[get("/@me")]
pub async fn fetch_owned_bots(db: &State<Database>, user: User) -> Result<Json<OwnedBotsResponse>> {
let mut bots = db.fetch_bots_by_user(&user.id).await?;
+2 -8
View File
@@ -5,16 +5,10 @@ use revolt_result::{create_error, Result};
use rocket::serde::json::Json;
use rocket::State;
/// Fetch Public Bot
/// # Fetch Public Bot
///
/// Fetch details of a public (or owned) bot by its id.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = []), ()),
responses(
(status = 200, body = PublicBot),
),
)]
#[openapi(tag = "Bots")]
#[get("/<target>/invite")]
pub async fn fetch_public_bot(
db: &State<Database>,
+2 -8
View File
@@ -11,16 +11,10 @@ use rocket::State;
use rocket::serde::json::Json;
use rocket_empty::EmptyResponse;
/// Invite Bot
/// # Invite Bot
///
/// Invite a bot to a server or group by its id.`
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::BotWithUserResponse),
),
)]
#[openapi(tag = "Bots")]
#[post("/<target>/invite", data = "<dest>")]
pub async fn invite_bot(
db: &State<Database>,
+3 -16
View File
@@ -1,3 +1,4 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod create;
@@ -8,22 +9,8 @@ mod fetch_owned;
mod fetch_public;
mod invite;
#[derive(OpenApi)]
#[openapi(
paths(
create::create_bot,
invite::invite_bot,
fetch_public::fetch_public_bot,
fetch::fetch_bot,
fetch_owned::fetch_owned_bots,
edit::edit_bot,
delete::delete_bot,
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
create::create_bot,
invite::invite_bot,
fetch_public::fetch_public_bot,
@@ -7,20 +7,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Acknowledge Message
/// # Acknowledge Message
///
/// Lets the server and all other clients know that we've seen this message id in this channel.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
("message" = Reference, Path),
),
responses(
(status = 204)
)
)]
#[openapi(tag = "Messaging")]
#[put("/<target>/ack/<message>")]
pub async fn ack(
db: &State<Database>,
@@ -9,20 +9,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Close Channel
/// # Close Channel
///
/// Deletes a server channel, leaves a group or closes a group.
#[utoipa::path(
tag = "Channel Information",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
v0::OptionsChannelDelete,
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Channel Information")]
#[delete("/<target>?<options..>")]
pub async fn delete(
db: &State<Database>,
@@ -9,19 +9,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// Edit Channel
/// # Edit Channel
///
/// Edit a channel object by its id.
#[utoipa::path(
tag = "Channel Information",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Channel Information")]
#[patch("/<target>", data = "<data>")]
pub async fn edit(
db: &State<Database>,
@@ -8,19 +8,10 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::Result;
use rocket::{serde::json::Json, State};
/// Fetch Channel
/// # Fetch Channel
///
/// Fetch channel by its id.
#[utoipa::path(
tag = "Channel Information",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Channel Information")]
#[get("/<target>")]
pub async fn fetch(
db: &State<Database>,
@@ -8,20 +8,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Add Member to Group
/// # Add Member to Group
///
/// Adds another user to the group.
#[utoipa::path(
tag = "Groups",
security(("Session-Token" = [])),
params(
("group_id" = Reference, Path),
("member_id" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Groups")]
#[put("/<group_id>/recipients/<member_id>")]
pub async fn add_member(
db: &State<Database>,
@@ -6,16 +6,10 @@ use rocket::serde::json::Json;
use rocket::State;
use validator::Validate;
/// Create Group
/// # Create Group
///
/// Create a new group channel.
#[utoipa::path(
tag = "Groups",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Groups")]
#[post("/create", data = "<data>")]
pub async fn create_group(
db: &State<Database>,
@@ -9,20 +9,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Remove Member from Group
/// # Remove Member from Group
///
/// Removes a user from the group.
#[utoipa::path(
tag = "Groups",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
("member" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Groups")]
#[delete("/<target>/recipients/<member>")]
pub async fn remove_member(
db: &State<Database>,
@@ -8,21 +8,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Create Invite
/// # Create Invite
///
/// Creates an invite to this channel.
///
/// Channel must be a `TextChannel`.
#[utoipa::path(
tag = "Channel Invites",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Invite),
),
)]
#[openapi(tag = "Channel Invites")]
#[post("/<target>/invites")]
pub async fn create_invite(
db: &State<Database>,
@@ -7,21 +7,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Fetch Group Members
/// # Fetch Group Members
///
/// Retrieves all users who are part of this group.
///
/// This may not return full user information if users are not friends but have mutual connections.
#[utoipa::path(
tag = "Groups",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = Vec<v0::User>),
),
)]
#[openapi(tag = "Groups")]
#[get("/<target>/members")]
pub async fn fetch_members(
db: &State<Database>,
@@ -10,23 +10,14 @@ use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
use validator::Validate;
/// Bulk Delete Messages
/// # Bulk Delete Messages
///
/// Delete multiple messages you've sent or one you have permission to delete.
///
/// This will always require `ManageMessages` permission regardless of whether you own the message or not.
///
/// Messages must have been sent within the past 1 week.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Messaging")]
#[delete("/<target>/messages/bulk", data = "<options>", rank = 1)]
pub async fn bulk_delete_messages(
db: &State<Database>,
@@ -7,22 +7,12 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Remove All Reactions from Message
/// # Remove All Reactions from Message
///
/// Remove your own, someone else's or all of a given reaction.
///
/// Requires `ManageMessages` permission.
#[utoipa::path(
tag = "Interactions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Interactions")]
#[delete("/<target>/messages/<msg>/reactions")]
pub async fn clear_reactions(
db: &State<Database>,
@@ -47,7 +37,7 @@ pub async fn clear_reactions(
reactions: Some(Default::default()),
..Default::default()
},
vec![],
vec![]
)
.await
.map(|_| EmptyResponse)
@@ -7,20 +7,10 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Delete Message
/// # Delete Message
///
/// Delete a message you've sent or one you have permission to delete.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Messaging")]
#[delete("/<target>/messages/<msg>", rank = 2)]
pub async fn delete(
db: &State<Database>,
@@ -10,20 +10,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// Edit Message
/// # Edit Message
///
/// Edits a message that you've previously sent.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 200, body = v0::Message),
),
)]
#[openapi(tag = "Messaging")]
#[patch("/<target>/messages/<msg>", data = "<edit>")]
pub async fn edit(
db: &State<Database>,
@@ -7,20 +7,10 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Fetch Message
/// # Fetch Message
///
/// Retrieves a message by its id.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 200, body = v0::Message),
),
)]
#[openapi(tag = "Messaging")]
#[get("/<target>/messages/<msg>")]
pub async fn fetch(
db: &State<Database>,
@@ -8,20 +8,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Pins a message
/// # Pins a message
///
/// Pins a message by its id.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Messaging")]
#[post("/<target>/messages/<msg>/pin")]
pub async fn message_pin(
db: &State<Database>,
@@ -8,20 +8,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// Fetch Messages
/// # Fetch Messages
///
/// Fetch multiple messages.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
v0::OptionsQueryMessages,
),
responses(
(status = 200, body = v0::BulkMessageResponse),
),
)]
#[openapi(tag = "Messaging")]
#[get("/<target>/messages?<options..>")]
pub async fn query(
db: &State<Database>,
@@ -7,21 +7,10 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Add Reaction to Message
/// # Add Reaction to Message
///
/// React to a given message.
#[utoipa::path(
tag = "Interactions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
("emoji" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Interactions")]
#[put("/<target>/messages/<msg>/reactions/<emoji>")]
pub async fn react_message(
db: &State<Database>,
@@ -8,19 +8,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// Search for Messages
/// # Search for Messages
///
/// This route searches for messages within the given parameters.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::BulkMessageResponse),
),
)]
#[openapi(tag = "Messaging")]
#[post("/<target>/search", data = "<options>")]
pub async fn search(
db: &State<Database>,
@@ -12,20 +12,10 @@ use rocket::serde::json::Json;
use rocket::State;
use validator::Validate;
/// Send Message
/// # Send Message
///
/// Sends a message to the given channel.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(IdempotencyKey),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Message),
),
)]
#[openapi(tag = "Messaging")]
#[post("/<target>/messages", data = "<data>")]
pub async fn message_send(
db: &State<Database>,
@@ -1,27 +1,14 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
Channel, Database, FieldsMessage, PartialMessage, SystemMessage, User, AMQP,
};
use revolt_database::{util::{permissions::DatabasePermissionQuery, reference::Reference}, Channel, Database, FieldsMessage, PartialMessage, SystemMessage, User, AMQP};
use revolt_models::v0::MessageAuthor;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Unpins a message
/// # Unpins a message
///
/// Unpins a message by its id.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Messaging")]
#[delete("/<target>/messages/<msg>/pin")]
pub async fn message_unpin(
db: &State<Database>,
@@ -8,24 +8,12 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Remove Reaction(s) to Message
/// # Remove Reaction(s) to Message
///
/// Remove your own, someone else's or all of a given reaction.
///
/// Requires `ManageMessages` if changing others' reactions.
#[utoipa::path(
tag = "Interactions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
("emoji" = Reference, Path),
v0::OptionsUnreact,
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Interactions")]
#[delete("/<target>/messages/<msg>/reactions/<emoji>?<options..>")]
pub async fn unreact_message(
db: &State<Database>,
+3 -36
View File
@@ -1,3 +1,4 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod channel_ack;
@@ -28,42 +29,8 @@ mod voice_stop_ring;
mod webhook_create;
mod webhook_fetch_all;
#[derive(OpenApi)]
#[openapi(
paths(
channel_ack::ack,
channel_fetch::fetch,
members_fetch::fetch_members,
channel_delete::delete,
channel_edit::edit,
invite_create::create_invite,
message_send::message_send,
message_query::query,
message_search::search,
message_pin::message_pin,
message_fetch::fetch,
message_edit::edit,
message_bulk_delete::bulk_delete_messages,
message_delete::delete,
message_unpin::message_unpin,
group_create::create_group,
group_add_member::add_member,
group_remove_member::remove_member,
voice_join::call,
voice_stop_ring::stop_ring,
permissions_set::set_role_permissions,
permissions_set_default::set_default_channel_permissions,
message_react::react_message,
message_unreact::unreact_message,
message_clear_reactions::clear_reactions,
webhook_create::create_webhook,
webhook_fetch_all::fetch_webhooks,
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
channel_ack::ack,
channel_fetch::fetch,
members_fetch::fetch_members,
@@ -6,22 +6,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission, Overr
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Set Role Permission
/// # Set Role Permission
///
/// Sets permissions for the specified role in this channel.
///
/// Channel must be a `TextChannel`.
#[utoipa::path(
tag = "Channel Permissions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("role_id" = Reference, Path),
),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Channel Permissions")]
#[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
pub async fn set_role_permissions(
db: &State<Database>,
@@ -6,21 +6,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Set Default Permission
/// # Set Default Permission
///
/// Sets permissions for the default role in this channel.
///
/// Channel must be a `Group` or `TextChannel`.
#[utoipa::path(
tag = "Channel Permissions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Channel Permissions")]
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn set_default_channel_permissions(
db: &State<Database>,
+2 -11
View File
@@ -13,19 +13,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Join Call
/// # Join Call
///
/// Asks the voice server for a token to join the call.
#[utoipa::path(
tag = "Voice",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::DataJoinCall),
),
)]
#[openapi(tag = "Voice")]
#[post("/<target>/join_call", data = "<data>")]
pub async fn call(
db: &State<Database>,
@@ -8,23 +8,12 @@ use revolt_result::{create_error, Result, ToRevoltError};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Stop Ring
///
/// # Stop Ring
/// Stops ringing a specific user in a dm call.
/// You must be in the call to use this endpoint, returns NotConnected otherwise.
/// Only valid in DM/Group channels, will return NoEffect in servers.
/// Returns NotFound if the user is not in the dm/group channel
#[utoipa::path(
tag = "Voice",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("target_user." = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Voice")]
#[put("/<target>/end_ring/<target_user>")]
pub async fn stop_ring(
db: &State<Database>,
@@ -11,19 +11,10 @@ use rocket::{serde::json::Json, State};
use ulid::Ulid;
use validator::Validate;
/// Creates a webhook
/// # Creates a webhook
///
/// Creates a webhook which 3rd party platforms can use to send messages
#[utoipa::path(
tag = "Webhooks",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Webhook),
),
)]
#[openapi(tag = "Webhooks")]
#[post("/<target>/webhooks", data = "<data>")]
pub async fn create_webhook(
db: &State<Database>,
@@ -7,19 +7,10 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::Result;
use rocket::{serde::json::Json, State};
/// Gets all webhooks
/// # Gets all webhooks
///
/// Gets all webhooks inside the channel
#[utoipa::path(
tag = "Webhooks",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = Vec<Webhook>),
),
)]
#[openapi(tag = "Webhooks")]
#[get("/<channel_id>/webhooks")]
pub async fn fetch_webhooks(
db: &State<Database>,
@@ -1,5 +1,5 @@
use revolt_config::config;
use revolt_database::{Database, Emoji, File, User, util::{permissions::DatabasePermissionQuery, reference::Reference}};
use revolt_database::{util::permissions::DatabasePermissionQuery, Database, Emoji, File, User};
use revolt_models::v0;
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
@@ -7,19 +7,10 @@ use validator::Validate;
use rocket::{serde::json::Json, State};
/// Create New Emoji
/// # Create New Emoji
///
/// Create an emoji by its Autumn upload id.
#[utoipa::path(
tag = "Emojis",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("id" = Reference, Path),
),
responses(
(status = 200, body = v0::Emoji),
),
)]
#[openapi(tag = "Emojis")]
#[put("/emoji/<id>", data = "<data>")]
pub async fn create_emoji(
db: &State<Database>,
@@ -8,19 +8,10 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Delete Emoji
/// # Delete Emoji
///
/// Delete an emoji by its id.
#[utoipa::path(
tag = "Emojis",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("emoji_id" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Emojis")]
#[delete("/emoji/<emoji_id>")]
pub async fn delete_emoji(
db: &State<Database>,
@@ -4,19 +4,10 @@ use revolt_result::Result;
use rocket::{serde::json::Json, State};
/// Fetch Emoji
/// # Fetch Emoji
///
/// Fetch an emoji by its id.
#[utoipa::path(
tag = "Emojis",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("emoji_id" = Reference, Path),
),
responses(
(status = 200, body = v0::Emoji),
),
)]
#[openapi(tag = "Emojis")]
#[get("/emoji/<emoji_id>")]
pub async fn fetch_emoji(db: &State<Database>, emoji_id: Reference<'_>) -> Result<Json<v0::Emoji>> {
emoji_id
+3 -12
View File
@@ -1,21 +1,12 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod emoji_create;
mod emoji_delete;
mod emoji_fetch;
#[derive(OpenApi)]
#[openapi(
paths(
emoji_create::create_emoji,
emoji_delete::delete_emoji,
emoji_fetch::fetch_emoji
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
emoji_create::create_emoji,
emoji_delete::delete_emoji,
emoji_fetch::fetch_emoji
@@ -7,19 +7,10 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Delete Invite
/// # Delete Invite
///
/// Delete an invite by its id.
#[utoipa::path(
tag = "Invites",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Invites")]
#[delete("/<target>")]
pub async fn delete(db: &State<Database>, user: User, target: Reference<'_>) -> Result<EmptyResponse> {
let invite = target.as_invite(db).await?;
@@ -3,19 +3,10 @@ use revolt_models::v0;
use revolt_result::Result;
use rocket::{serde::json::Json, State};
/// Fetch Invite
/// # Fetch Invite
///
/// Fetch an invite by its id.
#[utoipa::path(
tag = "Invites",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::InviteResponse),
),
)]
#[openapi(tag = "Invites")]
#[get("/<target>")]
pub async fn fetch(db: &State<Database>, target: Reference<'_>) -> Result<Json<v0::InviteResponse>> {
Ok(Json(match target.as_invite(db).await? {
+2 -11
View File
@@ -3,19 +3,10 @@ use revolt_models::v0::{self, InviteJoinResponse};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Join Invite
/// # Join Invite
///
/// Join an invite by its ID
#[utoipa::path(
tag = "Invites",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::InviteJoinResponse),
),
)]
#[openapi(tag = "Invites")]
#[post("/<target>")]
pub async fn join(
db: &State<Database>,
+3 -13
View File
@@ -1,22 +1,12 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod invite_delete;
mod invite_fetch;
mod invite_join;
#[derive(OpenApi)]
#[openapi(
paths(
invite_fetch::fetch,
invite_join::join,
invite_delete::delete
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
invite_fetch::fetch,
invite_join::join,
invite_delete::delete
+349 -313
View File
@@ -1,14 +1,8 @@
use revolt_config::Settings;
use revolt_database::util::{utoipa::TokenSecurity, reference::Reference};
use revolt_result::ErrorAddon;
use revolt_rocket_okapi::{revolt_okapi::openapi3::OpenApi, settings::OpenApiSettings};
pub use rocket::http::Status;
pub use rocket::response::Redirect;
use rocket::{Build, Rocket};
use rocket_authifier::SecurityAddon;
use utoipa::{
openapi::{extensions::ExtensionsBuilder, OpenApi},
Modify,
};
mod bots;
mod channels;
@@ -25,320 +19,362 @@ mod users;
mod webhooks;
pub fn mount(config: Settings, mut rocket: Rocket<Build>) -> Rocket<Build> {
rocket = rocket
.mount("/", routes![root::root])
.mount("/users", users::routes())
.mount("/bots", bots::routes())
.mount("/channels", channels::routes())
.mount("/servers", servers::routes())
.mount("/invites", invites::routes())
.mount("/custom", customisation::routes())
.mount("/safety", safety::routes())
.mount("/auth/account", rocket_authifier::routes::account::routes())
.mount("/auth/session", rocket_authifier::routes::session::routes())
.mount("/auth/mfa", rocket_authifier::routes::mfa::routes())
.mount("/onboard", onboard::routes())
.mount("/policy", policy::routes())
.mount("/push", push::routes())
.mount("/sync", sync::routes());
let settings = OpenApiSettings::default();
if config.features.webhooks_enabled {
rocket = rocket.mount("/webhooks", webhooks::routes());
};
mount_endpoints_and_merged_docs! {
rocket, "/".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/auth/sso" => rocket_authifier::routes::sso::routes(),
"/onboard" => onboard::routes(),
"/policy" => policy::routes(),
"/push" => push::routes(),
"/sync" => sync::routes(),
"/webhooks" => webhooks::routes()
};
} else {
mount_endpoints_and_merged_docs! {
rocket, "/".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/auth/sso" => rocket_authifier::routes::sso::routes(),
"/onboard" => onboard::routes(),
"/policy" => policy::routes(),
"/push" => push::routes(),
"/sync" => sync::routes()
};
}
if config.features.webhooks_enabled {
mount_endpoints_and_merged_docs! {
rocket, "/0.8".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/auth/sso" => rocket_authifier::routes::sso::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes(),
"/webhooks" => webhooks::routes()
};
} else {
mount_endpoints_and_merged_docs! {
rocket, "/0.8".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/auth/sso" => rocket_authifier::routes::sso::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes()
};
}
rocket
}
#[derive(OpenApi)]
#[openapi(
info(
title = "Stoat API",
description = "Open source user-first chat platform.",
terms_of_service = "https://stoat.chat/terms",
contact(
name = "Stoat Support",
url = "https://stoat.chat",
email = "contact@stoat.chat",
),
license(
name = "AGPLv3",
url = "https://github.com/stoatchat/stoatchat/blob/master/LICENSE",
),
),
servers(
(
description = "Stoat Production",
url = "https://stoat.chat/api",
),
(
description = "Stoat Production",
url = "https://api.stoat.chat"
)
),
external_docs(
url = "https://developers.stoat.chat",
description = "Revolt Developer Documentation",
),
tags(
(
name = "Core",
description = "Use in your applications to determine information about the Revolt node"
),
(
name = "User Information",
description = "Query and fetch users on Revolt"
),
(
name = "Direct Messaging",
description = "Direct message other users on Revolt"
),
(
name = "Relationships",
description = "Manage your friendships and block list on the platform"
),
(
name = "Bots",
description = "Create and edit bots"
),
(
name = "Channel Information",
description = "Query and fetch channels on Revolt"
),
(
name = "Channel Invites",
description = "Create and manage invites for channels"
),
(
name = "Channel Permissions",
description = "Manage permissions for channels"
),
(
name = "Messaging",
description = "Send and manipulate messages"
),
(
name = "Groups",
description = "Create, invite users and manipulate groups"
),
(
name = "Voice",
description = "Join and talk with other users"
),
(
name = "Server Information",
description = "Query and fetch servers on Revolt"
),
(
name = "Server Members",
description = "Find and edit server members"
),
(
name = "Server Permissions",
description = "Manage permissions for servers"
),
(
name = "Invites",
description = "View, join and delete invites"
),
(
name = "Account",
description = "Manage your account"
),
(
name = "Session",
description = "Create and manage sessions"
),
(
name = "MFA",
description = "Multi-factor Authentication"
),
(
name = "Onboarding",
description = "After signing up to Revolt, users must pick a unique username"
),
(
name = "Sync",
description = "Upload and retrieve any JSON data between clients"
),
(
name = "Web Push",
description = "Subscribe to and receive Revolt push notifications while offline"
),
(
name = "Webhooks",
description = "Send messages from 3rd party services"
),
),
paths(
root::root,
),
nest(
(
path = "/users",
api = users::ApiDoc,
),
(
path = "/bots",
api = bots::ApiDoc,
),
(
path = "/channels",
api = channels::ApiDoc,
),
(
path = "/servers",
api = servers::ApiDoc,
),
(
path = "/invites",
api = invites::ApiDoc,
),
(
path = "/custom",
api = customisation::ApiDoc,
),
(
path = "/safety",
api = safety::ApiDoc,
),
(
path = "/auth/account",
api = rocket_authifier::routes::account::ApiDoc,
),
(
path = "/auth/session",
api = rocket_authifier::routes::session::ApiDoc,
),
(
path = "/auth/mfa",
api = rocket_authifier::routes::mfa::ApiDoc,
),
(
path = "/onboard",
api = onboard::ApiDoc,
),
(
path = "/policy",
api = policy::ApiDoc,
),
(
path = "/push",
api = push::ApiDoc,
),
(
path = "/sync",
api = sync::ApiDoc,
),
(
path = "/webhooks",
api = webhooks::ApiDoc,
),
fn custom_openapi_spec() -> OpenApi {
use revolt_rocket_okapi::revolt_okapi::openapi3::*;
),
components(
schemas(
Reference,
)
),
modifiers(
&Extensions,
let mut extensions = schemars::Map::new();
extensions.insert(
"x-logo".to_owned(),
json!({
"url": "https://revolt.chat/header.png",
"altText": "Revolt Header"
}),
);
// TODO: merge these together when authifier is moved into the backend
&SecurityAddon,
&TokenSecurity,
extensions.insert(
"x-tagGroups".to_owned(),
json!([
{
"name": "Revolt",
"tags": [
"Core"
]
},
{
"name": "Users",
"tags": [
"User Information",
"Direct Messaging",
"Relationships"
]
},
{
"name": "Bots",
"tags": [
"Bots"
]
},
{
"name": "Channels",
"tags": [
"Channel Information",
"Channel Invites",
"Channel Permissions",
"Messaging",
"Interactions",
"Groups",
"Voice",
"Webhooks",
]
},
{
"name": "Servers",
"tags": [
"Server Information",
"Server Members",
"Server Permissions"
]
},
{
"name": "Invites",
"tags": [
"Invites"
]
},
{
"name": "Customisation",
"tags": [
"Emojis"
]
},
{
"name": "Platform Administration",
"tags": [
"Admin",
"User Safety"
]
},
{
"name": "Authentication",
"tags": [
"Account",
"Session",
"Onboarding",
"MFA"
]
},
{
"name": "Miscellaneous",
"tags": [
"Sync",
"Web Push"
]
}
]),
);
&ErrorAddon,
),
)]
pub struct ApiDoc;
struct Extensions;
impl Modify for Extensions {
fn modify(&self, utoipa: &mut OpenApi) {
utoipa.extensions = Some(
ExtensionsBuilder::new()
.add(
"tagGroups",
json!([
{
"name": "Revolt",
"tags": [
"Core"
]
},
{
"name": "Users",
"tags": [
"User Information",
"Direct Messaging",
"Relationships"
]
},
{
"name": "Bots",
"tags": [
"Bots"
]
},
{
"name": "Channels",
"tags": [
"Channel Information",
"Channel Invites",
"Channel Permissions",
"Messaging",
"Interactions",
"Groups",
"Voice",
"Webhooks",
]
},
{
"name": "Servers",
"tags": [
"Server Information",
"Server Members",
"Server Permissions"
]
},
{
"name": "Invites",
"tags": [
"Invites"
]
},
{
"name": "Customisation",
"tags": [
"Emojis"
]
},
{
"name": "Platform Administration",
"tags": [
"Admin",
"User Safety"
]
},
{
"name": "Authentication",
"tags": [
"Account",
"Session",
"Onboarding",
"MFA"
]
},
{
"name": "Miscellaneous",
"tags": [
"Sync",
"Web Push"
]
}
]),
)
.build(),
);
OpenApi {
openapi: OpenApi::default_version(),
info: Info {
title: "Revolt API".to_owned(),
description: Some("Open source user-first chat platform.".to_owned()),
terms_of_service: Some("https://revolt.chat/terms".to_owned()),
contact: Some(Contact {
name: Some("Revolt Support".to_owned()),
url: Some("https://revolt.chat".to_owned()),
email: Some("contact@revolt.chat".to_owned()),
..Default::default()
}),
license: Some(License {
name: "AGPLv3".to_owned(),
url: Some("https://github.com/revoltchat/delta/blob/master/LICENSE".to_owned()),
..Default::default()
}),
version: env!("CARGO_PKG_VERSION").to_string(),
..Default::default()
},
servers: vec![
Server {
url: "https://api.revolt.chat".to_owned(),
description: Some("Revolt Production".to_owned()),
..Default::default()
},
Server {
url: "https://revolt.chat/api".to_owned(),
description: Some("Revolt Staging".to_owned()),
..Default::default()
},
Server {
url: "http://local.revolt.chat:14702".to_owned(),
description: Some("Local Revolt Environment".to_owned()),
..Default::default()
},
Server {
url: "http://local.revolt.chat:14702/0.8".to_owned(),
description: Some("Local Revolt Environment (v0.8)".to_owned()),
..Default::default()
},
],
external_docs: Some(ExternalDocs {
url: "https://developers.revolt.chat".to_owned(),
description: Some("Revolt Developer Documentation".to_owned()),
..Default::default()
}),
extensions,
tags: vec![
Tag {
name: "Core".to_owned(),
description: Some(
"Use in your applications to determine information about the Revolt node"
.to_owned(),
),
..Default::default()
},
Tag {
name: "User Information".to_owned(),
description: Some("Query and fetch users on Revolt".to_owned()),
..Default::default()
},
Tag {
name: "Direct Messaging".to_owned(),
description: Some("Direct message other users on Revolt".to_owned()),
..Default::default()
},
Tag {
name: "Relationships".to_owned(),
description: Some(
"Manage your friendships and block list on the platform".to_owned(),
),
..Default::default()
},
Tag {
name: "Bots".to_owned(),
description: Some("Create and edit bots".to_owned()),
..Default::default()
},
Tag {
name: "Channel Information".to_owned(),
description: Some("Query and fetch channels on Revolt".to_owned()),
..Default::default()
},
Tag {
name: "Channel Invites".to_owned(),
description: Some("Create and manage invites for channels".to_owned()),
..Default::default()
},
Tag {
name: "Channel Permissions".to_owned(),
description: Some("Manage permissions for channels".to_owned()),
..Default::default()
},
Tag {
name: "Messaging".to_owned(),
description: Some("Send and manipulate messages".to_owned()),
..Default::default()
},
Tag {
name: "Groups".to_owned(),
description: Some("Create, invite users and manipulate groups".to_owned()),
..Default::default()
},
Tag {
name: "Voice".to_owned(),
description: Some("Join and talk with other users".to_owned()),
..Default::default()
},
Tag {
name: "Server Information".to_owned(),
description: Some("Query and fetch servers on Revolt".to_owned()),
..Default::default()
},
Tag {
name: "Server Members".to_owned(),
description: Some("Find and edit server members".to_owned()),
..Default::default()
},
Tag {
name: "Server Permissions".to_owned(),
description: Some("Manage permissions for servers".to_owned()),
..Default::default()
},
Tag {
name: "Invites".to_owned(),
description: Some("View, join and delete invites".to_owned()),
..Default::default()
},
Tag {
name: "Account".to_owned(),
description: Some("Manage your account".to_owned()),
..Default::default()
},
Tag {
name: "Session".to_owned(),
description: Some("Create and manage sessions".to_owned()),
..Default::default()
},
Tag {
name: "MFA".to_owned(),
description: Some("Multi-factor Authentication".to_owned()),
..Default::default()
},
Tag {
name: "Onboarding".to_owned(),
description: Some(
"After signing up to Revolt, users must pick a unique username".to_owned(),
),
..Default::default()
},
Tag {
name: "Sync".to_owned(),
description: Some("Upload and retrieve any JSON data between clients".to_owned()),
..Default::default()
},
Tag {
name: "Web Push".to_owned(),
description: Some(
"Subscribe to and receive Revolt push notifications while offline".to_owned(),
),
..Default::default()
},
Tag {
name: "Webhooks".to_owned(),
description: Some("Send messages from 3rd party services".to_owned()),
..Default::default()
},
],
..Default::default()
}
}
+4 -10
View File
@@ -15,24 +15,18 @@ use validator::Validate;
/// 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, ToSchema)]
/// # 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
/// # Complete Onboarding
///
/// This sets a new username, completes onboarding and allows a user to start using Revolt.
#[utoipa::path(
tag = "Onboarding",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::User),
),
)]
#[openapi(tag = "Onboarding")]
#[post("/complete", data = "<data>")]
pub async fn complete(
db: &State<Database>,
+4 -10
View File
@@ -4,23 +4,17 @@ use revolt_database::User;
use rocket::serde::json::Json;
use serde::Serialize;
/// Onboarding Status
#[derive(Serialize, ToSchema)]
/// # Onboarding Status
#[derive(Serialize, JsonSchema)]
pub struct DataHello {
/// Whether onboarding is required
onboarding: bool,
}
/// Check Onboarding Status
/// # 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.
#[utoipa::path(
tag = "Onboarding",
security(("Session-Token" = [])),
responses(
(status = 200, body = DataHello),
),
)]
#[openapi(tag = "Onboarding")]
#[get("/hello")]
pub async fn hello(_session: Session, user: Option<User>) -> Json<DataHello> {
Json(DataHello {
+3 -11
View File
@@ -1,17 +1,9 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod complete;
mod hello;
#[derive(OpenApi)]
#[openapi(
paths(
hello::hello, complete::complete
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![hello::hello, complete::complete]
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![hello::hello, complete::complete]
}
@@ -4,16 +4,10 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Acknowledge Policy Changes
/// # Acknowledge Policy Changes
///
/// Accept/acknowledge changes to platform policy.
#[utoipa::path(
tag = "Policy",
security(("Session-Token" = []), ("Bot-Token" = [])),
responses(
(status = 204),
),
)]
#[openapi(tag = "Policy")]
#[post("/acknowledge")]
pub async fn acknowledge_policy_changes(db: &State<Database>, user: User) -> Result<EmptyResponse> {
db.acknowledge_policy_changes(&user.id).await?;
+3 -11
View File
@@ -1,18 +1,10 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod acknowledge_policy_changes;
#[derive(OpenApi)]
#[openapi(
paths(
acknowledge_policy_changes::acknowledge_policy_changes
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
// Policy
acknowledge_policy_changes::acknowledge_policy_changes,
]
+3 -11
View File
@@ -1,17 +1,9 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod subscribe;
mod unsubscribe;
#[derive(OpenApi)]
#[openapi(
paths(
subscribe::subscribe,
unsubscribe::unsubscribe
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![subscribe::subscribe, unsubscribe::unsubscribe]
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![subscribe::subscribe, unsubscribe::unsubscribe]
}
+2 -8
View File
@@ -6,18 +6,12 @@ use revolt_result::{create_database_error, Result};
use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
/// Push Subscribe
/// # Push Subscribe
///
/// Create a new Web Push subscription.
///
/// If an existing subscription exists on this session, it will be removed.
#[utoipa::path(
tag = "Web Push",
security(("Session-Token" = [])),
responses(
(status = 204),
),
)]
#[openapi(tag = "Web Push")]
#[post("/subscribe", data = "<data>")]
pub async fn subscribe(
authifier: &State<Authifier>,
+2 -8
View File
@@ -5,16 +5,10 @@ use rocket_empty::EmptyResponse;
use rocket::State;
/// Unsubscribe
/// # Unsubscribe
///
/// Remove the Web Push subscription associated with the current session.
#[utoipa::path(
tag = "Web Push",
security(("Session-Token" = [])),
responses(
(status = 204),
),
)]
#[openapi(tag = "Web Push")]
#[post("/unsubscribe")]
pub async fn unsubscribe(
authifier: &State<Authifier>,
+97 -15
View File
@@ -1,37 +1,119 @@
use revolt_config::config;
use revolt_models::v0;
use revolt_result::Result;
use rocket::serde::json::Json;
use serde::Serialize;
/// Query Node
/// # hCaptcha Configuration
#[derive(Serialize, JsonSchema, Debug)]
pub struct CaptchaFeature {
/// Whether captcha is enabled
pub enabled: bool,
/// Client key used for solving captcha
pub key: String,
}
/// # Generic Service Configuration
#[derive(Serialize, JsonSchema, Debug)]
pub struct Feature {
/// Whether the service is enabled
pub enabled: bool,
/// URL pointing to the service
pub url: String,
}
/// # Information about a livekit node
#[derive(Serialize, JsonSchema, Debug)]
pub struct VoiceNode {
pub name: String,
pub lat: f64,
pub lon: f64,
pub public_url: String,
}
/// # Voice Server Configuration
#[derive(Serialize, JsonSchema, Debug)]
pub struct VoiceFeature {
/// Whether voice is enabled
pub enabled: bool,
/// All livekit nodes
pub nodes: Vec<VoiceNode>,
}
/// # Feature Configuration
#[derive(Serialize, JsonSchema, Debug)]
pub struct RevoltFeatures {
/// hCaptcha configuration
pub captcha: CaptchaFeature,
/// Whether email verification is enabled
pub email: bool,
/// Whether this server is invite only
pub invite_only: bool,
/// File server service configuration
pub autumn: Feature,
/// Proxy service configuration
pub january: Feature,
/// Voice server configuration
pub livekit: VoiceFeature,
}
/// # Build Information
#[derive(Serialize, JsonSchema, Debug)]
pub struct BuildInformation {
/// Commit Hash
pub commit_sha: String,
/// Commit Timestamp
pub commit_timestamp: String,
/// Git Semver
pub semver: String,
/// Git Origin URL
pub origin_url: String,
/// Build Timestamp
pub timestamp: String,
}
/// # Server Configuration
#[derive(Serialize, JsonSchema, Debug)]
pub struct RevoltConfig {
/// Revolt API Version
pub revolt: String,
/// Features enabled on this Revolt node
pub features: RevoltFeatures,
/// WebSocket URL
pub ws: String,
/// URL pointing to the client serving this node
pub app: String,
/// Web Push VAPID public key
pub vapid: String,
/// Build information
pub build: BuildInformation,
}
/// # Query Node
///
/// Fetch the server configuration for this Revolt instance.
#[utoipa::path(
tag = "Core",
responses((status = 200, body = v0::RevoltConfig))
)]
#[openapi(tag = "Core")]
#[get("/")]
pub async fn root() -> Result<Json<v0::RevoltConfig>> {
pub async fn root() -> Result<Json<RevoltConfig>> {
let config = config().await;
Ok(Json(v0::RevoltConfig {
Ok(Json(RevoltConfig {
revolt: env!("CARGO_PKG_VERSION").to_string(),
features: v0::RevoltFeatures {
captcha: v0::CaptchaFeature {
features: RevoltFeatures {
captcha: CaptchaFeature {
enabled: !config.api.security.captcha.hcaptcha_key.is_empty(),
key: config.api.security.captcha.hcaptcha_sitekey.clone(),
},
email: !config.api.smtp.host.is_empty(),
invite_only: config.api.registration.invite_only,
autumn: v0::Feature {
autumn: Feature {
enabled: !config.hosts.autumn.is_empty(),
url: config.hosts.autumn.clone(),
},
january: v0::Feature {
january: Feature {
enabled: !config.hosts.january.is_empty(),
url: config.hosts.january.clone(),
},
livekit: v0::VoiceFeature {
livekit: VoiceFeature {
enabled: !config.hosts.livekit.is_empty(),
nodes: config
.api
@@ -39,7 +121,7 @@ pub async fn root() -> Result<Json<v0::RevoltConfig>> {
.nodes
.iter()
.filter(|(_, node)| !node.private)
.map(|(name, value)| v0::VoiceNode {
.map(|(name, value)| VoiceNode {
name: name.clone(),
lat: value.lat,
lon: value.lon,
@@ -56,7 +138,7 @@ pub async fn root() -> Result<Json<v0::RevoltConfig>> {
ws: config.hosts.events,
app: config.hosts.app,
vapid: config.pushd.vapid.public_key,
build: v0::BuildInformation {
build: BuildInformation {
commit_sha: option_env!("VERGEN_GIT_SHA")
.unwrap_or_else(|| "<failed to generate>")
.to_string(),
Loaded 100 of 168 files, more files were not shown because too many files have changed in this diff. Show more