From 25a52cbbb140cde09b8b4ac87fbbe6048220a4b7 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 19 Mar 2022 14:14:24 +0000 Subject: [PATCH] chore(doc): comment channel routes --- src/routes/channels/channel_ack.rs | 4 +++ src/routes/channels/channel_delete.rs | 4 +++ src/routes/channels/channel_edit.rs | 17 ++++++++-- src/routes/channels/channel_fetch.rs | 4 +++ src/routes/channels/group_add_member.rs | 4 +++ src/routes/channels/group_create.rs | 18 ++++++++-- src/routes/channels/group_remove_member.rs | 4 +++ src/routes/channels/invite_create.rs | 6 ++++ src/routes/channels/members_fetch.rs | 4 +++ src/routes/channels/message_delete.rs | 4 +++ src/routes/channels/message_edit.rs | 13 ++++++-- src/routes/channels/message_fetch.rs | 4 +++ src/routes/channels/message_query.rs | 28 ++++++++++++---- src/routes/channels/message_query_stale.rs | 22 ++++++++++--- src/routes/channels/message_search.rs | 23 ++++++++++--- src/routes/channels/message_send.rs | 22 ++++++++++--- src/routes/channels/mod.rs | 5 +-- src/routes/channels/permissions_set.rs | 10 +++++- .../channels/permissions_set_default.rs | 32 ++++++++++++++---- src/routes/channels/voice_join.rs | 8 ++++- src/routes/mod.rs | 2 +- src/util/idempotency.rs | 33 +++++++++++++++++++ 22 files changed, 229 insertions(+), 42 deletions(-) diff --git a/src/routes/channels/channel_ack.rs b/src/routes/channels/channel_ack.rs index 7ecbf5d2..e3ae2510 100644 --- a/src/routes/channels/channel_ack.rs +++ b/src/routes/channels/channel_ack.rs @@ -1,5 +1,9 @@ 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("//ack/")] pub async fn req(db: &Db, user: User, target: Ref, message: Ref) -> Result { let channel = target.as_channel(db).await?; diff --git a/src/routes/channels/channel_delete.rs b/src/routes/channels/channel_delete.rs index 9ba247a7..1777c119 100644 --- a/src/routes/channels/channel_delete.rs +++ b/src/routes/channels/channel_delete.rs @@ -3,6 +3,10 @@ use revolt_quark::{ 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("/")] pub async fn req(db: &Db, user: User, target: Ref) -> Result { let mut channel = target.as_channel(db).await?; diff --git a/src/routes/channels/channel_edit.rs b/src/routes/channels/channel_edit.rs index a948de2b..d966ffff 100644 --- a/src/routes/channels/channel_edit.rs +++ b/src/routes/channels/channel_edit.rs @@ -10,28 +10,39 @@ use rocket::{serde::json::Json, State}; use serde::{Deserialize, Serialize}; use validator::Validate; -#[derive(Validate, Serialize, Deserialize)] -pub struct Data { +/// # Channel Details +#[derive(Validate, Serialize, Deserialize, JsonSchema)] +pub struct DataEditChannel { + /// Channel name #[validate(length(min = 1, max = 32))] #[serde(skip_serializing_if = "Option::is_none")] name: Option, + /// Channel description #[validate(length(min = 0, max = 1024))] #[serde(skip_serializing_if = "Option::is_none")] description: Option, + /// Icon + /// + /// Provide an Autumn attachment Id. #[validate(length(min = 1, max = 128))] icon: Option, + /// Whether this channel is age-restricted #[serde(skip_serializing_if = "Option::is_none")] nsfw: Option, #[validate(length(min = 1))] remove: Option>, } +/// # Edit Channel +/// +/// Edit a channel object by its id. +#[openapi(tag = "Channel Information")] #[patch("/", data = "")] pub async fn req( db: &State, user: User, target: Ref, - data: Json, + data: Json, ) -> Result> { let data = data.into_inner(); data.validate() diff --git a/src/routes/channels/channel_fetch.rs b/src/routes/channels/channel_fetch.rs index d6fd57d7..da762a87 100644 --- a/src/routes/channels/channel_fetch.rs +++ b/src/routes/channels/channel_fetch.rs @@ -5,6 +5,10 @@ use revolt_quark::{ use rocket::{serde::json::Json, State}; +/// # Fetch Channel +/// +/// Fetch channel by its id. +#[openapi(tag = "Channel Information")] #[get("/")] pub async fn req(db: &State, user: User, target: Ref) -> Result> { let channel = target.as_channel(db).await?; diff --git a/src/routes/channels/group_add_member.rs b/src/routes/channels/group_add_member.rs index 00ed5fb4..155cbeec 100644 --- a/src/routes/channels/group_add_member.rs +++ b/src/routes/channels/group_add_member.rs @@ -3,6 +3,10 @@ use revolt_quark::{ perms, Db, EmptyResponse, Error, Permission, Ref, Result, }; +/// # Add Member to Group +/// +/// Adds another user to the group. +#[openapi(tag = "Groups")] #[put("//recipients/")] pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result { let channel = target.as_channel(db).await?; diff --git a/src/routes/channels/group_create.rs b/src/routes/channels/group_create.rs index 14d4e61f..2f602ba5 100644 --- a/src/routes/channels/group_create.rs +++ b/src/routes/channels/group_create.rs @@ -13,19 +13,31 @@ use validator::Validate; use crate::util::variables::MAX_GROUP_SIZE; -#[derive(Validate, Serialize, Deserialize)] -pub struct Data { +/// # Group Data +#[derive(Validate, Serialize, Deserialize, JsonSchema)] +pub struct DataCreateGroup { + /// Group name #[validate(length(min = 1, max = 32))] name: String, + /// Group description #[validate(length(min = 0, max = 1024))] description: Option, + /// Array of user IDs to add to the group + /// + /// Must be friends with these users. + #[validate(length(min = 0, max = 49))] users: Vec, + /// Whether this group is age-restricted #[serde(skip_serializing_if = "Option::is_none")] nsfw: Option, } +/// # Create Group +/// +/// Create a new group channel. +#[openapi(tag = "Groups")] #[post("/create", data = "")] -pub async fn req(db: &Db, user: User, info: Json) -> Result> { +pub async fn req(db: &Db, user: User, info: Json) -> Result> { let info = info.into_inner(); info.validate() .map_err(|error| Error::FailedValidation { error })?; diff --git a/src/routes/channels/group_remove_member.rs b/src/routes/channels/group_remove_member.rs index 3b0c7aeb..2abbed80 100644 --- a/src/routes/channels/group_remove_member.rs +++ b/src/routes/channels/group_remove_member.rs @@ -3,6 +3,10 @@ use revolt_quark::{ Db, EmptyResponse, Error, Permission, Ref, Result, }; +/// # Remove Member from Group +/// +/// Removes a user from the group. +#[openapi(tag = "Groups")] #[delete("//recipients/")] pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result { let channel = target.as_channel(db).await?; diff --git a/src/routes/channels/invite_create.rs b/src/routes/channels/invite_create.rs index 51bd97b8..c27bf4de 100644 --- a/src/routes/channels/invite_create.rs +++ b/src/routes/channels/invite_create.rs @@ -5,6 +5,12 @@ use revolt_quark::{ use rocket::serde::json::Json; +/// # Create Invite +/// +/// Creates an invite to this channel. +/// +/// Channel must be a `TextChannel`. +#[openapi(tag = "Channel Invites")] #[post("//invites")] pub async fn req(db: &Db, user: User, target: Ref) -> Result> { let channel = target.as_channel(db).await?; diff --git a/src/routes/channels/members_fetch.rs b/src/routes/channels/members_fetch.rs index e8259899..51510bd2 100644 --- a/src/routes/channels/members_fetch.rs +++ b/src/routes/channels/members_fetch.rs @@ -5,6 +5,10 @@ use revolt_quark::{ use rocket::serde::json::Json; +/// # Fetch Group Members +/// +/// Retrieves all users who are part of this group. +#[openapi(tag = "Groups")] #[get("//members")] pub async fn req(db: &Db, user: User, target: Ref) -> Result>> { let channel = target.as_channel(db).await?; diff --git a/src/routes/channels/message_delete.rs b/src/routes/channels/message_delete.rs index c77bdc02..98aae099 100644 --- a/src/routes/channels/message_delete.rs +++ b/src/routes/channels/message_delete.rs @@ -1,5 +1,9 @@ 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("//messages/")] pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result { let message = msg.as_message(db).await?; diff --git a/src/routes/channels/message_edit.rs b/src/routes/channels/message_edit.rs index cae04db3..0e353f0c 100644 --- a/src/routes/channels/message_edit.rs +++ b/src/routes/channels/message_edit.rs @@ -11,21 +11,28 @@ use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; use validator::Validate; -#[derive(Validate, Serialize, Deserialize)] -pub struct Data { +/// # Message Details +#[derive(Validate, Serialize, Deserialize, JsonSchema)] +pub struct DataEditMessage { + /// New message content #[validate(length(min = 1, max = 2000))] content: Option, + /// Embeds to include in the message #[validate(length(min = 0, max = 10))] embeds: Option>, } +/// # Edit Message +/// +/// Edits a message that you've previously sent. +#[openapi(tag = "Messaging")] #[patch("//messages/", data = "")] pub async fn req( db: &Db, user: User, target: String, msg: Ref, - edit: Json, + edit: Json, ) -> Result> { let edit = edit.into_inner(); edit.validate() diff --git a/src/routes/channels/message_fetch.rs b/src/routes/channels/message_fetch.rs index 434f85db..3fcba734 100644 --- a/src/routes/channels/message_fetch.rs +++ b/src/routes/channels/message_fetch.rs @@ -5,6 +5,10 @@ use revolt_quark::{ use rocket::serde::json::Json; +/// # Fetch Message +/// +/// Retrieves a message by its id. +#[openapi(tag = "Messaging")] #[get("//messages/")] pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result> { let channel = target.as_channel(db).await?; diff --git a/src/routes/channels/message_query.rs b/src/routes/channels/message_query.rs index b591d965..a6dfe267 100644 --- a/src/routes/channels/message_query.rs +++ b/src/routes/channels/message_query.rs @@ -9,29 +9,43 @@ use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; use validator::Validate; -#[derive(Validate, Serialize, Deserialize, FromForm)] -pub struct Options { +/// # Query Parameters +#[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))] limit: Option, + /// Message id before which messages should be fetched #[validate(length(min = 26, max = 26))] before: Option, + /// Message id after which messages should be fetched #[validate(length(min = 26, max = 26))] after: Option, + /// Message sort direction sort: Option, - // 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. + /// Message id to search around + /// + /// 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))] nearby: Option, + /// Whether to include user (and member, if server channel) objects include_users: Option, } +/// # Fetch Messages +/// +/// Fetch multiple messages. +#[openapi(tag = "Messaging")] #[get("//messages?")] pub async fn req( db: &Db, user: User, target: Ref, - options: Options, + options: OptionsQueryMessages, ) -> Result> { options .validate() @@ -47,7 +61,7 @@ pub async fn req( .throw_permission_and_view_channel(db, Permission::ReadMessageHistory) .await?; - let Options { + let OptionsQueryMessages { limit, before, after, diff --git a/src/routes/channels/message_query_stale.rs b/src/routes/channels/message_query_stale.rs index 38aaca96..015b6ad3 100644 --- a/src/routes/channels/message_query_stale.rs +++ b/src/routes/channels/message_query_stale.rs @@ -1,14 +1,26 @@ use revolt_quark::{models::User, Ref, Result}; -use rocket::serde::json::{Json, Value}; +use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; +use validator::Validate; -#[derive(Serialize, Deserialize)] -pub struct Options { +/// # Query Parameters +#[derive(Validate, Serialize, Deserialize, JsonSchema)] +pub struct OptionsQueryStale { + /// Array of message IDs + #[validate(length(min = 0, max = 150))] ids: Vec, } +/// # 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>")] -pub async fn req(_user: User, _target: Ref, _data: Json) -> Result { - unimplemented!() +pub async fn req(_user: User, _target: Ref, _data: Json) -> Result<()> { + Ok(()) } diff --git a/src/routes/channels/message_search.rs b/src/routes/channels/message_search.rs index bb0d25c3..81c1d023 100644 --- a/src/routes/channels/message_search.rs +++ b/src/routes/channels/message_search.rs @@ -10,28 +10,43 @@ use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; use validator::Validate; -#[derive(Validate, Serialize, Deserialize, FromForm)] -pub struct Options { +/// # Search Parameters +#[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))] query: String, + /// Maximum number of messages to fetch #[validate(range(min = 1, max = 100))] limit: Option, + /// Message id before which messages should be fetched #[validate(length(min = 26, max = 26))] before: Option, + /// Message id after which messages should be fetched #[validate(length(min = 26, max = 26))] after: Option, + /// Message sort direction + /// + /// By default, it will be sorted by relevance. #[serde(default = "MessageSort::default")] sort: MessageSort, + /// Whether to include user (and member, if server channel) objects include_users: Option, } +/// # Search for Messages +/// +/// This route searches for messages within the given parameters. +#[openapi(tag = "Messaging")] #[post("//search", data = "")] pub async fn req( db: &Db, user: User, target: Ref, - options: Json, + options: Json, ) -> Result> { let options = options.into_inner(); options @@ -44,7 +59,7 @@ pub async fn req( .throw_permission_and_view_channel(db, Permission::ReadMessageHistory) .await?; - let Options { + let OptionsMessageSearch { query, limit, before, diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs index 7473ba3f..44e8ebae 100644 --- a/src/routes/channels/message_send.rs +++ b/src/routes/channels/message_send.rs @@ -16,19 +16,27 @@ use validator::Validate; use crate::util::idempotency::IdempotencyKey; -#[derive(Validate, Serialize, Deserialize)] -pub struct Data { +#[derive(Validate, Serialize, Deserialize, JsonSchema)] +pub struct DataMessageSend { + /// Unique token to prevent duplicate message sending + /// + /// **This is deprecated and replaced by Idempotency-Token!** nonce: Option, + /// Message content to send #[validate(length(min = 0, max = 2000))] content: String, + /// Attachments to include in message #[validate(length(min = 1, max = 128))] attachments: Option>, + /// Messages to reply to replies: Option>, - #[validate] - masquerade: Option, + /// Embeds to include in message #[validate(length(min = 1, max = 10))] embeds: Option>, + /// Masquerade to apply to this message + #[validate] + masquerade: Option, } lazy_static! { @@ -36,12 +44,16 @@ lazy_static! { 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("//messages", data = "")] pub async fn message_send( db: &Db, user: User, target: Ref, - data: Json, + data: Json, mut idempotency: IdempotencyKey, ) -> Result> { let data = data.into_inner(); diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs index 62dffe3f..95f8ec45 100644 --- a/src/routes/channels/mod.rs +++ b/src/routes/channels/mod.rs @@ -1,4 +1,5 @@ use rocket::Route; +use rocket_okapi::okapi::openapi3::OpenApi; mod channel_ack; mod channel_delete; @@ -20,8 +21,8 @@ mod permissions_set; mod permissions_set_default; mod voice_join; -pub fn routes() -> Vec { - routes![ +pub fn routes() -> (Vec, OpenApi) { + openapi_get_routes_spec![ channel_ack::req, channel_fetch::req, members_fetch::req, diff --git a/src/routes/channels/permissions_set.rs b/src/routes/channels/permissions_set.rs index 45721e43..7705fbce 100644 --- a/src/routes/channels/permissions_set.rs +++ b/src/routes/channels/permissions_set.rs @@ -6,11 +6,19 @@ use revolt_quark::{ perms, Db, Error, Override, Permission, Ref, Result, }; -#[derive(Deserialize)] +/// # Permission Value +#[derive(Deserialize, JsonSchema)] pub struct Data { + /// Allow / deny values to set for this role 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("//permissions/", data = "", rank = 2)] pub async fn req( db: &Db, diff --git a/src/routes/channels/permissions_set_default.rs b/src/routes/channels/permissions_set_default.rs index d80c8f5d..befccff4 100644 --- a/src/routes/channels/permissions_set_default.rs +++ b/src/routes/channels/permissions_set_default.rs @@ -6,15 +6,33 @@ use revolt_quark::{ perms, Db, Error, Override, Permission, Ref, Result, }; -#[derive(Deserialize)] +/// # Permission Value +#[derive(Deserialize, JsonSchema)] #[serde(untagged)] -pub enum Data { - Value { permissions: u64 }, - Field { permissions: Override }, +pub enum DataDefaultChannelPermissions { + Value { + /// 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("//permissions/default", data = "", rank = 1)] -pub async fn req(db: &Db, user: User, target: Ref, data: Json) -> Result> { +pub async fn req( + db: &Db, + user: User, + target: Ref, + data: Json, +) -> Result> { let data = data.into_inner(); let mut channel = target.as_channel(db).await?; @@ -25,7 +43,7 @@ pub async fn req(db: &Db, user: User, target: Ref, data: Json) -> Result { - if let Data::Value { permissions } = data { + if let DataDefaultChannelPermissions::Value { permissions } = data { channel .update( db, @@ -48,7 +66,7 @@ pub async fn req(db: &Db, user: User, target: Ref, data: Json) -> Result { - if let Data::Field { permissions } = data { + if let DataDefaultChannelPermissions::Field { permissions } = data { perm.throw_permission_override( db, default_permissions.map(|x| x.into()), diff --git a/src/routes/channels/voice_join.rs b/src/routes/channels/voice_join.rs index 3b278052..ad502733 100644 --- a/src/routes/channels/voice_join.rs +++ b/src/routes/channels/voice_join.rs @@ -3,11 +3,17 @@ use revolt_quark::{models::User, Ref, Result}; use rocket::serde::json::Value; use serde::{Deserialize, Serialize}; +/// # Token Response #[derive(Serialize, Deserialize)] -struct CreateUserResponse { +struct CreateVoiceUserResponse { + /// Token for authenticating with the voice server token: String, } +/// # Join Call +/// +/// Asks the voice server for a token to join the call. +#[openapi(tag = "Voice")] #[post("/<_target>/join_call")] pub async fn req(_user: User, _target: Ref) -> Result { unimplemented!() diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 75b22556..28733849 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -20,6 +20,7 @@ pub fn mount(mut rocket: Rocket) -> Rocket { rocket, "/".to_owned(), settings, "" => openapi_get_routes_spec![root::root, root::ping], "/bots" => bots::routes(), + "/channels" => channels::routes(), }; rocket @@ -27,7 +28,6 @@ pub fn mount(mut rocket: Rocket) -> Rocket { .mount("/auth/session", rauth::web::session::routes()) .mount("/onboard", onboard::routes()) .mount("/users", users::routes()) - .mount("/channels", channels::routes()) .mount("/servers", servers::routes()) .mount("/invites", invites::routes()) .mount("/push", push::routes()) diff --git a/src/util/idempotency.rs b/src/util/idempotency.rs index 6f19444b..a35b3a74 100644 --- a/src/util/idempotency.rs +++ b/src/util/idempotency.rs @@ -3,6 +3,10 @@ use mongodb::bson::doc; use revolt_quark::{Error, Result}; use rocket::http::Status; 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 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 { + 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] impl<'r> FromRequest<'r> for IdempotencyKey { type Error = Error;