forked from Ruderverein-Donau-Linz/rowt
		
	fix ci; nicer explanation; subpages
This commit is contained in:
		@@ -630,14 +630,14 @@ mod test {
 | 
			
		||||
    fn test_succ_create() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(User::create(&pool, "new-user-name".into()).await, true);
 | 
			
		||||
        User::create(&pool, "new-user-name".into()).await;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
    fn test_duplicate_name_create() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(User::create(&pool, "admin".into()).await, false);
 | 
			
		||||
        User::create(&pool, "admin".into()).await;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,13 @@
 | 
			
		||||
use rocket::{get, http::ContentType, routes, Route, State};
 | 
			
		||||
use rocket::{get, http::ContentType, request::FlashMessage, routes, Route, State};
 | 
			
		||||
use sqlx::SqlitePool;
 | 
			
		||||
 | 
			
		||||
use crate::model::{event::Event, personal::cal::get_personal_cal, user::User};
 | 
			
		||||
use crate::model::{
 | 
			
		||||
    event::Event,
 | 
			
		||||
    personal::cal::get_personal_cal,
 | 
			
		||||
    user::{User, UserWithDetails},
 | 
			
		||||
};
 | 
			
		||||
use rocket_dyn_templates::Template;
 | 
			
		||||
use tera::Context;
 | 
			
		||||
 | 
			
		||||
#[get("/cal")]
 | 
			
		||||
async fn cal(db: &State<SqlitePool>) -> (ContentType, String) {
 | 
			
		||||
@@ -9,6 +15,19 @@ async fn cal(db: &State<SqlitePool>) -> (ContentType, String) {
 | 
			
		||||
    (ContentType::Calendar, Event::get_ics_feed(db).await)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/kalender")]
 | 
			
		||||
async fn calinfo(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_>>) -> Template {
 | 
			
		||||
    let mut context = Context::new();
 | 
			
		||||
 | 
			
		||||
    if let Some(msg) = flash {
 | 
			
		||||
        context.insert("flash", &msg.into_inner());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await);
 | 
			
		||||
 | 
			
		||||
    Template::render("calinfo", context.into_json())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/cal/personal/<user_id>/<uuid>")]
 | 
			
		||||
async fn cal_registered(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
@@ -27,7 +46,7 @@ async fn cal_registered(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn routes() -> Vec<Route> {
 | 
			
		||||
    routes![cal, cal_registered]
 | 
			
		||||
    routes![cal, cal_registered, calinfo]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
 
 | 
			
		||||
@@ -11,17 +11,20 @@ use crate::model::{notification::Notification, user::User};
 | 
			
		||||
async fn mark_read(db: &State<SqlitePool>, user: User, notification_id: i64) -> Flash<Redirect> {
 | 
			
		||||
    let Some(notification) = Notification::find_by_id(db, notification_id).await else {
 | 
			
		||||
        return Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            Redirect::to("/notifications"),
 | 
			
		||||
            format!("Nachricht mit ID {notification_id} nicht gefunden."),
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if notification.user_id == user.id {
 | 
			
		||||
        notification.mark_read(db).await;
 | 
			
		||||
        Flash::success(Redirect::to("/"), "Nachricht als gelesen markiert")
 | 
			
		||||
        Flash::success(
 | 
			
		||||
            Redirect::to("/notifications"),
 | 
			
		||||
            "Nachricht als gelesen markiert",
 | 
			
		||||
        )
 | 
			
		||||
    } else {
 | 
			
		||||
        Flash::success(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            Redirect::to("/notifications"),
 | 
			
		||||
            "Du kannst fremde Nachrichten nicht als gelesen markieren.",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ use tera::Context;
 | 
			
		||||
use crate::{
 | 
			
		||||
    model::{
 | 
			
		||||
        log::Log,
 | 
			
		||||
        notification::Notification,
 | 
			
		||||
        tripdetails::TripDetails,
 | 
			
		||||
        triptype::TripType,
 | 
			
		||||
        user::{User, UserWithDetails},
 | 
			
		||||
@@ -47,9 +48,28 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_
 | 
			
		||||
    );
 | 
			
		||||
    context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await);
 | 
			
		||||
    context.insert("days", &days);
 | 
			
		||||
 | 
			
		||||
    Template::render("index", context.into_json())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/notifications")]
 | 
			
		||||
async fn notifications(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    user: User,
 | 
			
		||||
    flash: Option<FlashMessage<'_>>,
 | 
			
		||||
) -> Template {
 | 
			
		||||
    let mut context = Context::new();
 | 
			
		||||
 | 
			
		||||
    if let Some(msg) = flash {
 | 
			
		||||
        context.insert("flash", &msg.into_inner());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    context.insert("notifications", &Notification::for_user(db, &user).await);
 | 
			
		||||
    context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await);
 | 
			
		||||
 | 
			
		||||
    Template::render("notifications", context.into_json())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/join/<trip_details_id>?<user_note>")]
 | 
			
		||||
async fn join(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
@@ -215,7 +235,7 @@ async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Fla
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn routes() -> Vec<Route> {
 | 
			
		||||
    routes![index, join, remove, remove_guest]
 | 
			
		||||
    routes![index, join, remove, remove_guest, notifications]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								templates/calinfo.html.tera
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								templates/calinfo.html.tera
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
{% import "includes/macros" as macros %}
 | 
			
		||||
{% import "includes/forms/log" as log %}
 | 
			
		||||
{% extends "base" %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
                <div id="notification"
 | 
			
		||||
                     class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5 mb-5"
 | 
			
		||||
                     role="alert">
 | 
			
		||||
                    <h2 class="h2">Kalender</h2>
 | 
			
		||||
<div class="p-5">
 | 
			
		||||
                                    <p class="mt-3">
 | 
			
		||||
                                        Du möchtest immer up-to-date mit den Events und Ausfahrten bleiben? Wir bieten 2 verschiedene Arten von Kalender an:
 | 
			
		||||
                                    </p>
 | 
			
		||||
                                    <ol class="list-decimal ml-5 my-3">
 | 
			
		||||
                                        <li>
 | 
			
		||||
                                            <a class="underline break-all"
 | 
			
		||||
    href="/cal/personal/{{ loggedin_user.id }}/{{ loggedin_user.user_token }}"><strong>Alle Events und Ausfahrten</strong>, zu denen du dich angemeldet hast</a>
 | 
			
		||||
                                            <br />
 | 
			
		||||
                                            <small>Dieser Link enthält einen zufällig generierten Teil, damit nur du (und jene, denen du diesen Link weitergibst) Zugang zu diesen Daten hast.</small>
 | 
			
		||||
                                        </li>
 | 
			
		||||
                                        <li>
 | 
			
		||||
                                            <a class="break-all	underline" href="https://app.rudernlinz.at/cal"><strong>Alle Events</strong></a>
 | 
			
		||||
                                            <br />
 | 
			
		||||
                                            <small>Beachte, dass dieser Kalender keine Ausfahrten enthält, die von einzelnen Steuerpersonen augeschrieben werden. Dieser Kalender auf der Vereinswebsite verwendet werden, wo zB keine persönlichen Daten (Namen etc.) veröffentlicht werden soll.</small>
 | 
			
		||||
                                        </li>
 | 
			
		||||
                                    </ol>
 | 
			
		||||
                                    Du kannst die Kalender einfach in deinen Kalender als "externen Kalender" synchronisieren. Die genauen Schritte hängen von deiner verwendeten Software ab.
 | 
			
		||||
 | 
			
		||||
                </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
{% endblock content %}
 | 
			
		||||
@@ -82,7 +82,7 @@ function setChoiceByLabel(choicesInstance, label) {
 | 
			
		||||
 | 
			
		||||
            <div class="flex items-center">
 | 
			
		||||
                {% if loggedin_user.amount_unread_notifications > 0 %}
 | 
			
		||||
                    <a href="/#notification"
 | 
			
		||||
                    <a href="/notifications"
 | 
			
		||||
                       class="relative inline-flex items-end ms-2 me-3">
 | 
			
		||||
                        <svg height="20"
 | 
			
		||||
                             width="24"
 | 
			
		||||
@@ -110,11 +110,62 @@ function setChoiceByLabel(choicesInstance, label) {
 | 
			
		||||
                <div class="hidden">
 | 
			
		||||
                    <div id="mobile-menu">
 | 
			
		||||
                            <a href="/" class="block w-100 py-2 hover:text-primary-600">Geplante Ausfahrten</a>
 | 
			
		||||
                            <a href="/kalender" class="block w-100 py-2 hover:text-primary-600 border-t">Kalender</a>
 | 
			
		||||
                        {% if "admin" in loggedin_user.roles %}
 | 
			
		||||
                            <a href="/admin/user"
 | 
			
		||||
                               class="block w-100 py-2 hover:text-primary-600 border-t">Mitgliederverwaltung</a>
 | 
			
		||||
                               class="block w-100 py-2 hover:text-primary-600 border-t">Mitgliederverwaltung
 | 
			
		||||
                        <span class=""
 | 
			
		||||
                              onclick="event.preventDefault(); event.stopPropagation();this.nextElementSibling.showModal()">🛡️</span>
 | 
			
		||||
		<dialog
 | 
			
		||||
                        class="max-w-screen-sm dark:bg-primary-600 dark:text-white rounded-md"
 | 
			
		||||
                        onclick="this.close()">
 | 
			
		||||
                    <div onclick="event.stopPropagation();" class="p-3">
 | 
			
		||||
                        <button type="button"
 | 
			
		||||
                                onclick="this.parentNode.parentNode.close()"
 | 
			
		||||
                                title="Schließen"
 | 
			
		||||
                                class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
 | 
			
		||||
                            <svg class="inline h-5 w-5"
 | 
			
		||||
                                 width="16"
 | 
			
		||||
                                 height="16"
 | 
			
		||||
                                 fill="currentColor"
 | 
			
		||||
                                 viewBox="0 0 16 16">
 | 
			
		||||
                                <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
 | 
			
		||||
                            </svg>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <div class="mt-8">
 | 
			
		||||
                            <p>
 | 
			
		||||
			    Diesen Punkt sehen nur Mitglieder mit der Rolle <q>admin</q>
 | 
			
		||||
                            </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </dialog>
 | 
			
		||||
			       </a>
 | 
			
		||||
                            <a href="/admin/log"
 | 
			
		||||
                               class="block w-100 py-2 hover:text-primary-600 border-t">Log</a>
 | 
			
		||||
                               class="block w-100 py-2 hover:text-primary-600 border-t">Log
 | 
			
		||||
                        <span class=""
 | 
			
		||||
                              onclick="event.preventDefault(); event.stopPropagation();this.nextElementSibling.showModal()">🛡️</span>
 | 
			
		||||
		<dialog
 | 
			
		||||
                        class="max-w-screen-sm dark:bg-primary-600 dark:text-white rounded-md"
 | 
			
		||||
                        onclick="this.close()">
 | 
			
		||||
                    <div onclick="event.stopPropagation();" class="p-3">
 | 
			
		||||
                        <button type="button"
 | 
			
		||||
                                onclick="this.parentNode.parentNode.close()"
 | 
			
		||||
                                title="Schließen"
 | 
			
		||||
                                class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
 | 
			
		||||
                            <svg class="inline h-5 w-5"
 | 
			
		||||
                                 width="16"
 | 
			
		||||
                                 height="16"
 | 
			
		||||
                                 fill="currentColor"
 | 
			
		||||
                                 viewBox="0 0 16 16">
 | 
			
		||||
                                <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
 | 
			
		||||
                            </svg>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <div class="mt-8">
 | 
			
		||||
                            <p>
 | 
			
		||||
			    Diesen Punkt sehen nur Mitglieder mit der Rolle <q>admin</q>
 | 
			
		||||
                            </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </dialog>
 | 
			
		||||
			       </a>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <a href="/auth/logout"
 | 
			
		||||
                           class="block w-100 py-2 hover:text-primary-600 border-t">Ausloggen
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								templates/notifications.html.tera
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								templates/notifications.html.tera
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
{% import "includes/macros" as macros %}
 | 
			
		||||
{% import "includes/forms/log" as log %}
 | 
			
		||||
{% extends "base" %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
                <div id="notification"
 | 
			
		||||
                     class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5 mb-5"
 | 
			
		||||
                     role="alert">
 | 
			
		||||
                    <h2 class="h2">Nachrichten</h2>
 | 
			
		||||
            {% if notifications %}
 | 
			
		||||
                    {% if loggedin_user.amount_unread_notifications > 10 %}
 | 
			
		||||
                        <div class="text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 text-center pb-3 px-3">
 | 
			
		||||
                            Du hast viele ungelesene Benachrichtigungen. Um deine Oberfläche übersichtlich zu halten und wichtige Updates nicht zu verpassen, nimm dir bitte einen Moment Zeit sie zu überprüfen und als gelesen zu markieren (✓).
 | 
			
		||||
                        </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <div class="divide-y">
 | 
			
		||||
                        {% for notification in notifications %}
 | 
			
		||||
                            {% if not notification.read_at %}
 | 
			
		||||
                                <div class="relative flex justify-between items-center p-3">
 | 
			
		||||
                                    <div class="grow me-4">
 | 
			
		||||
                                        <small class="uppercase text-gray-600 dark:text-gray-100">
 | 
			
		||||
                                            <strong>{{ notification.category }}</strong> • {{ notification.created_at | date(format="%d.%m.%Y %H:%M",) }}
 | 
			
		||||
                                        </small>
 | 
			
		||||
                                        <div class="mt-1">{{ notification.message | safe }}</div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        {% if notification.link %}
 | 
			
		||||
                                            <a href="{{ notification.link }}" class="inline-block">
 | 
			
		||||
                                                <button class="btn btn-primary" type="button">🔗</button>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                        {% if not notification.read_at %}
 | 
			
		||||
                                            <a href="/notification/{{ notification.id }}/read" class="inline-block">
 | 
			
		||||
                                                <button class="btn btn-primary" type="button">
 | 
			
		||||
                                                    ✓
 | 
			
		||||
                                                    <span class="sr-only">Notification gelesen</span>
 | 
			
		||||
                                                </button>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <details class="py-3 border-t rounded-b-md">
 | 
			
		||||
                        <summary class="px-3 cursor-pointer">Vergangene Nachrichten (14 Tage)</summary>
 | 
			
		||||
                        <div class="divide-y text-sm">
 | 
			
		||||
                            {% for notification in notifications %}
 | 
			
		||||
                                {% if notification.read_at %}
 | 
			
		||||
                                    <div class="p-3 relative">
 | 
			
		||||
                                        <small class="uppercase text-gray-600 dark:text-gray-100">
 | 
			
		||||
                                            <strong>{{ notification.category }}</strong> • {{ notification.created_at | date(format="%d.%m.%Y %H:%M") }}
 | 
			
		||||
                                        </small>
 | 
			
		||||
                                        <div class="mt-1">{{ notification.message | safe }}</div>
 | 
			
		||||
                                        {% if notification.link %}
 | 
			
		||||
                                            <a href="{{ notification.link }}" class="inline-block">
 | 
			
		||||
                                                <button class="btn btn-primary" type="button">🔗</button>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            {% endfor %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </details>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
{% endblock content %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user