this repo has no description
1use crate::state::AppState;
2use axum::{
3 Json,
4 extract::State,
5 http::StatusCode,
6 response::{IntoResponse, Response},
7};
8use serde::{Deserialize, Serialize};
9use serde_json::{Value, json};
10use sqlx::Row;
11use tracing::error;
12
13#[derive(Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct CreateReportInput {
16 pub reason_type: String,
17 pub reason: Option<String>,
18 pub subject: Value,
19}
20
21#[derive(Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct CreateReportOutput {
24 pub id: i64,
25 pub reason_type: String,
26 pub reason: Option<String>,
27 pub subject: Value,
28 pub reported_by: String,
29 pub created_at: String,
30}
31
32pub async fn create_report(
33 State(state): State<AppState>,
34 headers: axum::http::HeaderMap,
35 Json(input): Json<CreateReportInput>,
36) -> Response {
37 let auth_header = headers.get("Authorization");
38 if auth_header.is_none() {
39 return (
40 StatusCode::UNAUTHORIZED,
41 Json(json!({"error": "AuthenticationRequired"})),
42 )
43 .into_response();
44 }
45
46 let token = auth_header
47 .unwrap()
48 .to_str()
49 .unwrap_or("")
50 .replace("Bearer ", "");
51
52 let session = sqlx::query(
53 r#"
54 SELECT s.did, k.key_bytes
55 FROM sessions s
56 JOIN users u ON s.did = u.did
57 JOIN user_keys k ON u.id = k.user_id
58 WHERE s.access_jwt = $1
59 "#,
60 )
61 .bind(&token)
62 .fetch_optional(&state.db)
63 .await;
64
65 let (did, key_bytes) = match session {
66 Ok(Some(row)) => (
67 row.get::<String, _>("did"),
68 row.get::<Vec<u8>, _>("key_bytes"),
69 ),
70 Ok(None) => {
71 return (
72 StatusCode::UNAUTHORIZED,
73 Json(json!({"error": "AuthenticationFailed"})),
74 )
75 .into_response();
76 }
77 Err(e) => {
78 error!("DB error in create_report: {:?}", e);
79 return (
80 StatusCode::INTERNAL_SERVER_ERROR,
81 Json(json!({"error": "InternalError"})),
82 )
83 .into_response();
84 }
85 };
86
87 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
88 return (
89 StatusCode::UNAUTHORIZED,
90 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
91 )
92 .into_response();
93 }
94
95 let valid_reason_types = [
96 "com.atproto.moderation.defs#reasonSpam",
97 "com.atproto.moderation.defs#reasonViolation",
98 "com.atproto.moderation.defs#reasonMisleading",
99 "com.atproto.moderation.defs#reasonSexual",
100 "com.atproto.moderation.defs#reasonRude",
101 "com.atproto.moderation.defs#reasonOther",
102 "com.atproto.moderation.defs#reasonAppeal",
103 ];
104
105 if !valid_reason_types.contains(&input.reason_type.as_str()) {
106 return (
107 StatusCode::BAD_REQUEST,
108 Json(json!({"error": "InvalidRequest", "message": "Invalid reasonType"})),
109 )
110 .into_response();
111 }
112
113 let created_at = chrono::Utc::now().to_rfc3339();
114 let report_id = chrono::Utc::now().timestamp_millis();
115
116 (
117 StatusCode::OK,
118 Json(CreateReportOutput {
119 id: report_id,
120 reason_type: input.reason_type,
121 reason: input.reason,
122 subject: input.subject,
123 reported_by: did,
124 created_at,
125 }),
126 )
127 .into_response()
128}