Files
fluxer/fluxer_admin/src/api/reports.rs
T

305 lines
9.1 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-or-later
use reqwest::Method;
use crate::api::generated::{
snowflake,
types::{
ReportNoteRequest, ReportNoteResponse, UpdateReportRequest, UpdateReportRequestOutcome,
UpdateReportRequestResolution, UpdateReportRequestStatus,
},
};
use super::client::{AdminApiClient, ApiError, ApiResult};
use super::types::{
ReportEntry, ReportLegalHoldResponse, ReportReasonListResponse, ResolveReportResponse,
SearchReportsResponse,
};
#[derive(Default)]
pub struct SearchReportsParams<'a> {
pub query: Option<&'a str>,
pub status: Option<i32>,
pub report_type: Option<i32>,
pub category: Option<&'a str>,
pub reason: Option<&'a str>,
pub reporter_id: Option<&'a str>,
pub reported_user_id: Option<&'a str>,
pub reported_webhook_id: Option<&'a str>,
pub reported_guild_id: Option<&'a str>,
pub reported_channel_id: Option<&'a str>,
pub guild_context_id: Option<&'a str>,
pub resolved_by_admin_id: Option<&'a str>,
pub sort_by: Option<&'a str>,
pub sort_order: Option<&'a str>,
pub limit: u32,
pub offset: u64,
}
impl AdminApiClient {
pub async fn get_report(&self, report_id: &str) -> ApiResult<ReportEntry> {
let response = self
.generated()
.get_admin_report(&snowflake(report_id))
.await
.map_err(|e| self.generated_error(e))?;
self.generated_value(response.into_inner())
}
pub async fn resolve_report(
&self,
report_id: &str,
resolution: UpdateReportRequestResolution,
outcome: Option<UpdateReportRequestOutcome>,
duplicate_of: Option<&str>,
public_comment: Option<&str>,
notify_reporter: bool,
) -> ApiResult<ResolveReportResponse> {
let body = UpdateReportRequest {
duplicate_of: duplicate_of.map(snowflake),
notify_reporter,
outcome,
public_comment: public_comment.map(str::to_owned),
resolution,
status: UpdateReportRequestStatus::Resolved,
};
self.send(
Method::PATCH,
&format!("/admin/reports/{}", urlencoding::encode(report_id)),
Some(&body),
None,
)
.await
}
pub async fn set_report_legal_hold(
&self,
report_id: &str,
legal_hold_until: Option<&str>,
legal_hold_reason: Option<&str>,
) -> ApiResult<ReportLegalHoldResponse> {
let body = serde_json::json!({
"legal_hold_until": legal_hold_until,
"legal_hold_reason": legal_hold_until.and(legal_hold_reason),
});
self.send(
Method::POST,
&format!(
"/admin/reports/{}/legal-hold",
urlencoding::encode(report_id)
),
Some(&body),
None,
)
.await
}
pub async fn annotate_report(
&self,
report_id: &str,
body: String,
) -> ApiResult<ReportNoteResponse> {
self.send(
Method::POST,
&format!("/admin/reports/{}/notes", urlencoding::encode(report_id)),
Some(&ReportNoteRequest { body }),
None,
)
.await
}
pub async fn delete_report(
&self,
report_id: &str,
audit_log_reason: Option<&str>,
) -> ApiResult<()> {
self.send_void(
Method::DELETE,
&format!("/admin/reports/{}", urlencoding::encode(report_id)),
None::<&serde_json::Value>,
audit_log_reason,
)
.await
}
pub async fn search_reports(
&self,
params: &SearchReportsParams<'_>,
) -> ApiResult<SearchReportsResponse> {
let status = params
.status
.map(report_status)
.transpose()?
.unwrap_or_default();
let report_type = params
.report_type
.map(report_type_name)
.transpose()?
.unwrap_or_default();
let sort_by = params
.sort_by
.map(report_sort_by)
.transpose()?
.unwrap_or_default();
let limit = params.limit.to_string();
let offset = params.offset.to_string();
let query_params = [
("q", params.query.unwrap_or_default()),
("status", status),
("report_type", report_type),
("category", params.category.unwrap_or_default()),
("reason", params.reason.unwrap_or_default()),
("reporter_id", params.reporter_id.unwrap_or_default()),
(
"reported_user_id",
params.reported_user_id.unwrap_or_default(),
),
(
"reported_webhook_id",
params.reported_webhook_id.unwrap_or_default(),
),
(
"reported_guild_id",
params.reported_guild_id.unwrap_or_default(),
),
(
"reported_channel_id",
params.reported_channel_id.unwrap_or_default(),
),
(
"guild_context_id",
params.guild_context_id.unwrap_or_default(),
),
(
"resolved_by_admin_id",
params.resolved_by_admin_id.unwrap_or_default(),
),
("sort_by", sort_by),
("sort_order", params.sort_order.unwrap_or_default()),
("limit", limit.as_str()),
("offset", offset.as_str()),
];
self.get("/admin/reports", Some(&query_params)).await
}
pub async fn list_report_reasons(&self) -> ApiResult<ReportReasonListResponse> {
self.get("/admin/report-reasons", None).await
}
pub async fn search_reports_by_reporter(
&self,
reporter_id: &str,
limit: u32,
offset: u64,
) -> ApiResult<SearchReportsResponse> {
self.search_reports(&SearchReportsParams {
reporter_id: Some(reporter_id),
limit,
offset,
..Default::default()
})
.await
}
pub async fn search_reports_by_reported_user(
&self,
reported_user_id: &str,
limit: u32,
offset: u64,
) -> ApiResult<SearchReportsResponse> {
self.search_reports(&SearchReportsParams {
reported_user_id: Some(reported_user_id),
limit,
offset,
..Default::default()
})
.await
}
}
fn report_status(value: i32) -> ApiResult<&'static str> {
match value {
0 => Ok("pending"),
1 => Ok("resolved"),
other => Err(ApiError::Parse(format!("unknown report status: {other}"))),
}
}
fn report_type_name(value: i32) -> ApiResult<&'static str> {
match value {
0 => Ok("message"),
1 => Ok("user"),
2 => Ok("guild"),
other => Err(ApiError::Parse(format!("unknown report type: {other}"))),
}
}
fn report_sort_by(value: &str) -> ApiResult<&'static str> {
match value {
"created_at" | "createdAt" => Ok("created_at"),
"reported_at" | "reportedAt" => Ok("reported_at"),
"resolved_at" | "resolvedAt" => Ok("resolved_at"),
other => Err(ApiError::Parse(format!(
"unknown report sort field: {other}"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn report_statuses_preserve_the_closed_wire_mapping() {
for (value, expected) in [(0, "pending"), (1, "resolved")] {
assert_eq!(report_status(value).expect("supported status"), expected);
}
for value in [-1, 2] {
assert_eq!(
report_status(value)
.expect_err("unknown status")
.to_string(),
format!("parse error: unknown report status: {value}")
);
}
}
#[test]
fn report_types_preserve_the_closed_wire_mapping() {
for (value, expected) in [(0, "message"), (1, "user"), (2, "guild")] {
assert_eq!(report_type_name(value).expect("supported type"), expected);
}
for value in [-1, 3] {
assert_eq!(
report_type_name(value)
.expect_err("unknown type")
.to_string(),
format!("parse error: unknown report type: {value}")
);
}
}
#[test]
fn report_sort_fields_accept_only_the_existing_aliases() {
for (field, expected) in [
("createdAt", "created_at"),
("created_at", "created_at"),
("reportedAt", "reported_at"),
("reported_at", "reported_at"),
("resolvedAt", "resolved_at"),
("resolved_at", "resolved_at"),
] {
assert_eq!(
report_sort_by(field).expect("supported sort field"),
expected
);
}
assert_eq!(
report_sort_by("unknown")
.expect_err("unknown sort field")
.to_string(),
"parse error: unknown report sort field: unknown"
);
}
}