finished backend
This commit is contained in:
+189
-45
@@ -1,54 +1,198 @@
|
||||
import express from "express";
|
||||
|
||||
import dotenv from "dotenv";
|
||||
import axios from "axios";
|
||||
import { formatData } from "./formatter.js";
|
||||
dotenv.config();
|
||||
const app = express();
|
||||
|
||||
app.get("/api/today", (req, res) => {
|
||||
const userLink = req.query.userLink;
|
||||
app.use(express.json());
|
||||
|
||||
// Simulate fetching today's schedule
|
||||
const todaySchedule = {
|
||||
subjects: [
|
||||
{
|
||||
lesson_nr: "1.",
|
||||
name: "D G2",
|
||||
room: "255",
|
||||
teacher: "VanC",
|
||||
clock: "08:00-08:55",
|
||||
cancelled: false,
|
||||
},
|
||||
{
|
||||
lesson_nr: "2. & 3.",
|
||||
name: "M G2",
|
||||
room: "255",
|
||||
teacher: "ScLa",
|
||||
clock: "08:55-10:25",
|
||||
cancelled: false,
|
||||
},
|
||||
{
|
||||
lesson_nr: "4.",
|
||||
name: "E G4",
|
||||
room: "254",
|
||||
teacher: "RadF",
|
||||
clock: "10:55-11:40",
|
||||
cancelled: false,
|
||||
},
|
||||
{
|
||||
lesson_nr: "5.",
|
||||
name: "CH G2",
|
||||
room: "133",
|
||||
teacher: "LenT",
|
||||
clock: "11:40-12:25",
|
||||
cancelled: true,
|
||||
},
|
||||
],
|
||||
date: "15.04.2026",
|
||||
day: "Mittwoch",
|
||||
week: "16",
|
||||
student: "Max Mustermann",
|
||||
app.get("/api/get-timetable/", async (req, res) => {
|
||||
const username = req.query.username; // required
|
||||
const password = req.query.password; // required
|
||||
const school = req.query.school; // required
|
||||
|
||||
if (!username || !password || !school) {
|
||||
res.status(400).json({
|
||||
error: "Missing required query params: username, password, school",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let startDate = req.query.startDate; // optional - expected format: YYYYMMDD
|
||||
let endDate = req.query.endDate; // optional - expected format: YYYYMMDD
|
||||
|
||||
let sessionId = ""; // will be set after authentication by server response
|
||||
let personId = ""; // will be set after authentication by server response
|
||||
let personType = ""; // will be set after authentication by server response
|
||||
|
||||
// static function for checking or/and generating start and end date for the current week
|
||||
function setDates() {
|
||||
if (startDate && endDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const dayOfWeek = today.getDay(); // 0 (Sunday) to 6 (Saturday)
|
||||
|
||||
const addDays = (date, days) =>
|
||||
new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
|
||||
|
||||
// format dates to YYYYMMDD
|
||||
const formatDate = (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}${month}${day}`;
|
||||
};
|
||||
|
||||
// Desired behavior:
|
||||
// - Weekday (Mon-Thu): startDate = today, endDate = coming Friday (same week)
|
||||
// - Friday: startDate = today, endDate = coming Friday (next week)
|
||||
// - Weekend (Sat/Sun): startDate = coming Monday, endDate = coming Friday (of that week)
|
||||
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
|
||||
|
||||
let start;
|
||||
let end;
|
||||
|
||||
if (isWeekend) {
|
||||
const daysUntilMonday = dayOfWeek === 0 ? 1 : 2; // Sun->Mon:1, Sat->Mon:2
|
||||
start = addDays(today, daysUntilMonday);
|
||||
end = addDays(start, 4); // Monday + 4 days = Friday
|
||||
} else {
|
||||
start = today;
|
||||
const daysUntilFriday = 5 - dayOfWeek; // Mon(1)->4 ... Fri(5)->0
|
||||
end = addDays(today, daysUntilFriday === 0 ? 7 : daysUntilFriday);
|
||||
}
|
||||
|
||||
startDate = formatDate(start);
|
||||
endDate = formatDate(end);
|
||||
}
|
||||
|
||||
setDates();
|
||||
|
||||
const baseUrl = `https://${school}.webuntis.com/WebUntis/jsonrpc.do?school=${school}`;
|
||||
|
||||
const toJsonRpcError = (data) => {
|
||||
const err = data?.error;
|
||||
if (!err) return null;
|
||||
const code = err.code;
|
||||
const message = err.message || "Unknown WebUntis error";
|
||||
return { code, message };
|
||||
};
|
||||
|
||||
console.log(`Fetching schedule for user: ${userLink}`);
|
||||
res.json(todaySchedule);
|
||||
try {
|
||||
// 1) authenticate
|
||||
const authResponse = await axios.post(baseUrl, {
|
||||
id: "1",
|
||||
method: "authenticate",
|
||||
params: {
|
||||
user: username,
|
||||
password: password,
|
||||
client: "watch-untis",
|
||||
},
|
||||
jsonrpc: "2.0",
|
||||
});
|
||||
|
||||
const authErr = toJsonRpcError(authResponse.data);
|
||||
if (authErr) {
|
||||
if (authErr.message === "bad credentials") {
|
||||
res.status(401).json({ error: "Invalid username or password" });
|
||||
return;
|
||||
}
|
||||
if (authErr.code === -8504) {
|
||||
res.status(403).json({ error: "WebUntis Error: -8504" });
|
||||
return;
|
||||
}
|
||||
res.status(502).json({ error: authErr.message, code: authErr.code });
|
||||
return;
|
||||
}
|
||||
|
||||
sessionId = authResponse.data?.result?.sessionId;
|
||||
personId = authResponse.data?.result?.personId;
|
||||
personType = authResponse.data?.result?.personType;
|
||||
if (!sessionId || !personId) {
|
||||
res.status(502).json({
|
||||
error: "WebUntis auth succeeded but returned no sessionId/personId",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) get timetable (after we have sessionId, personId & personType from auth response)
|
||||
const timetableResponse = await axios.post(
|
||||
baseUrl,
|
||||
{
|
||||
id: "3",
|
||||
method: "getTimetable",
|
||||
params: {
|
||||
options: {
|
||||
element: {
|
||||
id: personId,
|
||||
type: personType,
|
||||
},
|
||||
startDate,
|
||||
endDate,
|
||||
teacherFields: ["id", "name", "longname"],
|
||||
subjectFields: ["id", "name", "longname"],
|
||||
roomFields: ["id", "name", "longname"],
|
||||
},
|
||||
},
|
||||
jsonrpc: "2.0",
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Cookie: `JSESSIONID=${sessionId}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const timetableErr = toJsonRpcError(timetableResponse.data);
|
||||
if (timetableErr) {
|
||||
res
|
||||
.status(502)
|
||||
.json({ error: timetableErr.message, code: timetableErr.code });
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) format the data once it successfully returned
|
||||
try {
|
||||
const formattedData = formatData(timetableResponse.data);
|
||||
res.json(formattedData);
|
||||
} catch (formatError) {
|
||||
console.error("formatData failed", formatError);
|
||||
res.status(500).json({ error: "Failed to format timetable data" });
|
||||
}
|
||||
|
||||
// respond to client
|
||||
res.json(timetableResponse.data?.result ?? null);
|
||||
return;
|
||||
} catch (error) {
|
||||
const status = error?.response?.status;
|
||||
const data = error?.response?.data;
|
||||
const jsonRpcErr = toJsonRpcError(data);
|
||||
|
||||
if (jsonRpcErr?.message === "bad credentials") {
|
||||
res.status(401).json({ error: "Invalid username or password" });
|
||||
return;
|
||||
}
|
||||
if (jsonRpcErr?.code === -8504) {
|
||||
res.status(403).json({ error: "WebUntis Error: -8504" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
res.status(502).json({
|
||||
error: "Failed to fetch timetable",
|
||||
upstreamStatus: status,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// error handling code
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).send("Something broke!");
|
||||
});
|
||||
|
||||
app.listen(8001, () => {
|
||||
|
||||
Reference in New Issue
Block a user