Files
watch-untis/backend/server.js
T

197 lines
5.8 KiB
JavaScript

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");
});