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}