import express from "express"; import dotenv from "dotenv"; import axios from "axios"; import { formatData } from "./formatter.js"; dotenv.config(); const app = express(); app.use(express.json()); 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 }; }; 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" }); } } 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, () => { console.log("Server is running on port 8001"); });