Replace all async reads and writes to the JSON database with the sync reads and writes to prevent a data corruption race condition where two requests are processed at the same time and write to the same file, or one reads while the other writes causing read of partially written data.
48 lines
1.9 KiB
TypeScript
48 lines
1.9 KiB
TypeScript
/*
|
|
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
import { readNextSessionId, readNextUserId, readSessions, type ServerSession, type ServerUser, writeNextSessionId, writeNextUserId, writeSchedule, writeSessions, writeSubscriptions, writeUsers } from "~/server/database";
|
|
import type { ApiSchedule, ApiSubscription } from "~/shared/types/api";
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
await requireServerSessionWithAdmin(event);
|
|
const formData = await readMultipartFormData(event);
|
|
let snapshot: undefined | {
|
|
nextUserId: number,
|
|
users: ServerUser[],
|
|
nextSessionId: number,
|
|
sessions: ServerSession[],
|
|
subscriptions: ApiSubscription[],
|
|
schedule: ApiSchedule,
|
|
};
|
|
for (const part of formData ?? []) {
|
|
if (part.name === "snapshot") {
|
|
snapshot = JSON.parse(part.data.toString("utf-8"));
|
|
}
|
|
}
|
|
if (!snapshot) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Bad Request",
|
|
message: "snapshot missing."
|
|
});
|
|
}
|
|
|
|
const currentNextUserId = readNextUserId();
|
|
writeNextUserId(Math.max(currentNextUserId, snapshot.nextUserId));
|
|
writeUsers(snapshot.users);
|
|
const currentNextSessionId = readNextSessionId();
|
|
writeNextSessionId(Math.max(currentNextSessionId, snapshot.nextSessionId));
|
|
const currentSessions = new Map((readSessions()).map(session => [session.id, session]));
|
|
writeSessions(snapshot.sessions.filter(session => {
|
|
const current = currentSessions.get(session.id);
|
|
// Only keep sessions that match the account id in both sets to avoid
|
|
// resurrecting deleted sessions. This will still cause session cross
|
|
// pollution if a snapshot from another instance is loaded here.
|
|
return current?.accountId !== undefined && current.accountId === session.accountId;
|
|
}));
|
|
writeSubscriptions(snapshot.subscriptions);
|
|
writeSchedule(snapshot.schedule);
|
|
await sendRedirect(event, "/");
|
|
})
|