From edb42717bc35dc2fac86d2c76f86a8179b75858b Mon Sep 17 00:00:00 2001
From: philipp <philipp@hofer.link>
Date: Wed, 6 Mar 2024 15:55:13 +0100
Subject: [PATCH] add schnupper management

---
 src/model/user.rs                         | 37 ++++++++++++++
 src/tera/admin/mod.rs                     |  2 +
 src/tera/admin/schnupper.rs               | 60 +++++++++++++++++++++++
 templates/admin/schnupper/index.html.tera | 19 +++++++
 templates/index.html.tera                 | 15 ++++++
 5 files changed, 133 insertions(+)
 create mode 100644 src/tera/admin/schnupper.rs
 create mode 100644 templates/admin/schnupper/index.html.tera

diff --git a/src/model/user.rs b/src/model/user.rs
index 5a8c3d8..d2b0cde 100644
--- a/src/model/user.rs
+++ b/src/model/user.rs
@@ -842,6 +842,43 @@ impl<'r> FromRequest<'r> for DonauLinzUser {
     }
 }
 
+#[derive(Debug, Serialize, Deserialize)]
+pub struct SchnupperBetreuerUser(pub(crate) User);
+
+impl From<SchnupperBetreuerUser> for User {
+    fn from(val: SchnupperBetreuerUser) -> Self {
+        val.0
+    }
+}
+
+impl Deref for SchnupperBetreuerUser {
+    type Target = User;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+#[async_trait]
+impl<'r> FromRequest<'r> for SchnupperBetreuerUser {
+    type Error = LoginError;
+
+    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
+        let db = req.rocket().state::<SqlitePool>().unwrap();
+        match User::from_request(req).await {
+            Outcome::Success(user) => {
+                if user.has_role(db, "schnupper-betreuer").await {
+                    Outcome::Success(SchnupperBetreuerUser(user))
+                } else {
+                    Outcome::Forward(Status::Forbidden)
+                }
+            }
+            Outcome::Error(f) => Outcome::Error(f),
+            Outcome::Forward(f) => Outcome::Forward(f),
+        }
+    }
+}
+
 #[derive(Debug, Serialize, Deserialize)]
 pub struct VorstandUser(pub(crate) User);
 
diff --git a/src/tera/admin/mod.rs b/src/tera/admin/mod.rs
index 04b23cd..7f3dae4 100644
--- a/src/tera/admin/mod.rs
+++ b/src/tera/admin/mod.rs
@@ -11,6 +11,7 @@ use crate::{
 pub mod boat;
 pub mod mail;
 pub mod planned_event;
+pub mod schnupper;
 pub mod user;
 
 #[get("/rss?<key>")]
@@ -74,6 +75,7 @@ async fn list(db: &State<SqlitePool>, _admin: AdminUser, list_form: Form<ListFor
 pub fn routes() -> Vec<Route> {
     let mut ret = Vec::new();
     ret.append(&mut user::routes());
+    ret.append(&mut schnupper::routes());
     ret.append(&mut boat::routes());
     ret.append(&mut mail::routes());
     ret.append(&mut planned_event::routes());
diff --git a/src/tera/admin/schnupper.rs b/src/tera/admin/schnupper.rs
new file mode 100644
index 0000000..41d536d
--- /dev/null
+++ b/src/tera/admin/schnupper.rs
@@ -0,0 +1,60 @@
+use crate::model::{
+    role::Role,
+    user::{SchnupperBetreuerUser, User, UserWithRoles},
+};
+use futures::future::join_all;
+use rocket::{
+    get,
+    http::Status,
+    request::{FlashMessage, FromRequest, Outcome},
+    routes, Request, Route, State,
+};
+use rocket_dyn_templates::{tera::Context, Template};
+use sqlx::SqlitePool;
+
+// Custom request guard to extract the Referer header
+struct Referer(String);
+
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for Referer {
+    type Error = ();
+
+    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+        match request.headers().get_one("Referer") {
+            Some(referer) => Outcome::Success(Referer(referer.to_string())),
+            None => Outcome::Error((Status::BadRequest, ())),
+        }
+    }
+}
+
+#[get("/schnupper")]
+async fn index(
+    db: &State<SqlitePool>,
+    user: SchnupperBetreuerUser,
+    flash: Option<FlashMessage<'_>>,
+) -> Template {
+    let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap();
+
+    let user_futures: Vec<_> = User::all_with_role(db, &schnupperant)
+        .await
+        .into_iter()
+        .map(|u| async move { UserWithRoles::from_user(u, db).await })
+        .collect();
+    let users: Vec<UserWithRoles> = join_all(user_futures).await;
+
+    let mut context = Context::new();
+    if let Some(msg) = flash {
+        context.insert("flash", &msg.into_inner());
+    }
+    context.insert("schnupperanten", &users);
+    context.insert(
+        "loggedin_user",
+        &UserWithRoles::from_user(user.into(), db).await,
+    );
+
+    Template::render("admin/schnupper/index", context.into_json())
+}
+
+pub fn routes() -> Vec<Route> {
+    routes![index]
+}
diff --git a/templates/admin/schnupper/index.html.tera b/templates/admin/schnupper/index.html.tera
new file mode 100644
index 0000000..a3e9d5a
--- /dev/null
+++ b/templates/admin/schnupper/index.html.tera
@@ -0,0 +1,19 @@
+{% import "includes/macros" as macros %}
+{% extends "base" %}
+{% block content %}
+    <div class="max-w-screen-lg w-full">
+        {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
+        <h1 class="h1">Schnupper Verwaltung</h1>
+        <div class="grid gap-3">
+            <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
+                 role="alert">
+                <h2 class="h2">Angemeldete Personen: {{ schnupperanten | length }}</h2>
+                <div class="text-sm p-3">
+                    <ol class="ms-2" style="list-style: number;">
+                        {% for user in schnupperanten %}<li class="py-1">{{ user.name }} ({{ user.mail }} | {{ user.notes }})</li>{% endfor %}
+                    </ol>
+                </div>
+            </div>
+        </div>
+    </div>
+{% endblock content %}
diff --git a/templates/index.html.tera b/templates/index.html.tera
index dec52f8..06c03e7 100644
--- a/templates/index.html.tera
+++ b/templates/index.html.tera
@@ -79,6 +79,21 @@
                 </div>
             </div>
         {% endif %}
+        {% if "schnupper-betreuer" in loggedin_user.roles %}
+            <div class="grid gap-3">
+                <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
+                     role="alert">
+                    <h2 class="h2">Schnupper-Betreuer</h2>
+                    <div class="text-sm p-3">
+                        <ul class="list-disc ms-2">
+                            <li class="py-1">
+                                <a href="/admin/schnupper" class="link-primary">Schnuppern</a>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        {% endif %}
         {% if "Vorstand" in loggedin_user.roles %}
             <div class="grid gap-3">
                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"