mirror of
https://github.com/stoatchat/stoatchat.git
synced 2026-08-31 04:42:29 +00:00
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:
@@ -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>>>,
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
5
crates/core/database/src/models/discover_requests/mod.rs
Normal file
5
crates/core/database/src/models/discover_requests/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod model;
|
||||
mod ops;
|
||||
|
||||
pub use model::*;
|
||||
pub use ops::*;
|
||||
36
crates/core/database/src/models/discover_requests/model.rs
Normal file
36
crates/core/database/src/models/discover_requests/model.rs
Normal 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,
|
||||
}
|
||||
);
|
||||
35
crates/core/database/src/models/discover_requests/ops.rs
Normal file
35
crates/core/database/src/models/discover_requests/ops.rs
Normal 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>;
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user