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