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}