feat: implement discover endpoints (#940)

* feat: implement discover endpoints

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
Release-As: 0.15.2
This commit is contained in:
Tom
2026-08-24 10:57:44 -07:00
committed by IAmTomahawkx
parent 8e2eacb1b0
commit 793061685d
25 changed files with 703 additions and 25 deletions

View File

@@ -3,9 +3,10 @@ use std::{collections::HashMap, sync::Arc};
use futures::lock::Mutex;
use crate::{
Account, AccountInvite, AuditLogEntry, Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji,
File, FileHash, Invite, MFATicket, Member, MemberCompositeKey, Message, PolicyChange,
RatelimitEvent, Report, Server, ServerBan, Session, Snapshot, User, UserSettings, Webhook,
Account, AccountInvite, AuditLogEntry, Bot, Channel, ChannelCompositeKey, ChannelUnread,
DiscoverBan, DiscoverRequest, DiscoverRequestType, Emoji, File, FileHash, Invite, MFATicket,
Member, MemberCompositeKey, Message, PolicyChange, RatelimitEvent, Report, Server, ServerBan,
Session, Snapshot, User, UserSettings, Webhook,
};
database_derived!(
@@ -19,6 +20,8 @@ database_derived!(
pub channel_unreads: Arc<Mutex<HashMap<ChannelCompositeKey, ChannelUnread>>>,
pub channel_webhooks: Arc<Mutex<HashMap<String, Webhook>>>,
pub emojis: Arc<Mutex<HashMap<String, Emoji>>>,
pub discover_requests: Arc<Mutex<HashMap<(DiscoverRequestType, String), DiscoverRequest>>>,
pub discover_bans: Arc<Mutex<HashMap<String, DiscoverBan>>>,
pub file_hashes: Arc<Mutex<HashMap<String, FileHash>>>,
pub files: Arc<Mutex<HashMap<String, File>>>,
pub messages: Arc<Mutex<HashMap<String, Message>>>,

View File

@@ -113,6 +113,10 @@ pub async fn create_database(db: &MongoDb) {
.await
.expect("Failed to create mfa_tickets collection.");
db.create_collection("discover_requests")
.await
.expect("Failed to create discover_requests collection");
db.run_command(doc! {
"createIndexes": "users",
"indexes": [
@@ -414,5 +418,20 @@ pub async fn create_database(db: &MongoDb) {
.await
.unwrap();
db.run_command(doc! {
"createIndexes": "discover_requests",
"indexes": [
{
"key": {
"request_type": 1,
"request_id": 1
},
"name": "request_type_id"
}
]
})
.await
.expect("Failed to create discover_requests index");
info!("Created database.");
}

View File

@@ -26,7 +26,7 @@ struct MigrationInfo {
revision: i32,
}
pub const LATEST_REVISION: i32 = 52; // MUST BE +1 to last migration
pub const LATEST_REVISION: i32 = 53; // MUST BE +1 to last migration
pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::<Document>("migrations");
@@ -1299,7 +1299,9 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
}
for session in sessions {
let timestamp = iso8601_timestamp::Timestamp::from(Ulid::from_string(&session._id).unwrap().datetime());
let timestamp = iso8601_timestamp::Timestamp::from(
Ulid::from_string(&session._id).unwrap().datetime(),
);
db.db()
.collection::<Document>("sessions")
@@ -1478,14 +1480,15 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
if revision <= 50 {
info!("Running migration [revision 50 / 13-04-2026]: Rename invites collection to account_invites");
let result = db.db()
let result = db
.db()
.client()
.database("admin")
.run_command(doc! {
"renameCollection": "revolt.invites",
"to": "revolt.account_invites",
"dropTarget": true
})
"renameCollection": "revolt.invites",
"to": "revolt.account_invites",
"dropTarget": true
})
.await;
if let Err(e) = result {
@@ -1496,7 +1499,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
}
}
if revision >= 51 {
if revision <= 51 {
info!("Running migration [revision 51 / 28-11-2025]: Add audit logs collection");
db.db()
@@ -1529,6 +1532,35 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("Failed to create audit_logs index");
};
if revision <= 52 {
let config = revolt_config::config().await;
if config.production {
info!("Running migration [revision 52 / 20-08-2026]: Discover endpoints");
db.db()
.create_collection("discover_requests")
.await
.expect("Failed to create discover_requests collection");
db.db()
.run_command(doc! {
"createIndexes": "discover_requests",
"indexes": [
{
"key": {
"request_type": 1,
"request_id": 1
},
"name": "request_type_id"
}
]
})
.await
.expect("Failed to create index");
} else {
info!("Skipping migration [revision 52 / 20-08-2026]: Discover endpoints");
}
}
// Reminder to update LATEST_REVISION when adding new migrations.
LATEST_REVISION.max(revision)
}

View File

@@ -0,0 +1,5 @@
mod model;
mod ops;
pub use model::*;
pub use ops::*;

View File

@@ -0,0 +1,36 @@
auto_derived!(
#[derive(Hash)]
pub enum DiscoverRequestType {
Bot,
Server,
}
pub enum DiscoverRequestStatus {
Pending,
UnderReview,
Denied(Option<String>), // reason
Approved(Option<String>), // reason
}
/// Discover request
pub struct DiscoverRequest {
/// The type of request.
#[serde(rename = "type")]
pub request_type: DiscoverRequestType,
/// The ID of the bot/server
pub request_id: String,
/// status of the request
pub status: DiscoverRequestStatus,
}
pub struct DiscoverBan {
/// Ban Id
#[serde(rename = "_id")]
pub id: String,
/// The type of item.
#[serde(rename = "type")]
pub item_type: DiscoverRequestType,
/// The ID of the bot/server
pub item_id: String,
}
);

View File

@@ -0,0 +1,35 @@
use revolt_result::Result;
use crate::{DiscoverRequest, DiscoverRequestType};
#[cfg(feature = "mongodb")]
mod mongodb;
mod reference;
#[async_trait]
pub trait AbstractDiscoverRequest: Sync + Send {
/// Insert discover request into database.
/// Update an existing one if it was previously denied
async fn insert_discover_request(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<DiscoverRequest>;
/// Fetch Discover request by their parent id
async fn fetch_discover_request_by_item_id(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<DiscoverRequest>;
/// Remove Discover request
async fn delete_discover_request(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<()>;
/// Fetch if the item is banned from being requested. If the item is Some, then the item is banned.
async fn get_discover_ban(&self, item_type: DiscoverRequestType, item: &str) -> Result<bool>;
}

View File

@@ -0,0 +1,90 @@
use revolt_result::Result;
use crate::DiscoverBan;
use crate::DiscoverRequest;
use crate::DiscoverRequestStatus;
use crate::DiscoverRequestType;
use crate::MongoDb;
use super::AbstractDiscoverRequest;
static DISCOVER_COL: &str = "discover_requests";
static DISCOVER_BANS_COL: &str = "discover_bans";
#[async_trait]
impl AbstractDiscoverRequest for MongoDb {
/// Insert request into database.
async fn insert_discover_request(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<DiscoverRequest> {
if let Ok(mut prev) = self
.fetch_discover_request_by_item_id(request_type.clone(), item)
.await
{
match prev.status {
DiscoverRequestStatus::Approved(_)
| DiscoverRequestStatus::Pending
| DiscoverRequestStatus::UnderReview => return Err(create_error!(NoEffect)),
_ => Ok(()),
}?;
self.col::<DiscoverRequest>(DISCOVER_COL).update_one(
doc! {"request_type": bson::to_bson(&request_type).expect("failed to serialize"), "request_id": item},
doc! {"$set": {"status": bson::to_bson(&DiscoverRequestStatus::Pending).expect("failed to serialize")}},
).await.map_err(|_| create_database_error!("update_one", DISCOVER_COL))?;
prev.status = DiscoverRequestStatus::Pending;
Ok(prev)
} else {
let ret = DiscoverRequest {
request_type,
request_id: item.to_string(),
status: DiscoverRequestStatus::Pending,
};
self.col::<DiscoverRequest>(DISCOVER_COL)
.insert_one(ret.clone())
.await
.map_err(|_| create_database_error!("insert_one", DISCOVER_COL))?;
Ok(ret)
}
}
/// Fetch discover by item type/id combo
async fn fetch_discover_request_by_item_id(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<DiscoverRequest> {
query!(
self,
find_one,
DISCOVER_COL,
doc! {"request_type": bson::to_bson(&request_type).expect("failed to serialize"), "request_id": item}
)?.ok_or_else(|| create_database_error!("find_one", DISCOVER_COL))
}
/// Remove discover request
async fn delete_discover_request(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<()> {
query!(
self,
delete_one,
DISCOVER_COL,
doc! {"request_type": bson::to_bson(&request_type).expect("failed to serialize"), "request_id": item}
).map(|_| ())
}
/// Fetch if the item is banned from being requested
async fn get_discover_ban(&self, item_type: DiscoverRequestType, item: &str) -> Result<bool> {
query!(
self,
find_one,
DISCOVER_BANS_COL,
doc! {"request_type": bson::to_bson(&item_type).expect("failed to serialize"), "request_id": item}
)?.ok_or_else(|| create_database_error!("find_one", DISCOVER_COL)).map(|_: DiscoverBan| true)
}
}

View File

@@ -0,0 +1,77 @@
use revolt_result::Result;
use crate::ReferenceDb;
use crate::{DiscoverRequest, DiscoverRequestStatus, DiscoverRequestType};
use super::AbstractDiscoverRequest;
#[async_trait]
impl AbstractDiscoverRequest for ReferenceDb {
/// Insert request into database.
async fn insert_discover_request(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<DiscoverRequest> {
let ret = DiscoverRequest {
request_type: request_type.clone(),
request_id: item.to_string(),
status: DiscoverRequestStatus::Pending,
};
let mut discover = self.discover_requests.lock().await;
if let std::collections::hash_map::Entry::Vacant(e) =
discover.entry((request_type, item.to_string()))
{
e.insert(ret.clone());
Ok(ret)
} else {
Err(create_database_error!("insert", "discover_requests"))
}
}
/// Fetch discover by item type/id combo
async fn fetch_discover_request_by_item_id(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<DiscoverRequest> {
let discover = self.discover_requests.lock().await;
discover
.iter()
.find(|(_, d)| d.request_id == item && d.request_type == request_type)
.map(|(_, d)| d.clone())
.ok_or_else(|| create_error!(NotFound))
}
/// Remove discover request
async fn delete_discover_request(
&self,
request_type: DiscoverRequestType,
item: &str,
) -> Result<()> {
let discover = self.discover_requests.lock().await;
let req = discover
.iter()
.find(|(_, d)| d.request_id == item && d.request_type == request_type)
.map(|(id, _)| id);
if let Some(req) = req {
let mut discover = self.discover_requests.lock().await;
discover.remove_entry(req);
Ok(())
} else {
Err(create_error!(NotFound))
}
}
/// Fetch if the item is banned from being requested
async fn get_discover_ban(&self, item_type: DiscoverRequestType, item: &str) -> Result<bool> {
let discover = self.discover_bans.lock().await;
discover
.iter()
.find(|(_, d)| d.item_id == item && d.item_type == item_type)
.map(|(_, _)| true)
.ok_or_else(|| create_error!(NotFound))
}
}

View File

@@ -1,3 +1,5 @@
mod account_invites;
mod accounts;
mod admin_migrations;
mod audit_logs;
mod bots;
@@ -5,10 +7,12 @@ mod channel_invites;
mod channel_unreads;
mod channel_webhooks;
mod channels;
mod discover_requests;
mod emojis;
mod file_hashes;
mod files;
mod messages;
mod mfa_tickets;
mod policy_changes;
mod ratelimit_events;
mod safety_reports;
@@ -16,13 +20,12 @@ mod safety_snapshots;
mod server_bans;
mod server_members;
mod servers;
mod sessions;
mod user_settings;
mod users;
mod accounts;
mod account_invites;
mod sessions;
mod mfa_tickets;
pub use account_invites::*;
pub use accounts::*;
pub use admin_migrations::*;
pub use audit_logs::*;
pub use bots::*;
@@ -30,10 +33,12 @@ pub use channel_invites::*;
pub use channel_unreads::*;
pub use channel_webhooks::*;
pub use channels::*;
pub use discover_requests::*;
pub use emojis::*;
pub use file_hashes::*;
pub use files::*;
pub use messages::*;
pub use mfa_tickets::*;
pub use policy_changes::*;
pub use ratelimit_events::*;
pub use safety_reports::*;
@@ -41,12 +46,9 @@ pub use safety_snapshots::*;
pub use server_bans::*;
pub use server_members::*;
pub use servers::*;
pub use sessions::*;
pub use user_settings::*;
pub use users::*;
pub use accounts::*;
pub use account_invites::*;
pub use sessions::*;
pub use mfa_tickets::*;
use crate::{Database, ReferenceDb};
@@ -80,6 +82,7 @@ pub trait AbstractDatabase:
+ account_invites::AbstractAccountInvites
+ sessions::AbstractSessions
+ mfa_tickets::AbstractMFATickets
+ discover_requests::AbstractDiscoverRequest
{
}

View File

@@ -102,6 +102,46 @@ impl From<crate::ChannelCompositeKey> for ChannelCompositeKey {
}
}
impl From<crate::DiscoverBan> for DiscoverBan {
fn from(value: crate::DiscoverBan) -> Self {
DiscoverBan {
id: value.id,
item_type: value.item_type.into(),
item_id: value.item_id,
}
}
}
impl From<crate::DiscoverRequest> for DiscoverRequest {
fn from(value: crate::DiscoverRequest) -> Self {
DiscoverRequest {
request_type: value.request_type.into(),
request_id: value.request_id,
status: value.status.into(),
}
}
}
impl From<crate::DiscoverRequestType> for DiscoverRequestType {
fn from(value: crate::DiscoverRequestType) -> Self {
match value {
crate::DiscoverRequestType::Bot => DiscoverRequestType::Bot,
crate::DiscoverRequestType::Server => DiscoverRequestType::Server,
}
}
}
impl From<crate::DiscoverRequestStatus> for DiscoverRequestStatus {
fn from(value: crate::DiscoverRequestStatus) -> Self {
match value {
crate::DiscoverRequestStatus::Approved(s) => DiscoverRequestStatus::Approved(s),
crate::DiscoverRequestStatus::Denied(s) => DiscoverRequestStatus::Denied(s),
crate::DiscoverRequestStatus::Pending => DiscoverRequestStatus::Pending,
crate::DiscoverRequestStatus::UnderReview => DiscoverRequestStatus::UnderReview,
}
}
}
impl From<crate::Webhook> for Webhook {
fn from(value: crate::Webhook) -> Self {
Webhook {

View File

@@ -0,0 +1,35 @@
auto_derived!(
pub enum DiscoverRequestType {
Bot,
Server,
}
pub enum DiscoverRequestStatus {
Pending,
UnderReview,
Denied(Option<String>), // reason
Approved(Option<String>), // reason
}
/// Discover request
pub struct DiscoverRequest {
/// The type of request.
#[serde(rename = "type")]
pub request_type: DiscoverRequestType,
/// The ID of the bot/server
pub request_id: String,
/// status of the request
pub status: DiscoverRequestStatus,
}
pub struct DiscoverBan {
/// Ban Id
#[serde(rename = "_id")]
pub id: String,
/// The type of item.
#[serde(rename = "type")]
pub item_type: DiscoverRequestType,
/// The ID of the bot/server
pub item_id: String,
}
);

View File

@@ -1,41 +1,43 @@
mod accounts;
mod audit_logs;
mod bots;
mod channel_invites;
mod channel_unreads;
mod channel_webhooks;
mod channels;
mod discover;
mod embeds;
mod emojis;
mod files;
mod messages;
mod mfa_tickets;
mod policy_changes;
mod safety_reports;
mod server_bans;
mod server_members;
mod servers;
mod sessions;
mod user_settings;
mod users;
mod accounts;
mod mfa_tickets;
mod sessions;
pub use accounts::*;
pub use audit_logs::*;
pub use bots::*;
pub use channel_invites::*;
pub use channel_unreads::*;
pub use channel_webhooks::*;
pub use channels::*;
pub use discover::*;
pub use embeds::*;
pub use emojis::*;
pub use files::*;
pub use messages::*;
pub use mfa_tickets::*;
pub use policy_changes::*;
pub use safety_reports::*;
pub use server_bans::*;
pub use server_members::*;
pub use servers::*;
pub use sessions::*;
pub use user_settings::*;
pub use users::*;
pub use accounts::*;
pub use mfa_tickets::*;
pub use sessions::*;

View File

@@ -12,6 +12,8 @@ impl IntoResponse for Error {
let status = match self.error_type {
ErrorType::LabelMe => StatusCode::INTERNAL_SERVER_ERROR,
ErrorType::ContactSupport { .. } => StatusCode::BAD_REQUEST,
ErrorType::AlreadyOnboarded => StatusCode::FORBIDDEN,
ErrorType::UnknownUser => StatusCode::NOT_FOUND,

View File

@@ -57,6 +57,11 @@ pub enum ErrorType {
/// This error was not labeled :(
LabelMe,
// ? Support Errors
ContactSupport {
msg: String,
},
// ? Onboarding related errors
AlreadyOnboarded,

View File

@@ -14,6 +14,8 @@ impl<'r> Responder<'r, 'static> for Error {
let status = match self.error_type {
ErrorType::LabelMe => Status::InternalServerError,
ErrorType::ContactSupport { .. } => Status::BadRequest,
ErrorType::AlreadyOnboarded => Status::Forbidden,
ErrorType::UnknownUser => Status::NotFound,

View File

@@ -0,0 +1,41 @@
use revolt_config::config;
use revolt_database::{util::reference::Reference, Database, DiscoverRequestType, User};
use rocket_empty::EmptyResponse;
use revolt_result::{create_error, Result};
use rocket::State;
/// # Add bot to Discover
///
/// This puts your bot into the Discover request queue.
/// This endpoint is ONLY USEFUL in production on stoat.chat/app .
#[openapi(tag = "Discover")]
#[put("/<bot_id>/discover")]
pub async fn discover_add_bot(
db: &State<Database>,
bot_id: Reference<'_>,
user: User,
) -> Result<EmptyResponse> {
let config = config().await;
if !config.production {
return Err(create_error!(NoEffect));
}
let bot = bot_id.as_bot(db).await?;
if (bot.owner != user.id && bot.id != user.id) && !user.privileged {
return Err(create_error!(NotOwner));
}
if db
.get_discover_ban(DiscoverRequestType::Bot, &bot.id)
.await
.is_ok()
{
return Err(create_error!(Banned));
}
db.insert_discover_request(DiscoverRequestType::Bot, &bot.id)
.await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,43 @@
use revolt_config::config;
use revolt_database::{util::reference::Reference, Database, DiscoverRequestType, User};
use revolt_models::v0;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Get Discover request status
///
/// Fetches the status of your Discover request.
/// If it has been approved or denied, the reason will be provided (if applicable).
/// This endpoint is ONLY USEFUL in production on stoat.chat/app .
#[openapi(tag = "Discover")]
#[get("/<bot_id>/discover")]
pub async fn discover_get_bot(
db: &State<Database>,
bot_id: Reference<'_>,
user: User,
) -> Result<Json<v0::DiscoverRequest>> {
let config = config().await;
if !config.production {
return Err(create_error!(NoEffect));
}
let bot = bot_id.as_bot(db).await?;
if (bot.owner != user.id && bot.id != user.id) && !user.privileged {
return Err(create_error!(NotOwner));
}
if db
.get_discover_ban(DiscoverRequestType::Bot, &bot.id)
.await
.is_ok()
{
return Err(create_error!(Banned));
}
let ret = db
.fetch_discover_request_by_item_id(DiscoverRequestType::Bot, &bot.id)
.await?;
Ok(Json(ret.into()))
}

View File

@@ -0,0 +1,55 @@
use revolt_config::config;
use revolt_database::{
util::reference::Reference, Database, DiscoverRequestStatus, DiscoverRequestType, User,
};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// # Delete Discover request
///
/// This cannot be used if your request is no longer in the queue (ie approved or rejected).
/// If you wish to reapply after a rejection, submit another POST.
/// This endpoint is ONLY USEFUL in production on stoat.chat/app .
#[openapi(tag = "Discover")]
#[delete("/<bot_id>/discover")]
pub async fn discover_remove_bot(
db: &State<Database>,
bot_id: Reference<'_>,
user: User,
) -> Result<EmptyResponse> {
let config = config().await;
if !config.production {
return Err(create_error!(NoEffect));
}
let bot = bot_id.as_bot(db).await?;
if (bot.owner != user.id && bot.id != user.id) && !user.privileged {
return Err(create_error!(NotOwner));
}
if db
.get_discover_ban(DiscoverRequestType::Bot, &bot.id)
.await
.is_ok()
{
return Err(create_error!(Banned));
}
let ret = db
.fetch_discover_request_by_item_id(DiscoverRequestType::Bot, &bot.id)
.await?;
match ret.status {
DiscoverRequestStatus::Approved(_) => Err(create_error!(ContactSupport {
msg: "Contact support to have your bot removed from Discover".to_string()
})),
DiscoverRequestStatus::Denied(_) => Err(create_error!(NoEffect)),
DiscoverRequestStatus::Pending | DiscoverRequestStatus::UnderReview => {
db.delete_discover_request(ret.request_type, &ret.request_id)
.await?;
Ok(EmptyResponse)
}
}
}

View File

@@ -0,0 +1,3 @@
pub mod discover_add_bot;
pub mod discover_get_bot;
pub mod discover_remove_bot;

View File

@@ -3,6 +3,7 @@ use rocket::Route;
mod create;
mod delete;
mod discover;
mod edit;
mod fetch;
mod fetch_owned;
@@ -18,5 +19,8 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
fetch_owned::fetch_owned_bots,
edit::edit_bot,
delete::delete_bot,
discover::discover_add_bot::discover_add_bot,
discover::discover_get_bot::discover_get_bot,
discover::discover_remove_bot::discover_remove_bot,
]
}

View File

@@ -0,0 +1,41 @@
use revolt_config::config;
use revolt_database::{util::reference::Reference, Database, DiscoverRequestType, User};
use rocket_empty::EmptyResponse;
use revolt_result::{create_error, Result};
use rocket::State;
/// # Add server to Discover
///
/// This puts your server into the Discover request queue.
/// This endpoint is ONLY USEFUL in production on stoat.chat/app .
#[openapi(tag = "Discover")]
#[put("/<server>/discover")]
pub async fn discover_add(
db: &State<Database>,
server: Reference<'_>,
user: User,
) -> Result<EmptyResponse> {
let config = config().await;
if !config.production {
return Err(create_error!(NoEffect));
}
let server = server.as_server(db).await?;
if server.owner != user.id && !user.privileged {
return Err(create_error!(NotOwner));
}
if db
.get_discover_ban(DiscoverRequestType::Server, &server.id)
.await
.is_ok()
{
return Err(create_error!(Banned));
}
db.insert_discover_request(DiscoverRequestType::Server, &server.id)
.await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,43 @@
use revolt_config::config;
use revolt_database::{util::reference::Reference, Database, DiscoverRequestType, User};
use revolt_models::v0;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Get Discover request status
///
/// Fetches the status of your Discover request.
/// If it has been approved or denied, the reason will be provided (if applicable).
/// This endpoint is ONLY USEFUL in production on stoat.chat/app .
#[openapi(tag = "Discover")]
#[get("/<server>/discover")]
pub async fn discover_get(
db: &State<Database>,
server: Reference<'_>,
user: User,
) -> Result<Json<v0::DiscoverRequest>> {
let config = config().await;
if !config.production {
return Err(create_error!(NoEffect));
}
let server = server.as_server(db).await?;
if server.owner != user.id && !user.privileged {
return Err(create_error!(NotOwner));
}
if db
.get_discover_ban(DiscoverRequestType::Server, &server.id)
.await
.is_ok()
{
return Err(create_error!(Banned));
}
let ret = db
.fetch_discover_request_by_item_id(DiscoverRequestType::Server, &server.id)
.await?;
Ok(Json(ret.into()))
}

View File

@@ -0,0 +1,55 @@
use revolt_config::config;
use revolt_database::{
util::reference::Reference, Database, DiscoverRequestStatus, DiscoverRequestType, User,
};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// # Delete Discover request
///
/// This cannot be used if your request is no longer in the queue (ie approved or rejected).
/// If you wish to reapply after a rejection, submit another POST.
/// This endpoint is ONLY USEFUL in production on stoat.chat/app .
#[openapi(tag = "Discover")]
#[delete("/<server>/discover")]
pub async fn discover_remove(
db: &State<Database>,
server: Reference<'_>,
user: User,
) -> Result<EmptyResponse> {
let config = config().await;
if !config.production {
return Err(create_error!(NoEffect));
}
let server = server.as_server(db).await?;
if server.owner != user.id && !user.privileged {
return Err(create_error!(NotOwner));
}
if db
.get_discover_ban(DiscoverRequestType::Server, &server.id)
.await
.is_ok()
{
return Err(create_error!(Banned));
}
let ret = db
.fetch_discover_request_by_item_id(DiscoverRequestType::Server, &server.id)
.await?;
match ret.status {
DiscoverRequestStatus::Approved(_) => Err(create_error!(ContactSupport {
msg: "Contact support to have your server removed from Discover".to_string()
})),
DiscoverRequestStatus::Denied(_) => Err(create_error!(NoEffect)),
DiscoverRequestStatus::Pending | DiscoverRequestStatus::UnderReview => {
db.delete_discover_request(ret.request_type, &ret.request_id)
.await?;
Ok(EmptyResponse)
}
}
}

View File

@@ -0,0 +1,3 @@
pub mod discover_add;
pub mod discover_get;
pub mod discover_remove;

View File

@@ -6,6 +6,7 @@ mod ban_create;
mod ban_list;
mod ban_remove;
mod channel_create;
mod discover;
mod emoji_list;
mod invites_fetch;
mod member_edit;
@@ -52,5 +53,8 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
emoji_list::list_emoji,
roles_edit_positions::edit_role_ranks,
audit_log_query::query,
discover::discover_add::discover_add,
discover::discover_get::discover_get,
discover::discover_remove::discover_remove,
]
}