use crate::{language::language, page::Page, AppState}; use axum::{ extract::{Path, Query, State}, http::HeaderMap, response::{IntoResponse, Redirect, Response}, routing::{get, post}, Form, Router, }; use axum_extra::extract::{ cookie::{Cookie, Expiration}, CookieJar, PrivateCookieJar, }; use maud::{html, Markup}; use serde::Deserialize; use std::collections::HashMap; use time::OffsetDateTime; use uuid::Uuid; #[derive(Deserialize)] struct LoginForm { password: String, } #[derive(Deserialize)] struct CameraForm { uuid: String, name: String, desc: Option, } #[derive(Deserialize)] struct DeleteCameraForm { uuid: String, } #[derive(Deserialize)] struct EditCameraForm { name: String, desc: Option, } async fn login_page(cookies: CookieJar, headers: HeaderMap) -> Markup { let lang = language(&cookies, &headers); rust_i18n::set_locale(lang.to_locale()); Page::new(lang).content(html! { h1 { "Admin Login" } form method="POST" action="/admin/login" { fieldset { label for="password" { "Password:" } input type="password" name="password" id="password" required; input type="submit" value="Login"; } } }) } async fn login( State(state): State, private_cookies: PrivateCookieJar, Form(form): Form, ) -> Response { if form.password == state.admin_password { // Set secure admin session cookie let expiration_date = OffsetDateTime::now_utc() + time::Duration::days(30); let mut cookie = Cookie::new("admin_session", "authenticated"); cookie.set_expires(Expiration::DateTime(expiration_date)); cookie.set_http_only(true); cookie.set_secure(true); cookie.set_path("/"); let updated_cookies = private_cookies.add(cookie); (updated_cookies, Redirect::to("/protected")).into_response() } else { // Invalid password, redirect back to login Redirect::to("/admin/login").into_response() } } async fn logout(private_cookies: PrivateCookieJar) -> Response { // Remove admin session cookie let expired_cookie = Cookie::build(("admin_session", "")) .expires(Expiration::DateTime( OffsetDateTime::now_utc() - time::Duration::days(1), )) .http_only(true) .secure(true) .path("/") .build(); let updated_cookies = private_cookies.add(expired_cookie); (updated_cookies, Redirect::to("/")).into_response() } async fn protected_page( private_cookies: PrivateCookieJar, cookies: CookieJar, headers: HeaderMap, ) -> Response { // Check if admin is authenticated if private_cookies.get("admin_session").is_none() { return Redirect::to("/admin/login").into_response(); } let lang = language(&cookies, &headers); rust_i18n::set_locale(lang.to_locale()); let markup = Page::new(lang).content(html! { h1 { "Protected Admin Area" } p { "Welcome to the admin area! This is a protected route." } p { "Only authenticated administrators can access this page." } h2 { "Camera Management" } p { "Manage cameras in the system." } a href="/admin/cameras/add" { "Add Camera" } " | " a href="/admin/cameras" { "Manage Cameras" } form method="POST" action="/admin/logout" { input type="submit" value="Logout" class="secondary"; } }); markup.into_response() } async fn add_camera_page( private_cookies: PrivateCookieJar, cookies: CookieJar, headers: HeaderMap, Query(params): Query>, ) -> Response { // Check if admin is authenticated if private_cookies.get("admin_session").is_none() { return Redirect::to("/admin/login").into_response(); } let lang = language(&cookies, &headers); rust_i18n::set_locale(lang.to_locale()); // Get pre-filled UUID from query params let prefilled_uuid = params.get("uuid").unwrap_or(&String::new()).clone(); let markup = Page::new(lang).content(html! { h1 { "Add Camera" } @if !prefilled_uuid.is_empty() { p.text-muted { "Auto-detected missing camera with UUID: " strong { (prefilled_uuid) } } } form method="POST" action="/admin/cameras/add" { fieldset { label for="uuid" { "Camera UUID:" } input type="text" name="uuid" id="uuid" placeholder="e.g., 123e4567-e89b-12d3-a456-426614174000" value=(prefilled_uuid) required; label for="name" { "Camera Name:" } input type="text" name="name" id="name" placeholder="e.g., Front Entrance Camera" required; label for="desc" { "Description (optional):" } textarea name="desc" id="desc" placeholder="e.g., Camera monitoring the main entrance" {}; input type="submit" value="Add Camera"; } } p { a href="/protected" { "← Back to Admin Dashboard" } } }); markup.into_response() } async fn manage_cameras_page( State(state): State, private_cookies: PrivateCookieJar, cookies: CookieJar, headers: HeaderMap, ) -> Response { // Check if admin is authenticated if private_cookies.get("admin_session").is_none() { return Redirect::to("/admin/login").into_response(); } let lang = language(&cookies, &headers); rust_i18n::set_locale(lang.to_locale()); let cameras = state.backend.get_all_cameras().await; let markup = Page::new(lang).content(html! { h1 { "Manage Cameras" } p { "Total cameras: " strong { (cameras.len()) } } @if cameras.is_empty() { p.text-muted { "No cameras found in the system." } } @else { table { thead { tr { th { "UUID" } th { "Name" } th { "Description" } th { "Actions" } } } tbody { @for camera in &cameras { tr { td { code { (camera.uuid) } } td { (camera.name) } td { @if let Some(desc) = &camera.desc { (desc) } @else { em.text-muted { "No description" } } } td { a href=(format!("/admin/cameras/{}/edit", camera.uuid)) class="secondary" style="margin-right: 0.5rem;" { "Edit" } form method="POST" action="/admin/cameras/delete" style="display: inline;" { input type="hidden" name="uuid" value=(camera.uuid); input type="submit" value="Delete" class="secondary" onclick="return confirm('Are you sure you want to delete this camera? This will also remove all associated sightings.')"; } } } } } } } p { a href="/admin/cameras/add" { "Add New Camera" } " | " a href="/protected" { "← Back to Admin Dashboard" } } }); markup.into_response() } async fn add_camera( State(state): State, private_cookies: PrivateCookieJar, Form(form): Form, ) -> Response { // Check if admin is authenticated if private_cookies.get("admin_session").is_none() { return Redirect::to("/admin/login").into_response(); } // Parse UUID let uuid = match Uuid::parse_str(&form.uuid) { Ok(uuid) => uuid, Err(_) => return Redirect::to("/admin/cameras/add?error=invalid_uuid").into_response(), }; // Check if camera already exists if state.backend.get_camera(&uuid).await.is_some() { return Redirect::to("/admin/cameras/add?error=already_exists").into_response(); } // Create the camera let desc = if form .desc .as_ref() .map(|s| s.trim().is_empty()) .unwrap_or(true) { None } else { form.desc.as_deref() }; match state.backend.create_camera(&uuid, &form.name, desc).await { Ok(_) => Redirect::to("/admin/cameras?camera_added=1").into_response(), Err(_) => Redirect::to("/admin/cameras/add?error=creation_failed").into_response(), } } async fn edit_camera_page( State(state): State, private_cookies: PrivateCookieJar, cookies: CookieJar, headers: HeaderMap, Path(uuid_str): Path, ) -> Response { // Check if admin is authenticated if private_cookies.get("admin_session").is_none() { return Redirect::to("/admin/login").into_response(); } let lang = language(&cookies, &headers); rust_i18n::set_locale(lang.to_locale()); // Parse UUID let uuid = match Uuid::parse_str(&uuid_str) { Ok(uuid) => uuid, Err(_) => return Redirect::to("/admin/cameras?error=invalid_uuid").into_response(), }; // Get camera details let Some(camera) = state.backend.get_camera(&uuid).await else { return Redirect::to("/admin/cameras?error=not_found").into_response(); }; let markup = Page::new(lang).content(html! { h1 { "Edit Camera" } p.text-muted { "UUID: " code { (camera.uuid) } } form method="POST" action=(format!("/admin/cameras/{}/edit", camera.uuid)) { fieldset { label for="name" { "Camera Name:" } input type="text" name="name" id="name" value=(camera.name) required; label for="desc" { "Description (optional):" } textarea name="desc" id="desc" placeholder="e.g., Camera monitoring the main entrance" { @if let Some(desc) = &camera.desc { (desc) } } input type="submit" value="Update Camera"; } } p { a href="/admin/cameras" { "← Back to Camera List" } } }); markup.into_response() } async fn update_camera( State(state): State, private_cookies: PrivateCookieJar, Path(uuid_str): Path, Form(form): Form, ) -> Response { // Check if admin is authenticated if private_cookies.get("admin_session").is_none() { return Redirect::to("/admin/login").into_response(); } // Parse UUID let uuid = match Uuid::parse_str(&uuid_str) { Ok(uuid) => uuid, Err(_) => return Redirect::to("/admin/cameras?error=invalid_uuid").into_response(), }; // Process description let desc = if form .desc .as_ref() .map(|s| s.trim().is_empty()) .unwrap_or(true) { None } else { form.desc.as_deref() }; match state.backend.update_camera(&uuid, &form.name, desc).await { Ok(true) => Redirect::to("/admin/cameras?camera_updated=1").into_response(), Ok(false) => Redirect::to("/admin/cameras?error=not_found").into_response(), Err(_) => Redirect::to("/admin/cameras?error=update_failed").into_response(), } } async fn delete_camera( State(state): State, private_cookies: PrivateCookieJar, Form(form): Form, ) -> Response { // Check if admin is authenticated if private_cookies.get("admin_session").is_none() { return Redirect::to("/admin/login").into_response(); } // Parse UUID let uuid = match Uuid::parse_str(&form.uuid) { Ok(uuid) => uuid, Err(_) => return Redirect::to("/admin/cameras?error=invalid_uuid").into_response(), }; match state.backend.delete_camera(&uuid).await { Ok(true) => Redirect::to("/admin/cameras?camera_deleted=1").into_response(), Ok(false) => Redirect::to("/admin/cameras?error=not_found").into_response(), Err(_) => Redirect::to("/admin/cameras?error=deletion_failed").into_response(), } } pub fn routes() -> Router { Router::new() .route("/admin/login", get(login_page)) .route("/admin/login", post(login)) .route("/admin/logout", post(logout)) .route("/protected", get(protected_page)) .route("/admin/cameras", get(manage_cameras_page)) .route("/admin/cameras/add", get(add_camera_page)) .route("/admin/cameras/add", post(add_camera)) .route("/admin/cameras/{uuid}/edit", get(edit_camera_page)) .route("/admin/cameras/{uuid}/edit", post(update_camera)) .route("/admin/cameras/delete", post(delete_camera)) }