-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
11 changed files
with
365 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
use actix_identity::Identity; | ||
use actix_web::{web::{self}, Responder, Result}; | ||
use log::{debug, error}; | ||
use serde::{Deserialize, Serialize}; | ||
use surrealdb::sql::Thing; | ||
|
||
use crate::{ | ||
api::response::Response, api_wrapper::{ | ||
untis_client::UntisClient, utils::{FormattedLesson, TimetableParameter} | ||
}, models::{ | ||
model::{DBConnection, CRUD}, user_model::User | ||
}, prelude::Error, utils::time::{format_for_untis, get_this_friday, get_this_monday}, GlobalUntisData | ||
}; | ||
|
||
#[derive(Serialize)] | ||
struct TimetableResponse { | ||
lessons: Vec<FormattedLesson>, | ||
} | ||
|
||
#[derive(Deserialize)] | ||
pub struct TimetableQuery { | ||
from: Option<String>, | ||
until: Option<String>, | ||
class_name: Option<String> | ||
} | ||
|
||
#[derive(Deserialize)] | ||
pub struct ServiceWorkerQuery { | ||
jsessionid: Option<String>, | ||
} | ||
|
||
pub async fn get_timetable_serviceworker( | ||
id: Option<Identity>, query: web::Query<TimetableQuery>, data: web::Json<ServiceWorkerQuery>, untis_data: web::Data<GlobalUntisData>, | ||
db: web::Data<DBConnection>, | ||
) -> Result<impl Responder> { | ||
if id.is_none() { | ||
return Ok(web::Json(Response::new_error(403, "Not logged in".to_string()))); | ||
} | ||
|
||
let jsessionid = match data.jsessionid.clone() { | ||
Some(session_cookie) => session_cookie, | ||
None => return Ok(Response::new_error(403, "No JSESSIONID provided".to_string()).into()), | ||
}; | ||
|
||
let pot_user: Option<User> = User::get_from_id( | ||
db.clone(), | ||
match id.unwrap().id() { | ||
Ok(i) => { | ||
let split = i.split_once(':'); | ||
if split.is_some() { | ||
Thing::from(split.unwrap()) | ||
} else { | ||
error!("ID in session_cookie is wrong???"); | ||
return Ok(Response::new_error(500, "There was an error trying to get your id".to_string()).into()); | ||
} | ||
} | ||
Err(e) => { | ||
error!("Error getting Identity id\n{e}"); | ||
return Ok(Response::new_error(500, "There was an error trying to get your id".to_string()).into()); | ||
} | ||
}, | ||
) | ||
.await?; | ||
|
||
let user = match pot_user { | ||
Some(u) => u, | ||
None => { | ||
debug!("Deleted(?) User tried to log in with old session token"); | ||
return Ok(Response::new_error(404, "This account doesn't exist!".to_string()).into()); | ||
} | ||
}; | ||
|
||
if !user.verified { | ||
return Ok(Response::new_error( | ||
403, | ||
"Account not verified! Check your E-Mails for a verification link".to_string(), | ||
) | ||
.into()); | ||
} | ||
|
||
let untis = match UntisClient::unsafe_init( | ||
jsessionid, | ||
user.person_id.try_into().expect("the database to not store numbers bigger than u16"), | ||
5, | ||
"the-schedule".into(), | ||
untis_data.school.clone(), | ||
untis_data.subdomain.clone(), | ||
db, | ||
) | ||
.await | ||
{ | ||
Ok(u) => u, | ||
Err(e) => { | ||
if let Error::Reqwest(_) = e { | ||
return Ok(Response::new_error(400, "You done fucked up".into()).into()); | ||
} else if let Error::UntisError(body) = e { | ||
return Ok(Response::new_error(500, "Untis done fucked up ".to_string() + &body).into()); | ||
} | ||
else { | ||
return Ok(Response::new_error(500, "Some mysterious guy done fucked up".into()).into()); | ||
} | ||
} | ||
}; | ||
|
||
let from = match query.from.clone() { | ||
Some(from) => from, | ||
None => format_for_untis(get_this_monday()), | ||
}; | ||
let until = match query.until.clone() { | ||
Some(until) => until, | ||
None => format_for_untis(get_this_friday()), | ||
}; | ||
let class_name: Option<String> = query.class_name.clone(); | ||
let timetable = match untis.clone().get_timetable(TimetableParameter::default(untis, from, until), class_name).await { | ||
Ok(timetable) => timetable, | ||
Err(err) => { | ||
return Ok(Response::new_error(500, "Untis done fucked up ".to_string() + &err.to_string()).into()); | ||
} | ||
}; | ||
Ok(Response::new_success(TimetableResponse { lessons: timetable }).into()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/*@jsxImportSource preact */ | ||
|
||
import "@fontsource/inter"; | ||
import type { JSX } from "preact"; | ||
import { useEffect, useState } from "preact/hooks"; | ||
import "../styles/CookieBanner.scss"; | ||
|
||
export default function NotificationBanner(): JSX.Element | null { | ||
const [bannerContent, setBannerContent] = useState<JSX.Element | null>(null); | ||
const [showBanner, toggleBanner] = useState<boolean>(true); | ||
|
||
useEffect(() => { | ||
if (!document.cookie.match(/^(.*;)?\s*notification-consent\s*=\s*[^;]+(.*)?$/)) { | ||
setBannerContent( | ||
<div className="cookie-banner-container"> | ||
<p className="consent-message"> | ||
Wir können dir Benachrichtigungen über Entfälle senden. | ||
<br /> | ||
Du kannst sie jederzeit in den Einstellungen ändern | ||
</p> | ||
<button className="consent-button" onClick={setConsentCookie}> | ||
Benachrichtigungen erlauben | ||
</button> | ||
</div> | ||
); | ||
} else { | ||
setBannerContent(null); | ||
} | ||
}, [showBanner]); | ||
const setConsentCookie = () => { | ||
Notification.requestPermission().then((permission) => { | ||
if (permission === "granted") { | ||
if ("serviceWorker" in navigator) { | ||
navigator.serviceWorker.register("/notificationWorker.js", { scope: "/home" }).then((worker) => { | ||
console.log(worker.scope); | ||
}); | ||
} | ||
} | ||
}); | ||
document.cookie = `notification-consent=True; max-age=15552000;`; | ||
toggleBanner(false); | ||
}; | ||
return bannerContent; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
frontend/src/components/plan-components/settings-components/Notifications.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* @jsxImportSource preact */ | ||
|
||
import "../../../styles/SettingsElement.scss"; | ||
import type { JSX } from "preact"; | ||
import { useState } from "preact/hooks"; | ||
import { getLocalUntisCredentials } from "../../../api/untisAPI"; | ||
|
||
export default function Notifications(): JSX.Element { | ||
const [errorMessage, setErrorMessage] = useState(<p></p>); | ||
const unsubscribeNotification = async () => { | ||
try { | ||
const worker = await navigator.serviceWorker.getRegistration(); | ||
await worker?.unregister(); | ||
setErrorMessage(<p>Benachrichtigungen deaktiviert</p>); | ||
} catch {} | ||
}; | ||
const enableNotification = async () => { | ||
await Notification.requestPermission(); | ||
if (Notification.permission === "granted") { | ||
if ("serviceWorker" in navigator) { | ||
navigator.serviceWorker.register("/notificationWorker.js", { scope: "/home" }).then((worker) => { | ||
worker.active?.postMessage(getLocalUntisCredentials()); | ||
}); | ||
navigator.serviceWorker.ready.then((worker) => { | ||
worker.active?.postMessage(getLocalUntisCredentials()); | ||
}); | ||
} | ||
setErrorMessage(<p>Benachrichtigungen aktiviert</p>); | ||
} | ||
}; | ||
return ( | ||
<div class="page-content"> | ||
<div class="form-container"> | ||
<h2>Verwalte deine Benachrichtigungen</h2> | ||
<button onClick={enableNotification}>Benachrichtigungen aktivieren</button> | ||
<button onClick={unsubscribeNotification}>Benachrichtigungen deaktiveren</button> | ||
<div class="error-message">{errorMessage}</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { fetchJSessionId } from "../api/untisAPI"; | ||
import { getTimetableServiceWorker } from "../api/theBackend"; | ||
import type { TheScheduleObject } from "../api/main"; | ||
|
||
self.addEventListener("message", async (message) => { | ||
requestTimetable(message.data); | ||
}); | ||
|
||
async function requestTimetable(untisData: { username: string; password: string }, oldLessons?: TheScheduleObject[]) { | ||
let today = new Date().toISOString().slice(0, 10).replace(/-/g, ""); | ||
const JSessionId = (await fetchJSessionId(untisData.username, untisData.password)).JSessionId; | ||
let lessons = await getTimetableServiceWorker(today, today, JSessionId); | ||
if (oldLessons) { | ||
const changedLessons = compareLessons(oldLessons, lessons); | ||
console.log(changedLessons); | ||
handleChanges(changedLessons); | ||
} | ||
if (calculateTimeout() > 15 * 60 * 1000) { | ||
lessons = []; | ||
} | ||
console.log(calculateTimeout()); | ||
setTimeout(() => requestTimetable(untisData, lessons), calculateTimeout()); | ||
} | ||
function sendNotification(title: string, options: NotificationOptions) { | ||
console.log(title, options); | ||
if (Notification.permission === "granted") { | ||
self.registration.showNotification(title, options); | ||
} else { | ||
console.log("denied"); | ||
} | ||
} | ||
function compareLessons(oldLessons: TheScheduleObject[], newLessons: TheScheduleObject[]): TheScheduleObject[] { | ||
// Use .filter to find the lessons that have changed | ||
const changedLessons = newLessons.filter((newLesson, index) => { | ||
const oldLesson = oldLessons[index]; | ||
const oldSubstitution = oldLesson.substitution; | ||
const newSubstitution = newLesson.substitution; | ||
if (oldSubstitution && newSubstitution) { | ||
// Check if the substitution's cancelled status or teacher has changed | ||
console.log(oldSubstitution, newSubstitution); | ||
return ( | ||
oldSubstitution.cancelled !== newSubstitution.cancelled || | ||
oldSubstitution.teacher !== newSubstitution.teacher || | ||
oldSubstitution.room !== newSubstitution.room | ||
); | ||
} else if (!oldSubstitution && newSubstitution) { | ||
return true; | ||
} | ||
return false; | ||
}); | ||
return changedLessons; | ||
} | ||
function calculateTimeout(): number { | ||
const now = new Date(); | ||
const currentHour = now.getHours(); | ||
const startHour = 6; | ||
const endHour = 14; // 2 PM is 14:00 in 24-hour format | ||
|
||
if (currentHour >= startHour && currentHour <= endHour) { | ||
// If the current time is between 6:00 AM and 2:00 PM, return 15 minutes in milliseconds | ||
return 15 * 60 * 1000; | ||
} else { | ||
// Calculate the time remaining until the next 6:00 AM | ||
let nextSixAM = new Date(now); | ||
nextSixAM.setHours(startHour, 0, 0, 0); | ||
|
||
if (now > nextSixAM) { | ||
// If the current time is after 6:00 AM today, set the next 6:00 AM to tomorrow | ||
nextSixAM.setDate(nextSixAM.getDate() + 1); | ||
} | ||
|
||
const timeUntilNextSixAM = nextSixAM.getTime() - now.getTime(); | ||
return timeUntilNextSixAM; | ||
} | ||
} | ||
function handleChanges(changedLessons: TheScheduleObject[]) { | ||
changedLessons.forEach((lesson) => { | ||
if (lesson.substitution?.teacher == "---" || lesson.substitution?.cancelled) { | ||
if (lesson.length == 2) { | ||
sendNotification(`${lesson.start}. - ${lesson.start + 1}. Stunde entfällt`, { | ||
body: `${lesson.subject_short} bei ${lesson.teacher}` | ||
}); | ||
} else { | ||
sendNotification(`${lesson.start}. Stunde entfällt`, { | ||
body: `${lesson.subject_short} bei ${lesson.teacher}` | ||
}); | ||
} | ||
} else { | ||
if (lesson.length == 2) { | ||
sendNotification(`Änderungen in ${lesson.start}. - ${lesson.start + 1}. Stunde`, { | ||
body: `${lesson.subject_short} bei ${lesson.teacher}` | ||
}); | ||
} else { | ||
sendNotification(`Änderungen in ${lesson.start}. Stunde`, { | ||
body: `${lesson.subject_short} bei ${lesson.teacher}` | ||
}); | ||
} | ||
} | ||
}); | ||
} |