Use sync access for the temp JSON file database
All checks were successful
/ build (push) Successful in 1m37s
/ deploy (push) Has been skipped

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.
This commit is contained in:
Hornwitser 2025-09-20 23:04:16 +02:00
parent f9d188b2ba
commit 80cec71308
23 changed files with 138 additions and 138 deletions

View file

@ -11,11 +11,11 @@ import { broadcastEvent, cancelAccountStreams } from "~/server/streams";
export default defineEventHandler(async (event) => {
const serverSession = await requireServerSessionWithUser(event);
let users = await readUsers();
let users = readUsers();
// Expire sessions for this user
const expiredSessionIds = new Set<number>();
let sessions = await readSessions();
let sessions = readSessions();
const nowMs = Date.now();
for (const session of sessions) {
if (
@ -25,7 +25,7 @@ export default defineEventHandler(async (event) => {
) {
session.expiresAtMs = nowMs;
broadcastEvent({
id: await nextEventId(),
id: nextEventId(),
type: "session-expired",
sessionId: session.id,
});
@ -33,24 +33,24 @@ export default defineEventHandler(async (event) => {
}
}
cancelAccountStreams(serverSession.accountId);
await writeSessions(sessions);
writeSessions(sessions);
await deleteCookie(event, "session");
// Remove subscriptions for this user
let subscriptions = await readSubscriptions();
let subscriptions = readSubscriptions();
subscriptions = subscriptions.filter(
subscription => !expiredSessionIds.has(subscription.sessionId)
);
await writeSubscriptions(subscriptions);
writeSubscriptions(subscriptions);
// Remove the user
const account = users.find(user => user.id === serverSession.accountId)!;
const now = new Date(nowMs).toISOString();
account.deleted = true;
account.updatedAt = now;
await writeUsers(users);
writeUsers(users);
await broadcastEvent({
id: await nextEventId(),
id: nextEventId(),
type: "user-update",
data: {
id: account.id,

View file

@ -38,7 +38,7 @@ export default defineEventHandler(async (event) => {
}
}
const users = await readUsers();
const users = readUsers();
const account = users.find(user => user.id === session.accountId);
if (!account) {
throw Error("Account does not exist");
@ -70,7 +70,7 @@ export default defineEventHandler(async (event) => {
else
delete account.locale;
}
await writeUsers(users);
writeUsers(users);
// Update Schedule counts.
await updateScheduleInterestedCounts(users);

View file

@ -18,7 +18,7 @@ export default defineEventHandler(async (event): Promise<ApiSession> => {
const body = await readBody(event);
const name = body?.name;
const users = await readUsers();
const users = readUsers();
let user: ServerUser;
if (typeof name === "string") {
if (name === "") {
@ -36,7 +36,7 @@ export default defineEventHandler(async (event): Promise<ApiSession> => {
const firstUser = users.every(user => user.type === "anonymous");
user = {
id: await nextUserId(),
id: nextUserId(),
updatedAt: new Date().toISOString(),
type: firstUser ? "admin" : "regular",
name,
@ -44,7 +44,7 @@ export default defineEventHandler(async (event): Promise<ApiSession> => {
} else if (name === undefined) {
user = {
id: await nextUserId(),
id: nextUserId(),
updatedAt: new Date().toISOString(),
type: "anonymous",
};
@ -76,19 +76,19 @@ export default defineEventHandler(async (event): Promise<ApiSession> => {
});
}
authMethods.push({
id: await nextAuthenticationMethodId(),
id: nextAuthenticationMethodId(),
userId: user.id,
provider: session.authenticationProvider,
slug: session.authenticationSlug!,
name: session.authenticationName!,
})
await writeAuthenticationMethods(authMethods);
writeAuthenticationMethods(authMethods);
}
users.push(user);
await writeUsers(users);
writeUsers(users);
await broadcastEvent({
id: await nextEventId(),
id: nextEventId(),
type: "user-update",
data: user,
});

View file

@ -24,11 +24,11 @@ export default defineEventHandler(async (event) => {
});
}
const authMethods = await readAuthenticationMethods();
const authMethods = readAuthenticationMethods();
const method = authMethods.find(method => method.provider === "demo" && method.slug === slug);
let session;
if (method) {
const users = await readUsers();
const users = readUsers();
const account = users.find(user => !user.deleted && user.id === method.userId);
session = await setServerSession(event, account);
} else {

View file

@ -84,11 +84,11 @@ export default defineEventHandler(async (event): Promise<ApiSession> => {
}
const slug = String(data.authData.id);
const authMethods = await readAuthenticationMethods();
const authMethods = readAuthenticationMethods();
const method = authMethods.find(method => method.provider === "telegram" && method.slug === slug);
let session;
if (method) {
const users = await readUsers();
const users = readUsers();
const account = users.find(user => !user.deleted && user.id === method.userId);
session = await setServerSession(event, account);
} else {

View file

@ -8,7 +8,7 @@ import { cancelSessionStreams } from "~/server/streams";
export default defineEventHandler(async (event) => {
const session = await getServerSession(event, true);
if (session) {
const users = await readUsers();
const users = readUsers();
const account = users.find(user => user.id === session.accountId);
if (account?.type === "anonymous") {
throw createError({