board-scheckbook #687
| @@ -2,7 +2,7 @@ | |||||||
| secret_key = "/NtVGizglEoyoxBLzsRDWTy4oAG1qDw4J4O+CWJSv+fypD7W9sam8hUY4j90EZsbZk8wEradS5zBoWtWKi3k8w==" | secret_key = "/NtVGizglEoyoxBLzsRDWTy4oAG1qDw4J4O+CWJSv+fypD7W9sam8hUY4j90EZsbZk8wEradS5zBoWtWKi3k8w==" | ||||||
| rss_key = "rss-key-for-ci" | rss_key = "rss-key-for-ci" | ||||||
| limits = { file = "10 MiB", data-form = "10 MiB"} | limits = { file = "10 MiB", data-form = "10 MiB"} | ||||||
| smtp_pw = "8kIjlLH79Ky6D3jQ" | smtp_pw = "8kIjlLH79Ky6D3j" | ||||||
| usage_log_path = "./usage.txt" | usage_log_path = "./usage.txt" | ||||||
| openweathermap_key = "c8dab8f91b5b815d76e9879cbaecd8d5" | openweathermap_key = "c8dab8f91b5b815d76e9879cbaecd8d5" | ||||||
| wordpress_key = "pw-to-allow-sending-notifications" | wordpress_key = "pw-to-allow-sending-notifications" | ||||||
|   | |||||||
| @@ -648,6 +648,14 @@ ORDER BY last_access DESC | |||||||
|             .is_ok() |             .is_ok() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub async fn create_with_mail(db: &SqlitePool, name: &str, mail: &str) -> bool { | ||||||
|  |         let name = name.trim(); | ||||||
|  |         sqlx::query!("INSERT INTO USER(name, mail) VALUES (?, ?)", name, mail) | ||||||
|  |             .execute(db) | ||||||
|  |             .await | ||||||
|  |             .is_ok() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) { |     pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) { | ||||||
|         let mut family_id = data.family_id; |         let mut family_id = data.family_id; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,13 +7,14 @@ use crate::{ | |||||||
|         logbook::Logbook, |         logbook::Logbook, | ||||||
|         role::Role, |         role::Role, | ||||||
|         user::{ |         user::{ | ||||||
|             AdminUser, AllowedToEditPaymentStatusUser, User, UserWithDetails, |             AdminUser, AllowedToEditPaymentStatusUser, SchnupperBetreuerUser, User, | ||||||
|             UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, |             UserWithDetails, UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|     tera::Config, |     tera::Config, | ||||||
| }; | }; | ||||||
| use futures::future::join_all; | use futures::future::join_all; | ||||||
|  | use lettre::Address; | ||||||
| use rocket::{ | use rocket::{ | ||||||
|     form::Form, |     form::Form, | ||||||
|     fs::TempFile, |     fs::TempFile, | ||||||
| @@ -355,6 +356,100 @@ async fn create( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(FromForm, Debug)] | ||||||
|  | struct UserAddScheckbuchForm<'r> { | ||||||
|  |     name: &'r str, | ||||||
|  |     mail: &'r str, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[post("/user/new/scheckbuch", data = "<data>")] | ||||||
|  | async fn create_scheckbuch( | ||||||
|  |     db: &State<SqlitePool>, | ||||||
|  |     data: Form<UserAddScheckbuchForm<'_>>, | ||||||
|  |     admin: VorstandUser, | ||||||
|  |     config: &State<Config>, | ||||||
|  | ) -> Flash<Redirect> { | ||||||
|  |     // 1. Check mail adress | ||||||
|  |     let mail = data.mail.trim(); | ||||||
|  |     if mail.parse::<Address>().is_err() { | ||||||
|  |         return Flash::error( | ||||||
|  |             Redirect::to("/admin/user/scheckbuch"), | ||||||
|  |             format!("Keine gültige Mailadresse"), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 2. Check name | ||||||
|  |     let name = data.name.trim(); | ||||||
|  |     if User::find_by_name(db, name).await.is_some() { | ||||||
|  |         return Flash::error( | ||||||
|  |             Redirect::to("/admin/user/scheckbuch"), | ||||||
|  |             format!( | ||||||
|  |                 "Kann kein Scheckbuch erstellen, der Name wird bereits von einem User verwendet" | ||||||
|  |             ), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 3. Create user | ||||||
|  |     User::create_with_mail(db, name, mail).await; | ||||||
|  |     let user = User::find_by_name(db, name).await.unwrap(); | ||||||
|  |  | ||||||
|  |     // 4. Add 'scheckbuch' role | ||||||
|  |     let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap(); | ||||||
|  |     user.add_role(db, &scheckbuch).await; | ||||||
|  |  | ||||||
|  |     // 4. Send welcome mail (+ notification) | ||||||
|  |     user.send_welcome_email(db, &config.smtp_pw).await.unwrap(); | ||||||
|  |  | ||||||
|  |     Log::create( | ||||||
|  |         db, | ||||||
|  |         format!("{} created new scheckbuch: {data:?}", admin.name), | ||||||
|  |     ) | ||||||
|  |     .await; | ||||||
|  |     Flash::success(Redirect::to("/admin/user/scheckbuch"), &format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {mail} verschickt.")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[get("/user/move/schnupperant/<id>/to/scheckbuch")] | ||||||
|  | async fn schnupper_to_scheckbuch( | ||||||
|  |     db: &State<SqlitePool>, | ||||||
|  |     id: i32, | ||||||
|  |     admin: SchnupperBetreuerUser, | ||||||
|  |     config: &State<Config>, | ||||||
|  | ) -> Flash<Redirect> { | ||||||
|  |     let Some(user) = User::find_by_id(db, id).await else { | ||||||
|  |         return Flash::error( | ||||||
|  |             Redirect::to("/admin/schnupper"), | ||||||
|  |             format!("user id not found"), | ||||||
|  |         ); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if !user.has_role(db, "schnupperant").await { | ||||||
|  |         return Flash::error( | ||||||
|  |             Redirect::to("/admin/schnupper"), | ||||||
|  |             format!("kein schnupperant..."), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap(); | ||||||
|  |     let paid = Role::find_by_name(db, "paid").await.unwrap(); | ||||||
|  |     user.remove_role(db, &schnupperant).await; | ||||||
|  |     user.remove_role(db, &paid).await; | ||||||
|  |  | ||||||
|  |     let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap(); | ||||||
|  |     user.add_role(db, &scheckbuch).await; | ||||||
|  |  | ||||||
|  |     user.send_welcome_email(db, &config.smtp_pw).await.unwrap(); | ||||||
|  |  | ||||||
|  |     Log::create( | ||||||
|  |         db, | ||||||
|  |         format!( | ||||||
|  |             "{} created new scheckbuch (from schnupperant): {}", | ||||||
|  |             admin.name, user.name | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  |     .await; | ||||||
|  |     Flash::success(Redirect::to("/admin/schnupper"), &format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {} verschickt.", user.mail.unwrap())) | ||||||
|  | } | ||||||
|  |  | ||||||
| pub fn routes() -> Vec<Route> { | pub fn routes() -> Vec<Route> { | ||||||
|     routes![ |     routes![ | ||||||
|         index, |         index, | ||||||
| @@ -362,6 +457,8 @@ pub fn routes() -> Vec<Route> { | |||||||
|         resetpw, |         resetpw, | ||||||
|         update, |         update, | ||||||
|         create, |         create, | ||||||
|  |         create_scheckbuch, | ||||||
|  |         schnupper_to_scheckbuch, | ||||||
|         delete, |         delete, | ||||||
|         fees, |         fees, | ||||||
|         fees_paid, |         fees_paid, | ||||||
|   | |||||||
| @@ -12,7 +12,12 @@ | |||||||
|                         {% for user in schnupperanten %} |                         {% for user in schnupperanten %} | ||||||
|                             <li class="py-1" |                             <li class="py-1" | ||||||
|                                 {% if "paid" in user.roles %}style="background-color: green;"{% endif %}> |                                 {% if "paid" in user.roles %}style="background-color: green;"{% endif %}> | ||||||
|                                 {{ user.name }} ({{ user.mail }} | {{ user.notes }}) |                                 {{ user.name }} ({{ user.mail }} | ||||||
|  |                                 {%- if user.notes %} | {{ user.notes }}{% endif -%} | ||||||
|  |                                 ) | ||||||
|  |                                 <a class="btn btn-primary" | ||||||
|  |                                    href="/admin/user/move/schnupperant/{{ user.id }}/to/scheckbuch" | ||||||
|  |                                    onclick="return confirm('Willst du wirklich ein Scheckbuch erstellen? Die Person erhält ein Mail mit allen Infos.')">Zu Scheckbuch umwandeln</a> | ||||||
|                             </li> |                             </li> | ||||||
|                         {% endfor %} |                         {% endfor %} | ||||||
|                     </ol> |                     </ol> | ||||||
|   | |||||||
| @@ -4,6 +4,34 @@ | |||||||
| {% block content %} | {% block content %} | ||||||
|     <div class="max-w-screen-lg w-full bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> |     <div class="max-w-screen-lg w-full bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> | ||||||
|         <h1 class="h1">Scheckbücher</h1> |         <h1 class="h1">Scheckbücher</h1> | ||||||
|  |         <form action="/admin/user/new/scheckbuch" | ||||||
|  |               method="post" | ||||||
|  |               class="mt-4 bg-primary-900 rounded-md text-white px-3 pb-3 pt-2 sm:flex items-end justify-between"> | ||||||
|  |             <div class="w-full"> | ||||||
|  |                 <h2 class="text-md font-bold mb-2 uppercase tracking-wide">Neues Scheckbuch hinzufügen</h2> | ||||||
|  |                 <div class="grid md:grid-cols-3"> | ||||||
|  |                     <div> | ||||||
|  |                         <label for="name" class="sr-only">Name</label> | ||||||
|  |                         <input type="text" | ||||||
|  |                                name="name" | ||||||
|  |                                class="relative block rounded-md border-0 py-1.5 px-2 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-primary-600 sm:text-sm sm:leading-6 mb-2 md:mb-0" | ||||||
|  |                                placeholder="Name" /> | ||||||
|  |                     </div> | ||||||
|  |                     <div> | ||||||
|  |                         <label for="name" class="sr-only">Mail</label> | ||||||
|  |                         <input type="mail" | ||||||
|  |                                name="mail" | ||||||
|  |                                class="relative block rounded-md border-0 py-1.5 px-2 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-primary-600 sm:text-sm sm:leading-6 mb-2 md:mb-0" | ||||||
|  |                                placeholder="Mail" /> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |             <div class="text-right"> | ||||||
|  |                 <input value="Hinzufügen" | ||||||
|  |                        type="submit" | ||||||
|  |                        class="w-28 mt-2 sm:mt-0 rounded-md bg-primary-500 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer" /> | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|         <!-- START filterBar --> |         <!-- START filterBar --> | ||||||
|         <div class="search-wrapper"> |         <div class="search-wrapper"> | ||||||
|             <label for="name" class="sr-only">Suche</label> |             <label for="name" class="sr-only">Suche</label> | ||||||
|   | |||||||
| @@ -54,9 +54,12 @@ | |||||||
|                    name="destination" |                    name="destination" | ||||||
|                    value="" |                    value="" | ||||||
|                    data-relation="distance_in_km" |                    data-relation="distance_in_km" | ||||||
| 		   autocomplete="off" /> |                    autocomplete="off" /> | ||||||
|             <datalist id="destinations"> |             <datalist id="destinations"> | ||||||
|                 {% for distance in distances %}<option value="{{ distance.destination  }}" distance="{{ distance.distance_in_km}}" />{% endfor %} |                 {% for distance in distances %} | ||||||
|  |                     <option value="{{ distance.destination }}" | ||||||
|  |                             distance="{{ distance.distance_in_km }}" /> | ||||||
|  |                 {% endfor %} | ||||||
|             </datalist> |             </datalist> | ||||||
|         </div> |         </div> | ||||||
|         <div class="relative col-span-2"> |         <div class="relative col-span-2"> | ||||||
|   | |||||||
| @@ -35,9 +35,7 @@ | |||||||
|                                     <div> |                                     <div> | ||||||
|                                         {% if notification.link %} |                                         {% if notification.link %} | ||||||
|                                             <a href="{{ notification.link }}" class="inline-block"> |                                             <a href="{{ notification.link }}" class="inline-block"> | ||||||
|                                                 <button class="btn btn-primary" type="button"> |                                                 <button class="btn btn-primary" type="button">🔗</button> | ||||||
|                                                     🔗 |  | ||||||
|                                                 </button> |  | ||||||
|                                             </a> |                                             </a> | ||||||
|                                         {% endif %} |                                         {% endif %} | ||||||
|                                         {% if not notification.read_at %} |                                         {% if not notification.read_at %} | ||||||
| @@ -65,9 +63,7 @@ | |||||||
|                                         <div class="mt-1">{{ notification.message | safe }}</div> |                                         <div class="mt-1">{{ notification.message | safe }}</div> | ||||||
|                                         {% if notification.link %} |                                         {% if notification.link %} | ||||||
|                                             <a href="{{ notification.link }}" class="inline-block"> |                                             <a href="{{ notification.link }}" class="inline-block"> | ||||||
|                                                 <button class="btn btn-primary" type="button"> |                                                 <button class="btn btn-primary" type="button">🔗</button> | ||||||
|                                                     🔗 |  | ||||||
|                                                 </button> |  | ||||||
|                                             </a> |                                             </a> | ||||||
|                                         {% endif %} |                                         {% endif %} | ||||||
|                                     </div> |                                     </div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user