feat: podpora high-availability a multi-replica nasazení
- Socket.io Redis adapter pro sdílený stav přes repliky - graceful shutdown serveru - WATCH/MULTI v updateData pro race-condition-safe aktualizace - lease mechanismus pro push reminder (zabrání duplicitnímu odesílání) - k8s/ manifesty pro testovací kind cluster - Dockerfile: opraven EXPOSE port na 3001 - .gitignore: ignorovány Claude pracovní soubory
This commit is contained in:
+46
-54
@@ -320,18 +320,17 @@ export async function initIfNeeded(date?: Date, slot?: MealSlot) {
|
||||
*/
|
||||
export async function removeChoices(login: string, trusted: boolean, locationKey: LunchChoice, date?: Date, slot?: MealSlot) {
|
||||
const selectedDay = getDataKey(date ?? getToday(), slot);
|
||||
let data = await getClientData(date, slot);
|
||||
validateTrusted(data, login, trusted);
|
||||
if (locationKey in data.choices) {
|
||||
if (data.choices[locationKey] && login in data.choices[locationKey]) {
|
||||
delete data.choices[locationKey][login]
|
||||
if (Object.keys(data.choices[locationKey]).length === 0) {
|
||||
delete data.choices[locationKey]
|
||||
}
|
||||
await storage.setData(selectedDay, data);
|
||||
// Validate trusted flag against current data before atomic update
|
||||
const snapshot = await getClientData(date, slot);
|
||||
validateTrusted(snapshot, login, trusted);
|
||||
return storage.updateData<ClientData>(selectedDay, (current) => {
|
||||
const data = current ?? getEmptyData(date);
|
||||
if (locationKey in data.choices && data.choices[locationKey] && login in data.choices[locationKey]) {
|
||||
delete data.choices[locationKey][login];
|
||||
if (Object.keys(data.choices[locationKey]).length === 0) delete data.choices[locationKey];
|
||||
}
|
||||
}
|
||||
return data;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,18 +346,16 @@ export async function removeChoices(login: string, trusted: boolean, locationKey
|
||||
*/
|
||||
export async function removeChoice(login: string, trusted: boolean, locationKey: LunchChoice, foodIndex: number, date?: Date, slot?: MealSlot) {
|
||||
const selectedDay = getDataKey(date ?? getToday(), slot);
|
||||
let data = await getClientData(date, slot);
|
||||
validateTrusted(data, login, trusted);
|
||||
if (locationKey in data.choices) {
|
||||
if (data.choices[locationKey] && login in data.choices[locationKey]) {
|
||||
const snapshot = await getClientData(date, slot);
|
||||
validateTrusted(snapshot, login, trusted);
|
||||
return storage.updateData<ClientData>(selectedDay, (current) => {
|
||||
const data = current ?? getEmptyData(date);
|
||||
if (locationKey in data.choices && data.choices[locationKey] && login in data.choices[locationKey]) {
|
||||
const index = data.choices[locationKey][login].selectedFoods?.indexOf(foodIndex);
|
||||
if (index != null && index > -1) {
|
||||
data.choices[locationKey][login].selectedFoods?.splice(index, 1);
|
||||
await storage.setData(selectedDay, data);
|
||||
}
|
||||
if (index != null && index > -1) data.choices[locationKey][login].selectedFoods?.splice(index, 1);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -513,18 +510,17 @@ async function validateFoodIndex(locationKey: LunchChoice, foodIndex?: number, d
|
||||
export async function updateNote(login: string, trusted: boolean, note?: string, date?: Date, slot?: MealSlot) {
|
||||
const usedDate = date ?? getToday();
|
||||
await initIfNeeded(usedDate, slot);
|
||||
let data = await getClientData(usedDate, slot);
|
||||
validateTrusted(data, login, trusted);
|
||||
const userEntry = data.choices != null && Object.entries(data.choices).find(entry => entry[1][login] != null);
|
||||
if (userEntry) {
|
||||
if (!note?.length) {
|
||||
delete userEntry[1][login].note;
|
||||
} else {
|
||||
userEntry[1][login].note = note;
|
||||
const snapshot = await getClientData(usedDate, slot);
|
||||
validateTrusted(snapshot, login, trusted);
|
||||
return storage.updateData<ClientData>(getDataKey(usedDate, slot), (current) => {
|
||||
const data = current ?? getEmptyData(date);
|
||||
const userEntry = data.choices != null && Object.entries(data.choices).find(entry => entry[1][login] != null);
|
||||
if (userEntry) {
|
||||
if (!note?.length) delete userEntry[1][login].note;
|
||||
else userEntry[1][login].note = note;
|
||||
}
|
||||
await storage.setData(getDataKey(usedDate, slot), data);
|
||||
}
|
||||
return data;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,21 +532,18 @@ export async function updateNote(login: string, trusted: boolean, note?: string,
|
||||
*/
|
||||
export async function updateDepartureTime(login: string, time?: string, date?: Date) {
|
||||
const usedDate = date ?? getToday();
|
||||
let clientData = await getClientData(usedDate);
|
||||
const found = Object.values(clientData.choices).find(location => login in location);
|
||||
// TODO validace, že se jedná o restauraci
|
||||
if (found) {
|
||||
if (!time?.length) {
|
||||
delete found[login].departureTime;
|
||||
} else {
|
||||
if (!Object.values<string>(DepartureTime).includes(time)) {
|
||||
throw new Error(`Neplatný čas odchodu ${time}`);
|
||||
}
|
||||
found[login].departureTime = time;
|
||||
}
|
||||
await storage.setData(getDataKey(usedDate), clientData);
|
||||
if (time?.length && !Object.values<string>(DepartureTime).includes(time)) {
|
||||
throw Error(`Neplatný čas odchodu ${time}`);
|
||||
}
|
||||
return clientData;
|
||||
return storage.updateData<ClientData>(getDataKey(usedDate), (current) => {
|
||||
const data = current ?? getEmptyData(date);
|
||||
const found = Object.values(data.choices).find(location => login in location);
|
||||
if (found) {
|
||||
if (!time?.length) delete found[login].departureTime;
|
||||
else found[login].departureTime = time;
|
||||
}
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,14 +554,13 @@ export async function updateDepartureTime(login: string, time?: string, date?: D
|
||||
*/
|
||||
export async function updateBuyer(login: string, slot?: MealSlot) {
|
||||
const usedDate = getToday();
|
||||
let clientData = await getClientData(usedDate, slot);
|
||||
const userEntry = clientData.choices?.['OBJEDNAVAM']?.[login];
|
||||
if (!userEntry) {
|
||||
throw new Error("Nelze nastavit objednatele pro uživatele s jinou volbou než \"Budu objednávat\"");
|
||||
}
|
||||
userEntry.isBuyer = !(userEntry.isBuyer || false);
|
||||
await storage.setData(getDataKey(usedDate, slot), clientData);
|
||||
return clientData;
|
||||
return storage.updateData<ClientData>(getDataKey(usedDate, slot), (current) => {
|
||||
const data = current ?? getEmptyData();
|
||||
const userEntry = data.choices?.['OBJEDNAVAM']?.[login];
|
||||
if (!userEntry) throw new Error("Nelze nastavit objednatele pro uživatele s jinou volbou než \"Budu objednávat\"");
|
||||
userEntry.isBuyer = !(userEntry.isBuyer || false);
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user