//! Shared route helpers — validation, permission checks, markdown rendering, enforcement. use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; use chrono::{DateTime, Duration, Utc}; use uuid::Uuid; use mt_core::types::{CommunityRole, CommunityState, ModAction}; use crate::auth; use crate::templates::*; use crate::AppState; // ============================================================================ // Rate limiting constants // ============================================================================ /// Per-user rate limit: max posts per window (complements per-IP tower-governor). pub(crate) const USER_POST_RATE_LIMIT: i64 = 15; pub(crate) const USER_POST_RATE_WINDOW_SECS: i64 = 60; // ============================================================================ // Markdown rendering // ============================================================================ /// Render markdown to HTML, stripping raw HTML events to prevent XSS. /// /// Strict preset: no images, no raw HTML, dangerous-scheme filtering. Use this /// for content from non-Fan+ users. Fan+ subscribers get image embeds via /// [`render_markdown_plus`]. pub(crate) fn render_markdown(input: &str) -> String { docengine::render_strict(input) } /// Render markdown to HTML with image embeds permitted. Otherwise identical to /// the strict preset — raw HTML is still stripped, dangerous schemes filtered, /// links get `nofollow`. Use for Fan+ subscriber content. pub(crate) fn render_markdown_plus(input: &str) -> String { docengine::Renderer::strict() .with_strip_images(false) .render(input) } /// Render markdown to HTML, resolving `@mentions` to profile links for valid community members. pub(crate) fn render_markdown_with_mentions( input: &str, community_slug: &str, valid_usernames: &std::collections::HashSet, allow_images: bool, ) -> String { let template = format!("/p/{community_slug}/u/{{username}}"); let resolved = docengine::resolve_mentions(input, valid_usernames, &template); if allow_images { render_markdown_plus(&resolved) } else { docengine::render_strict(&resolved) } } /// Reject submissions that contain image embeds. Used to give non-Fan+ users /// a clear error rather than silently stripping their image at render time. /// /// Only matches the markdown image syntax `![alt](url)`. Raw HTML `` / /// `