1use crate::database::Database;
2use crate::model::{anonymous_profile, CommentCreate, DatabaseError, ResponseEdit};
3use axum::http::{HeaderMap, HeaderValue};
4use hcaptcha_no_wasm::Hcaptcha;
5use authbeam::model::{IpBlockCreate, NotificationCreate};
6use databeam::prelude::DefaultReturn;
7
8use axum::response::{IntoResponse, Redirect};
9use axum::{
10 extract::{Path, State},
11 routing::{delete, get, post, put},
12 Json, Router,
13};
14
15use axum_extra::extract::cookie::CookieJar;
16
17pub fn routes(database: Database) -> Router {
18 Router::new()
19 .route("/", post(create_request))
20 .route("/{id}", get(get_request))
21 .route("/{id}", put(edit_request))
22 .route("/{id}", delete(delete_request))
23 .route("/{id}/report", post(report_request))
24 .route("/{id}/ipblock", post(ipblock_request))
25 .with_state(database)
27}
28
29pub async fn create_request(
33 jar: CookieJar,
34 headers: HeaderMap,
35 State(database): State<Database>,
36 Json(req): Json<CommentCreate>,
37) -> impl IntoResponse {
38 let mut was_not_anonymous = false;
40
41 let auth_user = match jar.get("__Secure-Token") {
42 Some(c) => match database
43 .auth
44 .get_profile_by_unhashed(c.value_trimmed())
45 .await
46 {
47 Ok(ua) => {
48 was_not_anonymous = true;
49 ua
50 }
51 Err(_) => anonymous_profile(database.create_anonymous().0),
52 },
53 None => anonymous_profile(database.create_anonymous().0),
54 };
55
56 let existing_tag = match jar.get("__Secure-Question-Tag") {
57 Some(c) => c.value_trimmed().to_string(),
58 None => String::new(),
59 };
60
61 let real_ip = if let Some(ref real_ip_header) = database.config.real_ip_header {
63 headers
64 .get(real_ip_header.to_owned())
65 .unwrap_or(&HeaderValue::from_static(""))
66 .to_str()
67 .unwrap_or("")
68 .to_string()
69 } else {
70 String::new()
71 };
72
73 if database.auth.get_ipban_by_ip(&real_ip).await.is_ok() {
75 return (
76 [
77 ("Content-Type".to_string(), "text/plain".to_string()),
78 ("Set-Cookie".to_string(), String::new()),
79 ],
80 Json(DefaultReturn {
81 success: false,
82 message: DatabaseError::Banned.to_string(),
83 payload: None,
84 }),
85 );
86 }
87
88 let use_anonymous_anyways = req.anonymous; if (auth_user.username == "anonymous") | use_anonymous_anyways {
92 let tag = if was_not_anonymous && use_anonymous_anyways {
93 format!("anonymous#{}", auth_user.id)
95 } else if !existing_tag.is_empty() {
96 existing_tag
98 } else if !was_not_anonymous {
99 auth_user.id
101 } else {
102 if auth_user.username == "anonymous" {
104 auth_user.id
105 } else {
106 auth_user.id
107 }
108 };
109
110 return (
112 [
113 ("Content-Type".to_string(), "text/plain".to_string()),
114 (
115 "Set-Cookie".to_string(),
116 format!(
117 "__Secure-Question-Tag={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}",
118 tag,
119 60 * 60 * 24 * 365
120 ),
121 ),
122 ],
123 Json(match database.create_comment(req, tag, real_ip).await {
124 Ok(r) => DefaultReturn {
125 success: true,
126 message: String::new(),
127 payload: Some(r),
128 },
129 Err(e) => e.into(),
130 }),
131 );
132 }
133
134 (
136 [
137 ("Content-Type".to_string(), "text/plain".to_string()),
138 (
139 "Set-Cookie".to_string(),
140 format!(
141 "__Secure-Question-Tag={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}",
142 auth_user.username.replace("anonymous#", ""),
143 60 * 60 * 24 * 365
144 ),
145 ),
146 ],
147 Json(match database.create_comment(req, auth_user.id, real_ip).await {
148 Ok(r) => DefaultReturn {
149 success: true,
150 message: String::new(),
151 payload: Some(r),
152 },
153 Err(e) => e.into(),
154 })
155 )
156}
157
158pub async fn get_request(
160 Path(id): Path<String>,
161 State(database): State<Database>,
162) -> impl IntoResponse {
163 Json(match database.get_comment(id, true).await {
164 Ok(mut r) => DefaultReturn {
165 success: true,
166 message: String::new(),
167 payload: {
168 r.0.author.clean();
169 Some(r)
170 },
171 },
172 Err(e) => e.into(),
173 })
174}
175
176pub async fn expand_request(
178 Path(id): Path<String>,
179 State(database): State<Database>,
180) -> impl IntoResponse {
181 match database.get_comment(id, false).await {
182 Ok(c) => Redirect::to(&format!("/@{}/c/{}", c.0.author.username, c.0.id)),
183 Err(_) => Redirect::to("/"),
184 }
185}
186
187pub async fn edit_request(
189 jar: CookieJar,
190 Path(id): Path<String>,
191 State(database): State<Database>,
192 Json(req): Json<ResponseEdit>,
193) -> impl IntoResponse {
194 let auth_user = match jar.get("__Secure-Token") {
196 Some(c) => match database
197 .auth
198 .get_profile_by_unhashed(c.value_trimmed())
199 .await
200 {
201 Ok(ua) => ua,
202 Err(_) => {
203 return Json(DatabaseError::NotAllowed.into());
204 }
205 },
206 None => {
207 return Json(DatabaseError::NotAllowed.into());
208 }
209 };
210
211 Json(
213 match database
214 .update_comment_content(id, req.content, auth_user)
215 .await
216 {
217 Ok(r) => DefaultReturn {
218 success: true,
219 message: String::new(),
220 payload: Some(r),
221 },
222 Err(e) => e.into(),
223 },
224 )
225}
226
227pub async fn delete_request(
229 jar: CookieJar,
230 Path(id): Path<String>,
231 State(database): State<Database>,
232) -> impl IntoResponse {
233 let auth_user = match jar.get("__Secure-Token") {
235 Some(c) => match database
236 .auth
237 .get_profile_by_unhashed(c.value_trimmed())
238 .await
239 {
240 Ok(ua) => ua,
241 Err(_) => {
242 return Json(DatabaseError::NotAllowed.into());
243 }
244 },
245 None => {
246 return Json(DatabaseError::NotAllowed.into());
247 }
248 };
249
250 Json(match database.delete_comment(id, auth_user).await {
252 Ok(r) => DefaultReturn {
253 success: true,
254 message: String::new(),
255 payload: Some(r),
256 },
257 Err(e) => e.into(),
258 })
259}
260
261pub async fn report_request(
263 headers: HeaderMap,
264 Path(id): Path<String>,
265 State(database): State<Database>,
266 Json(req): Json<super::CreateReport>,
267) -> impl IntoResponse {
268 if let Err(e) = req
270 .valid_response(&database.config.captcha.secret, None)
271 .await
272 {
273 return Json(DefaultReturn {
274 success: false,
275 message: e.to_string(),
276 payload: (),
277 });
278 }
279
280 if let Err(_) = database.get_comment(id.clone(), false).await {
282 return Json(DefaultReturn {
283 success: false,
284 message: DatabaseError::NotFound.to_string(),
285 payload: (),
286 });
287 };
288
289 let real_ip = if let Some(ref real_ip_header) = database.config.real_ip_header {
291 headers
292 .get(real_ip_header.to_owned())
293 .unwrap_or(&HeaderValue::from_static(""))
294 .to_str()
295 .unwrap_or("")
296 .to_string()
297 } else {
298 String::new()
299 };
300
301 if database.auth.get_ipban_by_ip(&real_ip).await.is_ok() {
303 return Json(DefaultReturn {
304 success: false,
305 message: DatabaseError::Banned.to_string(),
306 payload: (),
307 });
308 }
309
310 match database
312 .auth
313 .create_notification(
314 NotificationCreate {
315 title: format!("**COMMENT REPORT**: {id}"),
316 content: format!("{}\n\n***\n\n[{real_ip}](/+i/{real_ip})", req.content),
317 address: format!("/comment/{id}"),
318 recipient: "*".to_string(), },
320 None,
321 )
322 .await
323 {
324 Ok(_) => {
325 return Json(DefaultReturn {
326 success: true,
327 message: "Comment reported!".to_string(),
328 payload: (),
329 })
330 }
331 Err(_) => Json(DefaultReturn {
332 success: false,
333 message: DatabaseError::NotFound.to_string(),
334 payload: (),
335 }),
336 }
337}
338
339pub async fn ipblock_request(
341 jar: CookieJar,
342 Path(id): Path<String>,
343 State(database): State<Database>,
344) -> impl IntoResponse {
345 let auth_user = match jar.get("__Secure-Token") {
347 Some(c) => match database
348 .auth
349 .get_profile_by_unhashed(c.value_trimmed())
350 .await
351 {
352 Ok(ua) => ua,
353 Err(_) => {
354 return Json(DatabaseError::NotAllowed.into());
355 }
356 },
357 None => {
358 return Json(DatabaseError::NotAllowed.into());
359 }
360 };
361
362 let comment = match database.get_comment(id.clone(), false).await {
364 Ok(q) => q.0,
365 Err(e) => return Json(e.to_json()),
366 };
367
368 match database
370 .auth
371 .create_ipblock(
372 IpBlockCreate {
373 ip: comment.ip,
374 context: comment.content,
375 },
376 auth_user,
377 )
378 .await
379 {
380 Ok(_) => {
381 return Json(DefaultReturn {
382 success: true,
383 message: "IP blocked!".to_string(),
384 payload: (),
385 })
386 }
387 Err(_) => Json(DefaultReturn {
388 success: false,
389 message: DatabaseError::Other.to_string(),
390 payload: (),
391 }),
392 }
393}