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(); 114 let report_id = created_at.timestamp_millis(); 115 116 let insert = sqlx::query( 117 "INSERT INTO reports (id, reason_type, reason, subject_json, reported_by_did, created_at) VALUES ($1, $2, $3, $4, $5, $6)" 118 ) 119 .bind(report_id) 120 .bind(&input.reason_type) 121 .bind(&input.reason) 122 .bind(json!(input.subject)) 123 .bind(&did) 124 .bind(created_at) 125 .execute(&state.db) 126 .await; 127 128 if let Err(e) = insert { 129 error!("Failed to insert report: {:?}", e); 130 return ( 131 StatusCode::INTERNAL_SERVER_ERROR, 132 Json(json!({"error": "InternalError"})), 133 ) 134 .into_response(); 135 } 136 137 ( 138 StatusCode::OK, 139 Json(CreateReportOutput { 140 id: report_id, 141 reason_type: input.reason_type, 142 reason: input.reason, 143 subject: input.subject, 144 reported_by: did, 145 created_at: created_at.to_rfc3339(), 146 }), 147 ) 148 .into_response() 149}