chore(doc): comment channel routes

This commit is contained in:
Paul Makles
2022-03-19 14:14:24 +00:00
parent 14e17b10c0
commit 25a52cbbb1
22 changed files with 229 additions and 42 deletions

View File

@@ -1,5 +1,9 @@
use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result}; use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result};
/// # Acknowledge Message
///
/// Lets the server and all other clients know that we've seen this message id in this channel.
#[openapi(tag = "Messaging")]
#[put("/<target>/ack/<message>")] #[put("/<target>/ack/<message>")]
pub async fn req(db: &Db, user: User, target: Ref, message: Ref) -> Result<EmptyResponse> { pub async fn req(db: &Db, user: User, target: Ref, message: Ref) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;

View File

@@ -3,6 +3,10 @@ use revolt_quark::{
perms, Db, EmptyResponse, Error, Permission, Ref, Result, perms, Db, EmptyResponse, Error, Permission, Ref, Result,
}; };
/// # Close Channel
///
/// Deletes a server channel, leaves a group or closes a group.
#[openapi(tag = "Channel Information")]
#[delete("/<target>")] #[delete("/<target>")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> { pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
let mut channel = target.as_channel(db).await?; let mut channel = target.as_channel(db).await?;

View File

@@ -10,28 +10,39 @@ use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
#[derive(Validate, Serialize, Deserialize)] /// # Channel Details
pub struct Data { #[derive(Validate, Serialize, Deserialize, JsonSchema)]
pub struct DataEditChannel {
/// Channel name
#[validate(length(min = 1, max = 32))] #[validate(length(min = 1, max = 32))]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>, name: Option<String>,
/// Channel description
#[validate(length(min = 0, max = 1024))] #[validate(length(min = 0, max = 1024))]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>, description: Option<String>,
/// Icon
///
/// Provide an Autumn attachment Id.
#[validate(length(min = 1, max = 128))] #[validate(length(min = 1, max = 128))]
icon: Option<String>, icon: Option<String>,
/// Whether this channel is age-restricted
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
nsfw: Option<bool>, nsfw: Option<bool>,
#[validate(length(min = 1))] #[validate(length(min = 1))]
remove: Option<Vec<FieldsChannel>>, remove: Option<Vec<FieldsChannel>>,
} }
/// # Edit Channel
///
/// Edit a channel object by its id.
#[openapi(tag = "Channel Information")]
#[patch("/<target>", data = "<data>")] #[patch("/<target>", data = "<data>")]
pub async fn req( pub async fn req(
db: &State<Database>, db: &State<Database>,
user: User, user: User,
target: Ref, target: Ref,
data: Json<Data>, data: Json<DataEditChannel>,
) -> Result<Json<Channel>> { ) -> Result<Json<Channel>> {
let data = data.into_inner(); let data = data.into_inner();
data.validate() data.validate()

View File

@@ -5,6 +5,10 @@ use revolt_quark::{
use rocket::{serde::json::Json, State}; use rocket::{serde::json::Json, State};
/// # Fetch Channel
///
/// Fetch channel by its id.
#[openapi(tag = "Channel Information")]
#[get("/<target>")] #[get("/<target>")]
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<Channel>> { pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<Channel>> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;

View File

@@ -3,6 +3,10 @@ use revolt_quark::{
perms, Db, EmptyResponse, Error, Permission, Ref, Result, perms, Db, EmptyResponse, Error, Permission, Ref, Result,
}; };
/// # Add Member to Group
///
/// Adds another user to the group.
#[openapi(tag = "Groups")]
#[put("/<target>/recipients/<member>")] #[put("/<target>/recipients/<member>")]
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> { pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;

View File

@@ -13,19 +13,31 @@ use validator::Validate;
use crate::util::variables::MAX_GROUP_SIZE; use crate::util::variables::MAX_GROUP_SIZE;
#[derive(Validate, Serialize, Deserialize)] /// # Group Data
pub struct Data { #[derive(Validate, Serialize, Deserialize, JsonSchema)]
pub struct DataCreateGroup {
/// Group name
#[validate(length(min = 1, max = 32))] #[validate(length(min = 1, max = 32))]
name: String, name: String,
/// Group description
#[validate(length(min = 0, max = 1024))] #[validate(length(min = 0, max = 1024))]
description: Option<String>, description: Option<String>,
/// Array of user IDs to add to the group
///
/// Must be friends with these users.
#[validate(length(min = 0, max = 49))]
users: Vec<String>, users: Vec<String>,
/// Whether this group is age-restricted
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
nsfw: Option<bool>, nsfw: Option<bool>,
} }
/// # Create Group
///
/// Create a new group channel.
#[openapi(tag = "Groups")]
#[post("/create", data = "<info>")] #[post("/create", data = "<info>")]
pub async fn req(db: &Db, user: User, info: Json<Data>) -> Result<Json<Channel>> { pub async fn req(db: &Db, user: User, info: Json<DataCreateGroup>) -> Result<Json<Channel>> {
let info = info.into_inner(); let info = info.into_inner();
info.validate() info.validate()
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;

View File

@@ -3,6 +3,10 @@ use revolt_quark::{
Db, EmptyResponse, Error, Permission, Ref, Result, Db, EmptyResponse, Error, Permission, Ref, Result,
}; };
/// # Remove Member from Group
///
/// Removes a user from the group.
#[openapi(tag = "Groups")]
#[delete("/<target>/recipients/<member>")] #[delete("/<target>/recipients/<member>")]
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> { pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;

View File

@@ -5,6 +5,12 @@ use revolt_quark::{
use rocket::serde::json::Json; use rocket::serde::json::Json;
/// # Create Invite
///
/// Creates an invite to this channel.
///
/// Channel must be a `TextChannel`.
#[openapi(tag = "Channel Invites")]
#[post("/<target>/invites")] #[post("/<target>/invites")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Invite>> { pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Invite>> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;

View File

@@ -5,6 +5,10 @@ use revolt_quark::{
use rocket::serde::json::Json; use rocket::serde::json::Json;
/// # Fetch Group Members
///
/// Retrieves all users who are part of this group.
#[openapi(tag = "Groups")]
#[get("/<target>/members")] #[get("/<target>/members")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<User>>> { pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<User>>> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;

View File

@@ -1,5 +1,9 @@
use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Permission, Ref, Result}; use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Permission, Ref, Result};
/// # Delete Message
///
/// Delete a message you've sent or one you have permission to delete.
#[openapi(tag = "Messaging")]
#[delete("/<target>/messages/<msg>")] #[delete("/<target>/messages/<msg>")]
pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<EmptyResponse> { pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<EmptyResponse> {
let message = msg.as_message(db).await?; let message = msg.as_message(db).await?;

View File

@@ -11,21 +11,28 @@ use rocket::serde::json::Json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
#[derive(Validate, Serialize, Deserialize)] /// # Message Details
pub struct Data { #[derive(Validate, Serialize, Deserialize, JsonSchema)]
pub struct DataEditMessage {
/// New message content
#[validate(length(min = 1, max = 2000))] #[validate(length(min = 1, max = 2000))]
content: Option<String>, content: Option<String>,
/// Embeds to include in the message
#[validate(length(min = 0, max = 10))] #[validate(length(min = 0, max = 10))]
embeds: Option<Vec<SendableEmbed>>, embeds: Option<Vec<SendableEmbed>>,
} }
/// # Edit Message
///
/// Edits a message that you've previously sent.
#[openapi(tag = "Messaging")]
#[patch("/<target>/messages/<msg>", data = "<edit>")] #[patch("/<target>/messages/<msg>", data = "<edit>")]
pub async fn req( pub async fn req(
db: &Db, db: &Db,
user: User, user: User,
target: String, target: String,
msg: Ref, msg: Ref,
edit: Json<Data>, edit: Json<DataEditMessage>,
) -> Result<Json<Message>> { ) -> Result<Json<Message>> {
let edit = edit.into_inner(); let edit = edit.into_inner();
edit.validate() edit.validate()

View File

@@ -5,6 +5,10 @@ use revolt_quark::{
use rocket::serde::json::Json; use rocket::serde::json::Json;
/// # Fetch Message
///
/// Retrieves a message by its id.
#[openapi(tag = "Messaging")]
#[get("/<target>/messages/<msg>")] #[get("/<target>/messages/<msg>")]
pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<Json<Message>> { pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<Json<Message>> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;

View File

@@ -9,29 +9,43 @@ use rocket::serde::json::Json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
#[derive(Validate, Serialize, Deserialize, FromForm)] /// # Query Parameters
pub struct Options { #[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
pub struct OptionsQueryMessages {
/// Maximum number of messages to fetch
///
/// For fetching nearby messages, this is \`(limit + 1)\`.
#[validate(range(min = 1, max = 100))] #[validate(range(min = 1, max = 100))]
limit: Option<i64>, limit: Option<i64>,
/// Message id before which messages should be fetched
#[validate(length(min = 26, max = 26))] #[validate(length(min = 26, max = 26))]
before: Option<String>, before: Option<String>,
/// Message id after which messages should be fetched
#[validate(length(min = 26, max = 26))] #[validate(length(min = 26, max = 26))]
after: Option<String>, after: Option<String>,
/// Message sort direction
sort: Option<MessageSort>, sort: Option<MessageSort>,
// Specifying 'nearby' ignores 'before', 'after' and 'sort'. /// Message id to search around
// It will also take half of limit rounded as the limits to each side. ///
// It also fetches the message ID specified. /// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
/// It will also take half of limit rounded as the limits to each side.
/// It also fetches the message ID specified.
#[validate(length(min = 26, max = 26))] #[validate(length(min = 26, max = 26))]
nearby: Option<String>, nearby: Option<String>,
/// Whether to include user (and member, if server channel) objects
include_users: Option<bool>, include_users: Option<bool>,
} }
/// # Fetch Messages
///
/// Fetch multiple messages.
#[openapi(tag = "Messaging")]
#[get("/<target>/messages?<options..>")] #[get("/<target>/messages?<options..>")]
pub async fn req( pub async fn req(
db: &Db, db: &Db,
user: User, user: User,
target: Ref, target: Ref,
options: Options, options: OptionsQueryMessages,
) -> Result<Json<BulkMessageResponse>> { ) -> Result<Json<BulkMessageResponse>> {
options options
.validate() .validate()
@@ -47,7 +61,7 @@ pub async fn req(
.throw_permission_and_view_channel(db, Permission::ReadMessageHistory) .throw_permission_and_view_channel(db, Permission::ReadMessageHistory)
.await?; .await?;
let Options { let OptionsQueryMessages {
limit, limit,
before, before,
after, after,

View File

@@ -1,14 +1,26 @@
use revolt_quark::{models::User, Ref, Result}; use revolt_quark::{models::User, Ref, Result};
use rocket::serde::json::{Json, Value}; use rocket::serde::json::Json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Serialize, Deserialize)] /// # Query Parameters
pub struct Options { #[derive(Validate, Serialize, Deserialize, JsonSchema)]
pub struct OptionsQueryStale {
/// Array of message IDs
#[validate(length(min = 0, max = 150))]
ids: Vec<String>, ids: Vec<String>,
} }
/// # Poll Message Changes
///
/// This route returns any changed message objects and tells you if any have been deleted.
///
/// Don't actually poll this route, instead use this to update your local database.
///
/// **DEPRECATED**
#[openapi(tag = "Messaging")]
#[post("/<_target>/messages/stale", data = "<_data>")] #[post("/<_target>/messages/stale", data = "<_data>")]
pub async fn req(_user: User, _target: Ref, _data: Json<Options>) -> Result<Value> { pub async fn req(_user: User, _target: Ref, _data: Json<OptionsQueryStale>) -> Result<()> {
unimplemented!() Ok(())
} }

View File

@@ -10,28 +10,43 @@ use rocket::serde::json::Json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
#[derive(Validate, Serialize, Deserialize, FromForm)] /// # Search Parameters
pub struct Options { #[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
pub struct OptionsMessageSearch {
/// Full-text search query
///
/// See [MongoDB documentation](https://docs.mongodb.com/manual/text-search/#-text-operator) for more information.
#[validate(length(min = 1, max = 64))] #[validate(length(min = 1, max = 64))]
query: String, query: String,
/// Maximum number of messages to fetch
#[validate(range(min = 1, max = 100))] #[validate(range(min = 1, max = 100))]
limit: Option<i64>, limit: Option<i64>,
/// Message id before which messages should be fetched
#[validate(length(min = 26, max = 26))] #[validate(length(min = 26, max = 26))]
before: Option<String>, before: Option<String>,
/// Message id after which messages should be fetched
#[validate(length(min = 26, max = 26))] #[validate(length(min = 26, max = 26))]
after: Option<String>, after: Option<String>,
/// Message sort direction
///
/// By default, it will be sorted by relevance.
#[serde(default = "MessageSort::default")] #[serde(default = "MessageSort::default")]
sort: MessageSort, sort: MessageSort,
/// Whether to include user (and member, if server channel) objects
include_users: Option<bool>, include_users: Option<bool>,
} }
/// # Search for Messages
///
/// This route searches for messages within the given parameters.
#[openapi(tag = "Messaging")]
#[post("/<target>/search", data = "<options>")] #[post("/<target>/search", data = "<options>")]
pub async fn req( pub async fn req(
db: &Db, db: &Db,
user: User, user: User,
target: Ref, target: Ref,
options: Json<Options>, options: Json<OptionsMessageSearch>,
) -> Result<Json<BulkMessageResponse>> { ) -> Result<Json<BulkMessageResponse>> {
let options = options.into_inner(); let options = options.into_inner();
options options
@@ -44,7 +59,7 @@ pub async fn req(
.throw_permission_and_view_channel(db, Permission::ReadMessageHistory) .throw_permission_and_view_channel(db, Permission::ReadMessageHistory)
.await?; .await?;
let Options { let OptionsMessageSearch {
query, query,
limit, limit,
before, before,

View File

@@ -16,19 +16,27 @@ use validator::Validate;
use crate::util::idempotency::IdempotencyKey; use crate::util::idempotency::IdempotencyKey;
#[derive(Validate, Serialize, Deserialize)] #[derive(Validate, Serialize, Deserialize, JsonSchema)]
pub struct Data { pub struct DataMessageSend {
/// Unique token to prevent duplicate message sending
///
/// **This is deprecated and replaced by Idempotency-Token!**
nonce: Option<String>, nonce: Option<String>,
/// Message content to send
#[validate(length(min = 0, max = 2000))] #[validate(length(min = 0, max = 2000))]
content: String, content: String,
/// Attachments to include in message
#[validate(length(min = 1, max = 128))] #[validate(length(min = 1, max = 128))]
attachments: Option<Vec<String>>, attachments: Option<Vec<String>>,
/// Messages to reply to
replies: Option<Vec<Reply>>, replies: Option<Vec<Reply>>,
#[validate] /// Embeds to include in message
masquerade: Option<Masquerade>,
#[validate(length(min = 1, max = 10))] #[validate(length(min = 1, max = 10))]
embeds: Option<Vec<SendableEmbed>>, embeds: Option<Vec<SendableEmbed>>,
/// Masquerade to apply to this message
#[validate]
masquerade: Option<Masquerade>,
} }
lazy_static! { lazy_static! {
@@ -36,12 +44,16 @@ lazy_static! {
static ref RE_MENTION: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap(); static ref RE_MENTION: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
} }
/// # Send Message
///
/// Sends a message to the given channel.
#[openapi(tag = "Messaging")]
#[post("/<target>/messages", data = "<data>")] #[post("/<target>/messages", data = "<data>")]
pub async fn message_send( pub async fn message_send(
db: &Db, db: &Db,
user: User, user: User,
target: Ref, target: Ref,
data: Json<Data>, data: Json<DataMessageSend>,
mut idempotency: IdempotencyKey, mut idempotency: IdempotencyKey,
) -> Result<Json<Message>> { ) -> Result<Json<Message>> {
let data = data.into_inner(); let data = data.into_inner();

View File

@@ -1,4 +1,5 @@
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod channel_ack; mod channel_ack;
mod channel_delete; mod channel_delete;
@@ -20,8 +21,8 @@ mod permissions_set;
mod permissions_set_default; mod permissions_set_default;
mod voice_join; mod voice_join;
pub fn routes() -> Vec<Route> { pub fn routes() -> (Vec<Route>, OpenApi) {
routes![ openapi_get_routes_spec![
channel_ack::req, channel_ack::req,
channel_fetch::req, channel_fetch::req,
members_fetch::req, members_fetch::req,

View File

@@ -6,11 +6,19 @@ use revolt_quark::{
perms, Db, Error, Override, Permission, Ref, Result, perms, Db, Error, Override, Permission, Ref, Result,
}; };
#[derive(Deserialize)] /// # Permission Value
#[derive(Deserialize, JsonSchema)]
pub struct Data { pub struct Data {
/// Allow / deny values to set for this role
permissions: Override, permissions: Override,
} }
/// # Set Role Permission
///
/// Sets permissions for the specified role in this channel.
///
/// Channel must be a `TextChannel` or `VoiceChannel`.
#[openapi(tag = "Channel Permissions")]
#[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)] #[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
pub async fn req( pub async fn req(
db: &Db, db: &Db,

View File

@@ -6,15 +6,33 @@ use revolt_quark::{
perms, Db, Error, Override, Permission, Ref, Result, perms, Db, Error, Override, Permission, Ref, Result,
}; };
#[derive(Deserialize)] /// # Permission Value
#[derive(Deserialize, JsonSchema)]
#[serde(untagged)] #[serde(untagged)]
pub enum Data { pub enum DataDefaultChannelPermissions {
Value { permissions: u64 }, Value {
Field { permissions: Override }, /// Permission values to set for members in a `Group`
permissions: u64,
},
Field {
/// Allow / deny values to set for members in this `TextChannel` or `VoiceChannel`
permissions: Override,
},
} }
/// # Set Default Permission
///
/// Sets permissions for the default role in this channel.
///
/// Channel must be a `Group`, `TextChannel` or `VoiceChannel`.
#[openapi(tag = "Channel Permissions")]
#[put("/<target>/permissions/default", data = "<data>", rank = 1)] #[put("/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn req(db: &Db, user: User, target: Ref, data: Json<Data>) -> Result<Json<Channel>> { pub async fn req(
db: &Db,
user: User,
target: Ref,
data: Json<DataDefaultChannelPermissions>,
) -> Result<Json<Channel>> {
let data = data.into_inner(); let data = data.into_inner();
let mut channel = target.as_channel(db).await?; let mut channel = target.as_channel(db).await?;
@@ -25,7 +43,7 @@ pub async fn req(db: &Db, user: User, target: Ref, data: Json<Data>) -> Result<J
match &channel { match &channel {
Channel::Group { .. } => { Channel::Group { .. } => {
if let Data::Value { permissions } = data { if let DataDefaultChannelPermissions::Value { permissions } = data {
channel channel
.update( .update(
db, db,
@@ -48,7 +66,7 @@ pub async fn req(db: &Db, user: User, target: Ref, data: Json<Data>) -> Result<J
default_permissions, default_permissions,
.. ..
} => { } => {
if let Data::Field { permissions } = data { if let DataDefaultChannelPermissions::Field { permissions } = data {
perm.throw_permission_override( perm.throw_permission_override(
db, db,
default_permissions.map(|x| x.into()), default_permissions.map(|x| x.into()),

View File

@@ -3,11 +3,17 @@ use revolt_quark::{models::User, Ref, Result};
use rocket::serde::json::Value; use rocket::serde::json::Value;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// # Token Response
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct CreateUserResponse { struct CreateVoiceUserResponse {
/// Token for authenticating with the voice server
token: String, token: String,
} }
/// # Join Call
///
/// Asks the voice server for a token to join the call.
#[openapi(tag = "Voice")]
#[post("/<_target>/join_call")] #[post("/<_target>/join_call")]
pub async fn req(_user: User, _target: Ref) -> Result<Value> { pub async fn req(_user: User, _target: Ref) -> Result<Value> {
unimplemented!() unimplemented!()

View File

@@ -20,6 +20,7 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
rocket, "/".to_owned(), settings, rocket, "/".to_owned(), settings,
"" => openapi_get_routes_spec![root::root, root::ping], "" => openapi_get_routes_spec![root::root, root::ping],
"/bots" => bots::routes(), "/bots" => bots::routes(),
"/channels" => channels::routes(),
}; };
rocket rocket
@@ -27,7 +28,6 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
.mount("/auth/session", rauth::web::session::routes()) .mount("/auth/session", rauth::web::session::routes())
.mount("/onboard", onboard::routes()) .mount("/onboard", onboard::routes())
.mount("/users", users::routes()) .mount("/users", users::routes())
.mount("/channels", channels::routes())
.mount("/servers", servers::routes()) .mount("/servers", servers::routes())
.mount("/invites", invites::routes()) .mount("/invites", invites::routes())
.mount("/push", push::routes()) .mount("/push", push::routes())

View File

@@ -3,6 +3,10 @@ use mongodb::bson::doc;
use revolt_quark::{Error, Result}; use revolt_quark::{Error, Result};
use rocket::http::Status; use rocket::http::Status;
use rocket::request::{FromRequest, Outcome}; use rocket::request::{FromRequest, Outcome};
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::okapi::openapi3::{Parameter, ParameterValue};
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
@@ -38,6 +42,35 @@ impl IdempotencyKey {
} }
} }
impl<'r> OpenApiFromRequest<'r> for IdempotencyKey {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> 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: "sus".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()
},
},
}))
}
}
#[async_trait] #[async_trait]
impl<'r> FromRequest<'r> for IdempotencyKey { impl<'r> FromRequest<'r> for IdempotencyKey {
type Error = Error; type Error = Error;