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};
/// # 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>")]
pub async fn req(db: &Db, user: User, target: Ref, message: Ref) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?;

View File

@@ -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("/<target>")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
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 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<String>,
/// Channel description
#[validate(length(min = 0, max = 1024))]
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
/// Icon
///
/// Provide an Autumn attachment Id.
#[validate(length(min = 1, max = 128))]
icon: Option<String>,
/// Whether this channel is age-restricted
#[serde(skip_serializing_if = "Option::is_none")]
nsfw: Option<bool>,
#[validate(length(min = 1))]
remove: Option<Vec<FieldsChannel>>,
}
/// # Edit Channel
///
/// Edit a channel object by its id.
#[openapi(tag = "Channel Information")]
#[patch("/<target>", data = "<data>")]
pub async fn req(
db: &State<Database>,
user: User,
target: Ref,
data: Json<Data>,
data: Json<DataEditChannel>,
) -> Result<Json<Channel>> {
let data = data.into_inner();
data.validate()

View File

@@ -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("/<target>")]
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<Channel>> {
let channel = target.as_channel(db).await?;

View File

@@ -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("/<target>/recipients/<member>")]
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?;

View File

@@ -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<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>,
/// Whether this group is age-restricted
#[serde(skip_serializing_if = "Option::is_none")]
nsfw: Option<bool>,
}
/// # Create Group
///
/// Create a new group channel.
#[openapi(tag = "Groups")]
#[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();
info.validate()
.map_err(|error| Error::FailedValidation { error })?;

View File

@@ -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("/<target>/recipients/<member>")]
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?;

View File

@@ -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("/<target>/invites")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Invite>> {
let channel = target.as_channel(db).await?;

View File

@@ -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("/<target>/members")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<User>>> {
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};
/// # Delete Message
///
/// Delete a message you've sent or one you have permission to delete.
#[openapi(tag = "Messaging")]
#[delete("/<target>/messages/<msg>")]
pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<EmptyResponse> {
let message = msg.as_message(db).await?;

View File

@@ -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<String>,
/// Embeds to include in the message
#[validate(length(min = 0, max = 10))]
embeds: Option<Vec<SendableEmbed>>,
}
/// # Edit Message
///
/// Edits a message that you've previously sent.
#[openapi(tag = "Messaging")]
#[patch("/<target>/messages/<msg>", data = "<edit>")]
pub async fn req(
db: &Db,
user: User,
target: String,
msg: Ref,
edit: Json<Data>,
edit: Json<DataEditMessage>,
) -> Result<Json<Message>> {
let edit = edit.into_inner();
edit.validate()

View File

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

View File

@@ -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<i64>,
/// Message id before which messages should be fetched
#[validate(length(min = 26, max = 26))]
before: Option<String>,
/// Message id after which messages should be fetched
#[validate(length(min = 26, max = 26))]
after: Option<String>,
/// Message sort direction
sort: Option<MessageSort>,
// 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<String>,
/// Whether to include user (and member, if server channel) objects
include_users: Option<bool>,
}
/// # Fetch Messages
///
/// Fetch multiple messages.
#[openapi(tag = "Messaging")]
#[get("/<target>/messages?<options..>")]
pub async fn req(
db: &Db,
user: User,
target: Ref,
options: Options,
options: OptionsQueryMessages,
) -> Result<Json<BulkMessageResponse>> {
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,

View File

@@ -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<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>")]
pub async fn req(_user: User, _target: Ref, _data: Json<Options>) -> Result<Value> {
unimplemented!()
pub async fn req(_user: User, _target: Ref, _data: Json<OptionsQueryStale>) -> Result<()> {
Ok(())
}

View File

@@ -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<i64>,
/// Message id before which messages should be fetched
#[validate(length(min = 26, max = 26))]
before: Option<String>,
/// Message id after which messages should be fetched
#[validate(length(min = 26, max = 26))]
after: Option<String>,
/// 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<bool>,
}
/// # Search for Messages
///
/// This route searches for messages within the given parameters.
#[openapi(tag = "Messaging")]
#[post("/<target>/search", data = "<options>")]
pub async fn req(
db: &Db,
user: User,
target: Ref,
options: Json<Options>,
options: Json<OptionsMessageSearch>,
) -> Result<Json<BulkMessageResponse>> {
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,

View File

@@ -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<String>,
/// 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<Vec<String>>,
/// Messages to reply to
replies: Option<Vec<Reply>>,
#[validate]
masquerade: Option<Masquerade>,
/// Embeds to include in message
#[validate(length(min = 1, max = 10))]
embeds: Option<Vec<SendableEmbed>>,
/// Masquerade to apply to this message
#[validate]
masquerade: Option<Masquerade>,
}
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("/<target>/messages", data = "<data>")]
pub async fn message_send(
db: &Db,
user: User,
target: Ref,
data: Json<Data>,
data: Json<DataMessageSend>,
mut idempotency: IdempotencyKey,
) -> Result<Json<Message>> {
let data = data.into_inner();

View File

@@ -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<Route> {
routes![
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
channel_ack::req,
channel_fetch::req,
members_fetch::req,

View File

@@ -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("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
pub async fn req(
db: &Db,

View File

@@ -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("/<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 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 {
Channel::Group { .. } => {
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<Data>) -> Result<J
default_permissions,
..
} => {
if let Data::Field { permissions } = data {
if let DataDefaultChannelPermissions::Field { permissions } = data {
perm.throw_permission_override(
db,
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 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<Value> {
unimplemented!()

View File

@@ -20,6 +20,7 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
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<Build>) -> Rocket<Build> {
.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())

View File

@@ -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<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]
impl<'r> FromRequest<'r> for IdempotencyKey {
type Error = Error;