//! User-facing report submission endpoint. use axum::{ extract::State, response::IntoResponse, Form, }; use serde::Deserialize; use uuid::Uuid; use crate::{ auth::AuthUser, db::{self, ReportTargetType, ReportType}, error::{AppError, Result}, templates::AlertTemplate, AppState, }; /// Form input for submitting a report. #[derive(Debug, Deserialize)] pub struct ReportForm { pub target_type: String, pub target_id: String, pub report_type: String, #[serde(default)] pub reason: String, } /// Submit a report (logged-in users only). #[tracing::instrument(skip_all, name = "api::submit_report")] pub async fn submit_report( State(state): State, AuthUser(user): AuthUser, Form(form): Form, ) -> Result { user.check_not_sandbox()?; // Parse target_type let target_type: ReportTargetType = form.target_type.parse() .map_err(|_| AppError::validation("Invalid target type".to_string()))?; // Parse target_id let target_id: Uuid = form.target_id.parse() .map_err(|_| AppError::validation("Invalid target ID".to_string()))?; // Parse report_type let report_type: ReportType = form.report_type.parse() .map_err(|_| AppError::validation("Invalid report type".to_string()))?; // Require reason for "other" type let reason = form.reason.trim(); if report_type == ReportType::Other && reason.is_empty() { return Err(AppError::validation("Please provide details for 'Other' reports".to_string())); } // Prevent self-reporting match target_type { ReportTargetType::Project => { let project = db::projects::get_project_by_id(&state.db, target_id.into()) .await? .ok_or(AppError::NotFound)?; if project.user_id == user.id { return Err(AppError::validation("You cannot report your own project".to_string())); } } ReportTargetType::Item => { let item = db::items::get_item_by_id(&state.db, target_id.into()) .await? .ok_or(AppError::NotFound)?; let project = db::projects::get_project_by_id(&state.db, item.project_id) .await? .ok_or(AppError::NotFound)?; if project.user_id == user.id { return Err(AppError::validation("You cannot report your own item".to_string())); } } } // Rate limit: max 10 reports per user per day, enforced atomically (count + // insert under a per-reporter advisory lock) so concurrent submits can't // slip past the cap. let created = db::reports::create_report_within_daily_limit( &state.db, user.id, target_type, target_id, report_type, reason, 10, ).await?; if created.is_none() { return Err(AppError::validation("Report limit reached. Please try again later.".to_string())); } tracing::info!( reporter = %user.id, target_type = %target_type, target_id = %target_id, report_type = %report_type, "report submitted" ); Ok(AlertTemplate::new("success", "Report submitted. Thank you.")) }