139 lines
3.8 KiB
Rust
139 lines
3.8 KiB
Rust
use axum::{
|
|
extract::{Extension, Query, State},
|
|
Json,
|
|
};
|
|
use chrono::Utc;
|
|
use serde::{Deserialize, Serialize};
|
|
use shared::{errors::AppError, models::{Claims, ReportType}};
|
|
use sqlx::PgPool;
|
|
use uuid::Uuid;
|
|
use validator::Validate;
|
|
|
|
#[derive(Debug, Deserialize, Validate)]
|
|
pub struct CreateReportRequest {
|
|
pub lat: f64,
|
|
pub lng: f64,
|
|
pub report_type: ReportType,
|
|
#[validate(length(min = 1, max = 500))]
|
|
pub text: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct NearQuery {
|
|
pub min_lat: f64,
|
|
pub min_lng: f64,
|
|
pub max_lat: f64,
|
|
pub max_lng: f64,
|
|
#[serde(default = "default_limit")]
|
|
pub limit: i64,
|
|
}
|
|
|
|
fn default_limit() -> i64 { 100 }
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ReportResponse {
|
|
pub id: Uuid,
|
|
pub user_id: Uuid,
|
|
pub handle: String,
|
|
pub report_type: ReportType,
|
|
pub lat: f64,
|
|
pub lng: f64,
|
|
pub text: String,
|
|
pub upvotes: i32,
|
|
pub flags: i32,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
}
|
|
|
|
pub async fn create_report(
|
|
State(db): State<PgPool>,
|
|
Extension(claims): Extension<Claims>,
|
|
Json(req): Json<CreateReportRequest>,
|
|
) -> Result<Json<ReportResponse>, AppError> {
|
|
req.validate()
|
|
.map_err(|e| AppError::BadRequest(e.to_string()))?;
|
|
|
|
let id = Uuid::new_v4();
|
|
let user_id: Uuid = claims.sub.parse().map_err(|_| AppError::Internal("bad uid".into()))?;
|
|
|
|
let report_type = req.report_type.clone();
|
|
sqlx::query!(
|
|
r#"INSERT INTO reports (id, user_id, report_type, geom, text)
|
|
VALUES ($1, $2, $3::report_type, ST_SetSRID(ST_MakePoint($5, $4), 4326), $6)"#,
|
|
id, user_id, report_type as ReportType, req.lat, req.lng, req.text
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
Ok(Json(ReportResponse {
|
|
id,
|
|
user_id,
|
|
handle: claims.handle,
|
|
report_type: req.report_type,
|
|
lat: req.lat,
|
|
lng: req.lng,
|
|
text: req.text,
|
|
upvotes: 0,
|
|
flags: 0,
|
|
created_at: Utc::now(),
|
|
}))
|
|
}
|
|
|
|
pub async fn get_reports_near(
|
|
State(db): State<PgPool>,
|
|
Query(q): Query<NearQuery>,
|
|
) -> Result<Json<Vec<ReportResponse>>, AppError> {
|
|
let rows = sqlx::query!(
|
|
r#"SELECT r.id, r.user_id, u.handle,
|
|
r.report_type as "report_type: ReportType",
|
|
ST_Y(r.geom::geometry) as lat,
|
|
ST_X(r.geom::geometry) as lng,
|
|
r.text, r.upvotes, r.flags, r.created_at
|
|
FROM reports r
|
|
JOIN users u ON u.id = r.user_id
|
|
WHERE r.geom && ST_MakeEnvelope($1, $2, $3, $4, 4326)
|
|
AND r.created_at > NOW() - INTERVAL '24 hours'
|
|
ORDER BY r.created_at DESC
|
|
LIMIT $5"#,
|
|
q.min_lng, q.min_lat, q.max_lng, q.max_lat, q.limit
|
|
)
|
|
.fetch_all(&db)
|
|
.await?;
|
|
|
|
let reports = rows.into_iter().map(|r| ReportResponse {
|
|
id: r.id,
|
|
user_id: r.user_id,
|
|
handle: r.handle,
|
|
report_type: r.report_type,
|
|
lat: r.lat.unwrap_or(0.0),
|
|
lng: r.lng.unwrap_or(0.0),
|
|
text: r.text,
|
|
upvotes: r.upvotes,
|
|
flags: r.flags,
|
|
created_at: r.created_at,
|
|
}).collect();
|
|
|
|
Ok(Json(reports))
|
|
}
|
|
|
|
pub async fn upvote_report(
|
|
State(db): State<PgPool>,
|
|
Extension(_claims): Extension<Claims>,
|
|
axum::extract::Path(id): axum::extract::Path<Uuid>,
|
|
) -> Result<Json<serde_json::Value>, AppError> {
|
|
sqlx::query!("UPDATE reports SET upvotes = upvotes + 1 WHERE id = $1", id)
|
|
.execute(&db)
|
|
.await?;
|
|
Ok(Json(serde_json::json!({ "ok": true })))
|
|
}
|
|
|
|
pub async fn flag_report(
|
|
State(db): State<PgPool>,
|
|
Extension(_claims): Extension<Claims>,
|
|
axum::extract::Path(id): axum::extract::Path<Uuid>,
|
|
) -> Result<Json<serde_json::Value>, AppError> {
|
|
sqlx::query!("UPDATE reports SET flags = flags + 1 WHERE id = $1", id)
|
|
.execute(&db)
|
|
.await?;
|
|
Ok(Json(serde_json::json!({ "ok": true })))
|
|
}
|