197 lines
5.8 KiB
JavaScript
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");
|
|
});
|