Compare commits
4 Commits
b44d21d476
...
6d89858e3e
Author | SHA1 | Date | |
---|---|---|---|
6d89858e3e | |||
3460d69899 | |||
74c8ab9e39 | |||
ca9a7c5c23 |
@ -14,12 +14,12 @@ import './App.css';
|
||||
import { SelectSearchOption } from 'react-select-search';
|
||||
import { faCircleCheck, faTrashCan } from '@fortawesome/free-regular-svg-icons';
|
||||
import { useBank } from './context/bank';
|
||||
import { ClientData, Restaurants, Food, Order, Locations, PizzaOrder, PizzaDayState, FoodChoices, Menu } from './types';
|
||||
import { ClientData, Restaurants, Food, Order, Locations, PizzaOrder, PizzaDayState, FoodChoices, DayMenu } from './types';
|
||||
import Footer from './components/Footer';
|
||||
import { faChainBroken, faChevronLeft, faChevronRight, faGear, faSatelliteDish, faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||
import Loader from './components/Loader';
|
||||
import { getData, errorHandler, getQrUrl } from './api/Api';
|
||||
import { addChoice, removeChoices, removeChoice, changeDepartureTime } from './api/FoodApi';
|
||||
import { addChoice, removeChoices, removeChoice, changeDepartureTime, jdemeObed } from './api/FoodApi';
|
||||
|
||||
const EVENT_CONNECT = "connect"
|
||||
|
||||
@ -28,7 +28,7 @@ function App() {
|
||||
const bank = useBank();
|
||||
const [isConnected, setIsConnected] = useState<boolean>(false);
|
||||
const [data, setData] = useState<ClientData>();
|
||||
const [food, setFood] = useState<{ [key in Restaurants]?: Menu }>();
|
||||
const [food, setFood] = useState<{ [key in Restaurants]?: DayMenu }>();
|
||||
const [myOrder, setMyOrder] = useState<Order>();
|
||||
const [foodChoiceList, setFoodChoiceList] = useState<Food[]>();
|
||||
const [closed, setClosed] = useState<boolean>(false);
|
||||
@ -168,6 +168,12 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
const doJdemeObed = async () => {
|
||||
if (auth?.login) {
|
||||
await jdemeObed();
|
||||
}
|
||||
}
|
||||
|
||||
const doAddFoodChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (event.target.value && foodChoiceList?.length && choiceRef.current?.value) {
|
||||
const restaurantKey = choiceRef.current.value;
|
||||
@ -288,7 +294,7 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
const renderFoodTable = (name: string, menu: Menu) => {
|
||||
const renderFoodTable = (name: string, menu: DayMenu) => {
|
||||
let content;
|
||||
if (menu?.closed) {
|
||||
content = <h3>Zavřeno</h3>
|
||||
@ -352,13 +358,8 @@ function App() {
|
||||
<Alert variant={'primary'}>
|
||||
Poslední změny:
|
||||
<ul>
|
||||
<li>Oprava generování QR kódů pro Pizza day</li>
|
||||
<li>Serverová validace času odchodu</li>
|
||||
<li>Loader při zakládání Pizza day</li>
|
||||
<li>Možnost ručního zadání příplatku k Pizza day objednávkám</li>
|
||||
<li>Vylepšená detekce uzavření pro podniky Sladovnická a TechTower</li>
|
||||
<li>Úprava zvýraznění aktuálního dne</li>
|
||||
<li>Možnost hlasování o nových funkcích</li>
|
||||
<li>Parsování jídelních lístků na celý týden</li>
|
||||
<li>Oprava mizejícího Pizza day</li>
|
||||
</ul>
|
||||
</Alert>
|
||||
{dayIndex != null &&
|
||||
@ -475,10 +476,13 @@ function App() {
|
||||
<FontAwesomeIcon icon={faGear} className='fa-spin' /> Zjišťujeme dostupné pizzy
|
||||
</span>
|
||||
:
|
||||
<>
|
||||
<Button onClick={async () => {
|
||||
setLoadingPizzaDay(true);
|
||||
await createPizzaDay().then(() => setLoadingPizzaDay(false));
|
||||
}}>Založit Pizza day</Button>
|
||||
<Button onClick={doJdemeObed} style={{ marginLeft: "14px" }}>Jdeme na oběd !</Button>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -17,3 +17,7 @@ export const removeChoice = async (locationIndex: number, foodIndex: number, day
|
||||
export const changeDepartureTime = async (time: string, dayIndex?: number) => {
|
||||
return await api.post<any, any>(`${FOOD_API_PREFIX}/changeDepartureTime`, JSON.stringify({ time, dayIndex }));
|
||||
}
|
||||
|
||||
export const jdemeObed = async () => {
|
||||
return await api.post<any, any>(`${FOOD_API_PREFIX}/jdemeObed`, JSON.stringify({}));
|
||||
}
|
||||
|
@ -24,3 +24,7 @@
|
||||
# To je užitečné pro odesílání upozornění na různé servery Gotify s různými klíči API.
|
||||
# Struktura dat je ve formátu JSON a je uložena jako řetězec.
|
||||
# GOTIFY_SERVERS_AND_KEYS='[{"server":"https://notification.server.eu", "api_keys":["key1", "key2"]},{"server":"https://notification.server2.eu", "api_keys":["key3", "key4"]}]'
|
||||
|
||||
#NTFY_HOST = "http://192.168.0.113:80"
|
||||
#NTFY_USERNAME="username"
|
||||
#NTFY_PASSWD="password"
|
||||
|
6
server/babel.config.js
Normal file
6
server/babel.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
@ -7,13 +7,19 @@
|
||||
"scripts": {
|
||||
"start": "ts-node src/index.ts",
|
||||
"startReload": "nodemon src/index.ts",
|
||||
"build": "tsc -p ."
|
||||
"build": "tsc -p .",
|
||||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.0",
|
||||
"@babel/preset-env": "^7.22.20",
|
||||
"@babel/preset-typescript": "^7.23.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jsonwebtoken": "^9.0.2",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/request-promise": "^4.1.48",
|
||||
"babel-jest": "^29.7.0",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.2"
|
||||
|
@ -1124,16 +1124,16 @@ export const getTodayMock = () => {
|
||||
return '2023-05-31'; // středa
|
||||
}
|
||||
|
||||
export const getMenuSladovnickaMock = (date: Date) => {
|
||||
return MOCK_DATA['sladovnicka'][getDayOfWeekIndex(date)];
|
||||
export const getMenuSladovnickaMock = () => {
|
||||
return MOCK_DATA['sladovnicka'];
|
||||
}
|
||||
|
||||
export const getMenuUMotlikuMock = (date: Date) => {
|
||||
return MOCK_DATA['uMotliku'][getDayOfWeekIndex(date)];
|
||||
export const getMenuUMotlikuMock = () => {
|
||||
return MOCK_DATA['uMotliku'];
|
||||
}
|
||||
|
||||
export const getMenuTechTowerMock = (date: Date) => {
|
||||
return MOCK_DATA['techTower'][getDayOfWeekIndex(date)];
|
||||
export const getMenuTechTowerMock = () => {
|
||||
return MOCK_DATA['techTower'];
|
||||
}
|
||||
|
||||
export const getPizzaListMock = () => {
|
||||
|
@ -1,15 +1,18 @@
|
||||
/** Notifikace pro gotify*/
|
||||
import { GotifyServer, NotififaceInput, NotifikaceData } from '../../types';
|
||||
import { ClientData, GotifyServer, NotififaceInput, NotifikaceData } from '../../types';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import { getToday } from "./service";
|
||||
import { formatDate, getUsersByLocation } from "./utils";
|
||||
import getStorage from "./storage";
|
||||
|
||||
const storage = getStorage();
|
||||
const ENVIRONMENT = process.env.NODE_ENV || 'production'
|
||||
dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) });
|
||||
|
||||
const gotifyDataRaw = process.env.GOTIFY_SERVERS_AND_KEYS || "{}";
|
||||
const gotifyData: GotifyServer[] = JSON.parse(gotifyDataRaw);
|
||||
|
||||
export const gotifyCall = async (data: NotififaceInput, gotifyServers?: GotifyServer[]): Promise<any[]> => {
|
||||
if (!Array.isArray(gotifyServers)) {
|
||||
return []
|
||||
@ -51,15 +54,56 @@ export const gotifyCall = async (data: NotififaceInput, gotifyServers?: GotifySe
|
||||
return promises;
|
||||
};
|
||||
|
||||
/** Zavolá notifikace na všechny konfigurované způsoby notifikace, přetížení proměných na false pro jednotlivé způsoby je vypne*/
|
||||
export const callNotifikace = async ({ input, teams = true, gotify = true }: NotifikaceData) => {
|
||||
const notifications = [];
|
||||
|
||||
if (gotify) {
|
||||
const gotifyPromises = await gotifyCall(input, gotifyData);
|
||||
notifications.push(...gotifyPromises);
|
||||
export const ntfyCall = async (data: NotififaceInput) => {
|
||||
const url = process.env.NTFY_HOST
|
||||
const username = process.env.NTFY_USERNAME;
|
||||
const password = process.env.NTFY_PASSWD;
|
||||
if (!url) {
|
||||
console.log("NTFY_HOST není definován v env")
|
||||
return
|
||||
}
|
||||
if (!username) {
|
||||
console.log("NTFY_USERNAME není definován v env")
|
||||
return
|
||||
}
|
||||
if (!password) {
|
||||
console.log("NTFY_PASSWD není definován v env")
|
||||
return
|
||||
}
|
||||
const today = formatDate(getToday());
|
||||
let clientData: ClientData = await storage.getData(today);
|
||||
const userByCLocation = getUsersByLocation(clientData.choices, data.user)
|
||||
|
||||
const token = Buffer.from(`${username}:${password}`, 'utf8').toString('base64');
|
||||
const promises = userByCLocation.map(async user => {
|
||||
try {
|
||||
const response = await axios({
|
||||
url: `${url}/${user}`,
|
||||
method: 'POST',
|
||||
data: `${data.udalost} - spustil:${data.user}`,
|
||||
headers: {
|
||||
'Authorization': `Basic ${token}`,
|
||||
'Tag': 'meat_on_bone'
|
||||
}
|
||||
});
|
||||
console.log(response.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})
|
||||
|
||||
return promises;
|
||||
|
||||
}
|
||||
/** Zavolá notifikace na všechny konfigurované způsoby notifikace, přetížení proměných na false pro jednotlivé způsoby je vypne*/
|
||||
export const callNotifikace = async ({ input, teams = true, gotify = false, ntfy = true }: NotifikaceData) => {
|
||||
const notifications = [];
|
||||
if (ntfy) {
|
||||
const ntfyPromises = await ntfyCall(input);
|
||||
if (ntfyPromises) {
|
||||
notifications.push(...ntfyPromises);
|
||||
}
|
||||
}
|
||||
/* Zatím není
|
||||
if (teams) {
|
||||
notifications.push(teamsCall(input));
|
||||
@ -67,6 +111,12 @@ export const callNotifikace = async ({ input, teams = true, gotify = true }: Not
|
||||
|
||||
// Add more notifications as necessary
|
||||
|
||||
//gotify bych řekl, že už je deprecated
|
||||
if (gotify) {
|
||||
const gotifyPromises = await gotifyCall(input, gotifyData);
|
||||
notifications.push(...gotifyPromises);
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(notifications);
|
||||
return results;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { formatDate } from "./utils";
|
||||
import { callNotifikace } from "./notifikace";
|
||||
import { generateQr } from "./qr";
|
||||
import { ClientData, PizzaDayState, UdalostEnum, Pizza, PizzaSize, Order, PizzaOrder } from "../../types";
|
||||
import { ClientData, PizzaDayState, UdalostEnum, Pizza, PizzaSize, Order, PizzaOrder, DayData } from "../../types";
|
||||
import getStorage from "./storage";
|
||||
import { downloadPizzy } from "./chefie";
|
||||
import { getToday, initIfNeeded } from "./service";
|
||||
@ -15,7 +15,7 @@ const storage = getStorage();
|
||||
export async function getPizzaList(): Promise<Pizza[] | undefined> {
|
||||
await initIfNeeded();
|
||||
const today = formatDate(getToday());
|
||||
let clientData: ClientData = await storage.getData(today);
|
||||
let clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaList) {
|
||||
const mock = process.env.MOCK_DATA === 'true';
|
||||
clientData = await savePizzaList(await downloadPizzy(mock));
|
||||
@ -31,7 +31,7 @@ export async function getPizzaList(): Promise<Pizza[] | undefined> {
|
||||
export async function savePizzaList(pizzaList: Pizza[]): Promise<ClientData> {
|
||||
await initIfNeeded();
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
clientData.pizzaList = pizzaList;
|
||||
clientData.pizzaListLastUpdate = new Date();
|
||||
await storage.setData(today, clientData);
|
||||
@ -44,7 +44,7 @@ export async function savePizzaList(pizzaList: Pizza[]): Promise<ClientData> {
|
||||
export async function createPizzaDay(creator: string): Promise<ClientData> {
|
||||
await initIfNeeded();
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
if (clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den již existuje");
|
||||
}
|
||||
@ -61,7 +61,7 @@ export async function createPizzaDay(creator: string): Promise<ClientData> {
|
||||
*/
|
||||
export async function deletePizzaDay(login: string): Promise<ClientData> {
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
@ -82,7 +82,7 @@ export async function deletePizzaDay(login: string): Promise<ClientData> {
|
||||
*/
|
||||
export async function addPizzaOrder(login: string, pizza: Pizza, size: PizzaSize) {
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
@ -118,7 +118,7 @@ export async function addPizzaOrder(login: string, pizza: Pizza, size: PizzaSize
|
||||
*/
|
||||
export async function removePizzaOrder(login: string, pizzaOrder: PizzaOrder) {
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
@ -149,7 +149,7 @@ export async function removePizzaOrder(login: string, pizzaOrder: PizzaOrder) {
|
||||
*/
|
||||
export async function lockPizzaDay(login: string) {
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
@ -172,7 +172,7 @@ export async function lockPizzaDay(login: string) {
|
||||
*/
|
||||
export async function unlockPizzaDay(login: string) {
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
@ -195,7 +195,7 @@ export async function unlockPizzaDay(login: string) {
|
||||
*/
|
||||
export async function finishPizzaOrder(login: string) {
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
@ -220,7 +220,7 @@ export async function finishPizzaOrder(login: string) {
|
||||
*/
|
||||
export async function finishPizzaDelivery(login: string, bankAccount?: string, bankAccountHolder?: string) {
|
||||
const today = formatDate(getToday());
|
||||
const clientData: ClientData = await storage.getData(today);
|
||||
const clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
@ -255,7 +255,7 @@ export async function finishPizzaDelivery(login: string, bankAccount?: string, b
|
||||
*/
|
||||
export async function updatePizzaDayNote(login: string, note?: string) {
|
||||
const today = formatDate(getToday());
|
||||
let clientData: ClientData = await storage.getData(today);
|
||||
let clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
@ -282,7 +282,7 @@ export async function updatePizzaDayNote(login: string, note?: string) {
|
||||
*/
|
||||
export async function updatePizzaFee(login: string, targetLogin: string, text?: string, price?: number) {
|
||||
const today = formatDate(getToday());
|
||||
let clientData: ClientData = await storage.getData(today);
|
||||
let clientData: DayData = await storage.getData(today);
|
||||
if (!clientData.pizzaDay) {
|
||||
throw Error("Pizza day pro dnešní den neexistuje");
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import axios from "axios";
|
||||
import { load } from 'cheerio';
|
||||
import { Food } from "../../types";
|
||||
import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock } from "./mock";
|
||||
import { getDayOfWeekIndex } from "./utils";
|
||||
|
||||
// Fráze v názvech jídel, které naznačují že se jedná o polévku
|
||||
const SOUP_NAMES = ['polévka', 'česnečka', 'česnekový krém', 'cibulačka', 'vývar']
|
||||
@ -48,27 +47,29 @@ const getHtml = async (url: string): Promise<any> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Získá obědovou nabídku Sladovnické pro předané datum.
|
||||
* Získá obědovou nabídku Sladovnické pro jeden týden.
|
||||
*
|
||||
* @param date datum, pro které získat menu
|
||||
* @param firstDayOfWeek první den v týdnu, pro který získat menu
|
||||
* @param mock zda vrátit mock data
|
||||
* @returns seznam jídel pro dané datum
|
||||
* @returns seznam jídel pro daný týden
|
||||
*/
|
||||
export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean = false): Promise<Food[]> => {
|
||||
export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
|
||||
if (mock) {
|
||||
return getMenuSladovnickaMock(date);
|
||||
}
|
||||
const todayDayIndex = getDayOfWeekIndex(date);
|
||||
if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
|
||||
return [];
|
||||
return getMenuSladovnickaMock();
|
||||
}
|
||||
|
||||
const html = await getHtml(SLADOVNICKA_URL);
|
||||
const $ = load(html);
|
||||
|
||||
const list = $('ul.tab-links').children();
|
||||
const result: Food[][] = [];
|
||||
for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
|
||||
const currentDate = new Date(firstDayOfWeek);
|
||||
currentDate.setDate(firstDayOfWeek.getDate() + dayIndex);
|
||||
const searchedDayText = `${currentDate.getDate()}.${currentDate.getMonth() + 1}.${capitalize(DAYS_IN_WEEK[dayIndex])}`;
|
||||
// Najdeme index pro vstupní datum (např. při svátcích bude posunutý)
|
||||
// TODO validovat, že vstupní datum je v aktuálním týdnu
|
||||
// TODO tenhle způsob je zbytečně komplikovaný - stačilo by hledat rovnou v div.tab-content, protože každý den tam má datum taky (akorát je print-only)
|
||||
const list = $('ul.tab-links').children();
|
||||
const searchedDayText = `${date.getDate()}.${date.getMonth() + 1}.${capitalize(DAYS_IN_WEEK[todayDayIndex])}`;
|
||||
let index = undefined;
|
||||
list.each((i, dayRow) => {
|
||||
const rowText = $(dayRow).first().text().trim();
|
||||
@ -79,12 +80,13 @@ export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean
|
||||
})
|
||||
if (index === undefined) {
|
||||
// Pravděpodobně svátek, nebo je zavřeno
|
||||
return [{
|
||||
result[dayIndex] = [{
|
||||
amount: undefined,
|
||||
name: "Pro daný den nebyla nalezena denní nabídka",
|
||||
price: "",
|
||||
isSoup: false,
|
||||
}];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Dle dohledaného indexu najdeme správný tabpanel
|
||||
@ -109,13 +111,13 @@ export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean
|
||||
if (tables.length !== 2) {
|
||||
throw Error("Neočekávaný počet tabulek na řádce nalezeného dne: " + tables.length + ", ale očekávány byly 2");
|
||||
}
|
||||
const results: Food[] = [];
|
||||
const currentDayFood: Food[] = [];
|
||||
// Polévka - div -> table -> tbody -> tr -> 3x td
|
||||
const soupCells = $(tables.get(0)).children().first().children().first().children();
|
||||
if (soupCells.length !== 3) {
|
||||
throw Error("Neočekávaný počet buněk v tabulce polévky: " + soupCells.length + ", ale očekávány byly 3");
|
||||
}
|
||||
results.push({
|
||||
currentDayFood.push({
|
||||
amount: sanitizeText($(soupCells.get(0)).text()),
|
||||
name: sanitizeText($(soupCells.get(1)).text()),
|
||||
price: sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0')),
|
||||
@ -123,60 +125,61 @@ export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean
|
||||
});
|
||||
// Hlavní jídla - div -> table -> tbody -> 3x tr
|
||||
const mainCourseRows = $(tables.get(1)).children().first().children();
|
||||
// Záměrně zakomentováno - občas je ve Sladovnické jídel méně
|
||||
// if (mainCourseRows.length !== 3) {
|
||||
// throw Error("Neočekávaný počet řádek jídel: " + mainCourseRows.length + ", ale očekávány byly 3");
|
||||
// }
|
||||
mainCourseRows.each((i, foodRow) => {
|
||||
const foodCells = $(foodRow).children();
|
||||
if (foodCells.length !== 3) {
|
||||
throw Error("Neočekávaný počet buněk v řádku jídla: " + foodCells.length + ", ale očekávány byly 3");
|
||||
}
|
||||
results.push({
|
||||
currentDayFood.push({
|
||||
amount: sanitizeText($(foodCells.get(0)).text()),
|
||||
name: sanitizeText($(foodCells.get(1)).text()),
|
||||
price: sanitizeText($(foodCells.get(2)).text().replace(' ', '\xA0')),
|
||||
isSoup: false,
|
||||
});
|
||||
})
|
||||
return results;
|
||||
result[index] = currentDayFood;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Získá obědovou nabídku restaurace U Motlíků pro předané datum.
|
||||
* Získá obědovou nabídku restaurace U Motlíků pro jeden týden.
|
||||
*
|
||||
* @param date datum, pro které získat menu
|
||||
* @param firstDayOfWeek první den v týdnu, pro který získat menu
|
||||
* @param mock zda vrátit mock data
|
||||
* @returns seznam jídel pro dané datum
|
||||
*/
|
||||
export const getMenuUMotliku = async (date: Date = new Date(), mock: boolean = false): Promise<Food[]> => {
|
||||
export const getMenuUMotliku = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
|
||||
if (mock) {
|
||||
return getMenuUMotlikuMock(date);
|
||||
}
|
||||
const todayDayIndex = getDayOfWeekIndex(date);
|
||||
if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
|
||||
return [];
|
||||
return getMenuUMotlikuMock();
|
||||
}
|
||||
|
||||
const html = await getHtml(U_MOTLIKU_URL);
|
||||
const $ = load(html);
|
||||
|
||||
const table = $('table.table.table-hover.Xtable-striped').first();
|
||||
const body = table.children().first();
|
||||
const rows = body.children();
|
||||
const results: Food[] = [];
|
||||
|
||||
const result: Food[][] = [];
|
||||
for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
|
||||
if (!(dayIndex in result)) {
|
||||
result[dayIndex] = [];
|
||||
}
|
||||
let parsing = false;
|
||||
let isSoup = false;
|
||||
rows.each((i, row) => {
|
||||
const firstChild = $(row).children().get(0);
|
||||
if (firstChild?.name == 'th') {
|
||||
const childText = $(firstChild).text();
|
||||
if (capitalize(DAYS_IN_WEEK[todayDayIndex]) === childText) { // Našli jsme dnešek
|
||||
if (capitalize(DAYS_IN_WEEK[dayIndex]) === childText) {
|
||||
parsing = true;
|
||||
} else if (parsing) {
|
||||
// Narazili jsme na další den - konec parsování
|
||||
parsing = false;
|
||||
return;
|
||||
}
|
||||
} else if (parsing) { // Jsme aktuálně na dnešním dni
|
||||
} else if (parsing) {
|
||||
const children = $(row).children();
|
||||
if (children.length === 1) { // Nadpis "Polévka" nebo "Hlavní jídlo"
|
||||
const foodType = children.first().text();
|
||||
@ -194,7 +197,7 @@ export const getMenuUMotliku = async (date: Date = new Date(), mock: boolean = f
|
||||
const amount = sanitizeText($(children.get(0)).text());
|
||||
const name = sanitizeText($(children.get(1)).text());
|
||||
const price = sanitizeText($(children.get(2)).text()).replace(',-', '').replace(' ', '\xA0');
|
||||
results.push({
|
||||
result[dayIndex].push({
|
||||
amount,
|
||||
name,
|
||||
price,
|
||||
@ -203,26 +206,25 @@ export const getMenuUMotliku = async (date: Date = new Date(), mock: boolean = f
|
||||
}
|
||||
}
|
||||
})
|
||||
return results;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Získá obědovou nabídku TechTower pro předané datum.
|
||||
* Získá obědovou nabídku TechTower pro jeden týden.
|
||||
*
|
||||
* @param date datum, pro které získat menu
|
||||
* @param firstDayOfWeek první den v týdnu, pro který získat menu
|
||||
* @param mock zda vrátit mock data
|
||||
* @returns seznam jídel pro dané datum
|
||||
*/
|
||||
export const getMenuTechTower = async (date: Date = new Date(), mock: boolean = false) => {
|
||||
export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
|
||||
if (mock) {
|
||||
return getMenuTechTowerMock(date);
|
||||
}
|
||||
const todayDayIndex = getDayOfWeekIndex(date);
|
||||
if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
|
||||
return [];
|
||||
return getMenuTechTowerMock();
|
||||
}
|
||||
|
||||
const html = await getHtml(TECHTOWER_URL);
|
||||
const $ = load(html);
|
||||
|
||||
const fonts = $('font.wsw-41');
|
||||
let font = undefined;
|
||||
fonts.each((i, f) => {
|
||||
@ -236,11 +238,16 @@ export const getMenuTechTower = async (date: Date = new Date(), mock: boolean =
|
||||
// TODO validovat, že v textu nalezeného <font> je rozsah, do kterého spadá vstupní datum
|
||||
const siblings = $(font).parent().parent().siblings();
|
||||
let parsing = false;
|
||||
const results: Food[] = [];
|
||||
const result: Food[][] = [];
|
||||
// TODO toto je kvůli poslednímu "línému" refaktoru neoptimální, stačilo by to projít jedním cyklem
|
||||
for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
|
||||
if (!(dayIndex in result)) {
|
||||
result[dayIndex] = [];
|
||||
}
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
const text = $(siblings.get(i)).text().trim().replace('\t', '').replace('\n', ' ');
|
||||
if (DAYS_IN_WEEK.includes(text)) {
|
||||
if (text === DAYS_IN_WEEK[todayDayIndex]) {
|
||||
if (text === DAYS_IN_WEEK[dayIndex]) {
|
||||
// Našli jsme dnešní den, odtud začínáme parsovat jídla
|
||||
parsing = true;
|
||||
continue
|
||||
@ -261,7 +268,7 @@ export const getMenuTechTower = async (date: Date = new Date(), mock: boolean =
|
||||
price = `${split.slice(1)[0]}\xA0Kč`
|
||||
name = split[0]
|
||||
}
|
||||
results.push({
|
||||
result[dayIndex].push({
|
||||
amount: '-',
|
||||
name,
|
||||
price,
|
||||
@ -269,5 +276,6 @@ export const getMenuTechTower = async (date: Date = new Date(), mock: boolean =
|
||||
})
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
return result;
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import express from "express";
|
||||
import { getLogin, getTrusted } from "../auth";
|
||||
import { getDateForWeekIndex, addChoice, removeChoices, removeChoice, updateDepartureTime, getToday } from "../service";
|
||||
import { addChoice, addVolatileData, getDateForWeekIndex, getToday, removeChoice, removeChoices, updateDepartureTime } from "../service";
|
||||
import { getDayOfWeekIndex, parseToken } from "../utils";
|
||||
import { getWebsocket } from "../websocket";
|
||||
import { callNotifikace } from "../notifikace";
|
||||
import { UdalostEnum } from "../../../types";
|
||||
|
||||
/**
|
||||
* Ověří a vrátí index dne v týdnu z požadavku, za předpokladu, že byl předán, a je zároveň
|
||||
@ -44,7 +46,7 @@ router.post("/addChoice", async (req, res, next) => {
|
||||
}
|
||||
try {
|
||||
const data = await addChoice(login, trusted, req.body.locationIndex, req.body.foodIndex, date);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
return res.status(200).json(data);
|
||||
} catch (e: any) { next(e) }
|
||||
}
|
||||
@ -66,7 +68,7 @@ router.post("/removeChoices", async (req, res, next) => {
|
||||
}
|
||||
try {
|
||||
const data = await removeChoices(login, trusted, req.body.locationIndex, date);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json(data);
|
||||
} catch (e: any) { next(e) }
|
||||
});
|
||||
@ -86,7 +88,7 @@ router.post("/removeChoice", async (req, res, next) => {
|
||||
}
|
||||
try {
|
||||
const data = await removeChoice(login, trusted, req.body.locationIndex, req.body.foodIndex, date);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json(data);
|
||||
} catch (e: any) { next(e) }
|
||||
});
|
||||
@ -105,9 +107,16 @@ router.post("/changeDepartureTime", async (req, res, next) => {
|
||||
}
|
||||
try {
|
||||
const data = await updateDepartureTime(login, req.body?.time, date);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json(data);
|
||||
} catch (e: any) { next(e) }
|
||||
});
|
||||
|
||||
router.post("/jdemeObed", async (req, res, next) => {
|
||||
const login = getLogin(parseToken(req));
|
||||
try {
|
||||
await callNotifikace({ input: { user: login, udalost: UdalostEnum.JDEME_OBED }, gotify: false })
|
||||
} catch (e: any) { next(e) }
|
||||
});
|
||||
|
||||
export default router;
|
@ -3,6 +3,7 @@ import { getLogin } from "../auth";
|
||||
import { createPizzaDay, deletePizzaDay, getPizzaList, addPizzaOrder, removePizzaOrder, lockPizzaDay, unlockPizzaDay, finishPizzaOrder, finishPizzaDelivery, updatePizzaDayNote, updatePizzaFee } from "../pizza";
|
||||
import { parseToken } from "../utils";
|
||||
import { getWebsocket } from "../websocket";
|
||||
import { addVolatileData } from "../service";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -11,14 +12,14 @@ router.post("/create", async (req, res) => {
|
||||
const login = getLogin(parseToken(req));
|
||||
const data = await createPizzaDay(login);
|
||||
res.status(200).json(data);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
});
|
||||
|
||||
/** Smaže pizza day pro aktuální den, za předpokladu že existuje. */
|
||||
router.post("/delete", async (req, res) => {
|
||||
const login = getLogin(parseToken(req));
|
||||
const data = await deletePizzaDay(login);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
});
|
||||
|
||||
router.post("/add", async (req, res) => {
|
||||
@ -42,7 +43,7 @@ router.post("/add", async (req, res) => {
|
||||
throw Error("Neplatný index velikosti pizzy: " + pizzaSizeIndex);
|
||||
}
|
||||
const data = await addPizzaOrder(login, pizzy[pizzaIndex], pizzy[pizzaIndex].sizes[pizzaSizeIndex]);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json({});
|
||||
});
|
||||
|
||||
@ -52,35 +53,35 @@ router.post("/remove", async (req, res) => {
|
||||
throw Error("Nebyla předána objednávka");
|
||||
}
|
||||
const data = await removePizzaOrder(login, req.body?.pizzaOrder);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json({});
|
||||
});
|
||||
|
||||
router.post("/lock", async (req, res) => {
|
||||
const login = getLogin(parseToken(req));
|
||||
const data = await lockPizzaDay(login);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json({});
|
||||
});
|
||||
|
||||
router.post("/unlock", async (req, res) => {
|
||||
const login = getLogin(parseToken(req));
|
||||
const data = await unlockPizzaDay(login);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json({});
|
||||
});
|
||||
|
||||
router.post("/finishOrder", async (req, res) => {
|
||||
const login = getLogin(parseToken(req));
|
||||
const data = await finishPizzaOrder(login);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json({});
|
||||
});
|
||||
|
||||
router.post("/finishDelivery", async (req, res) => {
|
||||
const login = getLogin(parseToken(req));
|
||||
const data = await finishPizzaDelivery(login, req.body.bankAccount, req.body.bankAccountHolder);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json({});
|
||||
});
|
||||
|
||||
@ -90,7 +91,7 @@ router.post("/updatePizzaDayNote", async (req, res) => {
|
||||
throw Error("Poznámka může mít maximálně 100 znaků");
|
||||
}
|
||||
const data = await updatePizzaDayNote(login, req.body.note);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json(data);
|
||||
});
|
||||
|
||||
@ -101,7 +102,7 @@ router.post("/updatePizzaFee", async (req, res, next) => {
|
||||
}
|
||||
try {
|
||||
const data = await updatePizzaFee(login, req.body.login, req.body.text, req.body.price);
|
||||
getWebsocket().emit("message", data);
|
||||
getWebsocket().emit("message", await addVolatileData(data));
|
||||
res.status(200).json(data);
|
||||
} catch (e: any) { next(e) }
|
||||
});
|
||||
|
@ -2,7 +2,6 @@ import express from "express";
|
||||
import { getLogin } from "../auth";
|
||||
import { parseToken } from "../utils";
|
||||
import { getUserVotes, updateFeatureVote } from "../voting";
|
||||
import { getWebsocket } from "../websocket";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -19,7 +18,6 @@ router.post("/updateVote", async (req, res, next) => {
|
||||
}
|
||||
try {
|
||||
const data = await updateFeatureVote(login, req.body.option, req.body.active);
|
||||
getWebsocket().emit("message", data);
|
||||
res.status(200).json(data);
|
||||
} catch (e: any) { next(e) }
|
||||
});
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getHumanDate, getHumanTime, getIsWeekend } from "./utils";
|
||||
import { ClientData, Locations, Restaurants, Menu, DepartureTime } from "../../types";
|
||||
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getHumanDate, getHumanTime, getIsWeekend, getLastWorkDayOfWeek, getWeekNumber } from "./utils";
|
||||
import { ClientData, Locations, Restaurants, DayMenu, DepartureTime, DayData, WeekMenu } from "../../types";
|
||||
import getStorage from "./storage";
|
||||
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
|
||||
import { getTodayMock } from "./mock";
|
||||
|
||||
const storage = getStorage();
|
||||
const MENU_PREFIX = 'menu';
|
||||
|
||||
/** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */
|
||||
export function getToday(): Date {
|
||||
@ -34,81 +35,131 @@ function getEmptyData(date?: Date): ClientData {
|
||||
isWeekend: getIsWeekend(usedDate),
|
||||
weekIndex: getDayOfWeekIndex(usedDate),
|
||||
choices: {},
|
||||
departureTimes: Object.values(DepartureTime),
|
||||
departureTimes: Object.values(DepartureTime), // TODO tohle zmizí, bude se přidávat do dat dynamicky
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Přidá k datům "dopočítaná" data, která nejsou přímo uložena v databázi.
|
||||
*
|
||||
* @param data data z databáze
|
||||
* @returns obohacená data
|
||||
*/
|
||||
export async function addVolatileData(data: ClientData): Promise<ClientData> {
|
||||
data.todayWeekIndex = getDayOfWeekIndex(getToday());
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí veškerá klientská data pro předaný den, nebo aktuální den, pokud není předán.
|
||||
*/
|
||||
export async function getData(date?: Date): Promise<ClientData> {
|
||||
const dateString = formatDate(date ?? getToday());
|
||||
const data: ClientData = await storage.getData(dateString) || getEmptyData(date);
|
||||
data.todayWeekIndex = getDayOfWeekIndex(getToday());
|
||||
// Dotažení jídel, pokud je ještě nemáme
|
||||
if (!data.menus) {
|
||||
data.menus = {
|
||||
[Restaurants.SLADOVNICKA]: await getRestaurantMenu(Restaurants.SLADOVNICKA, date ?? getToday()),
|
||||
[Restaurants.UMOTLIKU]: await getRestaurantMenu(Restaurants.UMOTLIKU, date ?? getToday()),
|
||||
[Restaurants.TECHTOWER]: await getRestaurantMenu(Restaurants.TECHTOWER, date ?? getToday()),
|
||||
const targetDate = date ?? getToday();
|
||||
const dateString = formatDate(targetDate);
|
||||
const data: DayData = await storage.getData(dateString) || getEmptyData(date);
|
||||
let clientData: ClientData = { ...data };
|
||||
clientData.menus = {
|
||||
[Restaurants.SLADOVNICKA]: await getRestaurantMenu(Restaurants.SLADOVNICKA, date),
|
||||
[Restaurants.UMOTLIKU]: await getRestaurantMenu(Restaurants.UMOTLIKU, date),
|
||||
[Restaurants.TECHTOWER]: await getRestaurantMenu(Restaurants.TECHTOWER, date),
|
||||
}
|
||||
await storage.setData(dateString, data);
|
||||
}
|
||||
return data;
|
||||
clientData = await addVolatileData(clientData);
|
||||
return clientData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí klíč, pod kterým je uloženo menu pro předané datum.
|
||||
*
|
||||
* @param date datum
|
||||
* @returns databázový klíč
|
||||
*/
|
||||
function getMenuKey(date: Date) {
|
||||
const weekNumber = getWeekNumber(date);
|
||||
return `${MENU_PREFIX}_${date.getFullYear()}_${weekNumber}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí menu restaurací pro předané datum, pokud již existují.
|
||||
*
|
||||
* @param date datum
|
||||
* @returns menu restaurací pro předané datum
|
||||
*/
|
||||
async function getMenu(date: Date): Promise<WeekMenu | undefined> {
|
||||
return await storage.getData(getMenuKey(date));
|
||||
}
|
||||
|
||||
// TODO přesun do restaurants.ts
|
||||
/**
|
||||
* Vrátí menu dané restaurace pro předaný den. Pokud neexistuje, provede jeho stažení a uložení do DB.
|
||||
* Vrátí menu dané restaurace pro předaný den.
|
||||
* Pokud neexistuje, provede stažení menu pro příslušný týden a uložení do DB.
|
||||
*
|
||||
* @param restaurant restaurace
|
||||
* @param date datum
|
||||
* @param date datum, ke kterému získat menu
|
||||
* @param mock příznak, zda chceme pouze mock data
|
||||
*/
|
||||
export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): Promise<Menu> {
|
||||
await initIfNeeded(date);
|
||||
const selectedDay = formatDate(date ?? getToday());
|
||||
const clientData: ClientData = await storage.getData(selectedDay);
|
||||
if (!clientData.menus) {
|
||||
clientData.menus = {};
|
||||
storage.setData(selectedDay, clientData);
|
||||
export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): Promise<DayMenu> {
|
||||
const usedDate = date ?? getToday();
|
||||
const dayOfWeekIndex = getDayOfWeekIndex(usedDate);
|
||||
|
||||
let menus = await getMenu(usedDate);
|
||||
if (menus == null) {
|
||||
menus = [];
|
||||
}
|
||||
if (!clientData.menus[restaurant]) {
|
||||
clientData.menus[restaurant] = {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (menus[i] == null) {
|
||||
menus[i] = {};
|
||||
}
|
||||
if (menus[i][restaurant] == null) {
|
||||
menus[i][restaurant] = {
|
||||
lastUpdate: getHumanTime(new Date()),
|
||||
closed: false,
|
||||
food: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!menus[dayOfWeekIndex][restaurant]?.food?.length) {
|
||||
const firstDay = getFirstWorkDayOfWeek(usedDate);
|
||||
const mock = process.env.MOCK_DATA === 'true';
|
||||
switch (restaurant) {
|
||||
case Restaurants.SLADOVNICKA:
|
||||
const sladovnickaFood = await getMenuSladovnicka(date, mock);
|
||||
clientData.menus[restaurant]!.food = sladovnickaFood;
|
||||
const sladovnickaFood = await getMenuSladovnicka(firstDay, mock);
|
||||
for (let i = 0; i < sladovnickaFood.length; i++) {
|
||||
menus[i][restaurant]!.food = sladovnickaFood[i];
|
||||
// Velice chatrný a nespolehlivý způsob detekce uzavření...
|
||||
if (sladovnickaFood.length === 1 && sladovnickaFood[0].name.toLowerCase() === 'pro daný den nebyla nalezena denní nabídka') {
|
||||
clientData.menus[restaurant]!.closed = true;
|
||||
if (sladovnickaFood[i].length === 1 && sladovnickaFood[i][0].name.toLowerCase() === 'pro daný den nebyla nalezena denní nabídka') {
|
||||
menus[i][restaurant]!.closed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Restaurants.UMOTLIKU:
|
||||
const uMotlikuFood = await getMenuUMotliku(date, mock);
|
||||
clientData.menus[restaurant]!.food = uMotlikuFood;
|
||||
if (uMotlikuFood.length === 1 && uMotlikuFood[0].name.toLowerCase() === 'zavřeno') {
|
||||
clientData.menus[restaurant]!.closed = true;
|
||||
const uMotlikuFood = await getMenuUMotliku(firstDay, mock);
|
||||
for (let i = 0; i < uMotlikuFood.length; i++) {
|
||||
menus[i][restaurant]!.food = uMotlikuFood[i];
|
||||
if (uMotlikuFood[i].length === 1 && uMotlikuFood[i][0].name.toLowerCase() === 'zavřeno') {
|
||||
menus[i][restaurant]!.closed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Restaurants.TECHTOWER:
|
||||
const techTowerFood = await getMenuTechTower(date, mock);
|
||||
clientData.menus[restaurant]!.food = techTowerFood;
|
||||
if (techTowerFood.length === 1 && techTowerFood[0].name.toLowerCase() === 'svátek') {
|
||||
clientData.menus[restaurant]!.closed = true;
|
||||
const techTowerFood = await getMenuTechTower(firstDay, mock);
|
||||
for (let i = 0; i < techTowerFood.length; i++) {
|
||||
menus[i][restaurant]!.food = techTowerFood[i];
|
||||
if (techTowerFood[i].length === 1 && techTowerFood[i][0].name.toLowerCase() === 'svátek') {
|
||||
menus[i][restaurant]!.closed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
storage.setData(selectedDay, clientData);
|
||||
await storage.setData(getMenuKey(usedDate), menus);
|
||||
}
|
||||
return clientData.menus[restaurant]!;
|
||||
return menus[dayOfWeekIndex][restaurant]!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicializuje výchozí data pro předané datum, nebo dnešek, pokud není datum předáno.
|
||||
*
|
||||
* @param date datum
|
||||
*/
|
||||
export async function initIfNeeded(date?: Date) {
|
||||
const usedDate = formatDate(date ?? getToday());
|
||||
const hasData = await storage.hasData(usedDate);
|
||||
@ -128,7 +179,7 @@ export async function initIfNeeded(date?: Date) {
|
||||
*/
|
||||
export async function removeChoices(login: string, trusted: boolean, location: Locations, date?: Date) {
|
||||
const selectedDay = formatDate(date ?? getToday());
|
||||
let data: ClientData = await storage.getData(selectedDay);
|
||||
let data: DayData = await storage.getData(selectedDay);
|
||||
validateTrusted(data, login, trusted);
|
||||
if (location in data.choices) {
|
||||
if (login in data.choices[location]) {
|
||||
@ -155,7 +206,7 @@ export async function removeChoices(login: string, trusted: boolean, location: L
|
||||
*/
|
||||
export async function removeChoice(login: string, trusted: boolean, location: Locations, foodIndex: number, date?: Date) {
|
||||
const selectedDay = formatDate(date ?? getToday());
|
||||
let data: ClientData = await storage.getData(selectedDay);
|
||||
let data: DayData = await storage.getData(selectedDay);
|
||||
validateTrusted(data, login, trusted);
|
||||
if (location in data.choices) {
|
||||
if (login in data.choices[location]) {
|
||||
@ -175,7 +226,7 @@ export async function removeChoice(login: string, trusted: boolean, location: Lo
|
||||
* @param login login uživatele
|
||||
*/
|
||||
async function removeChoiceIfPresent(login: string, date: string) {
|
||||
let data: ClientData = await storage.getData(date);
|
||||
let data: DayData = await storage.getData(date);
|
||||
for (const key of Object.keys(data.choices)) {
|
||||
if (login in data.choices[key]) {
|
||||
delete data.choices[key][login];
|
||||
@ -222,9 +273,10 @@ function validateTrusted(data: ClientData, login: string, trusted: boolean) {
|
||||
* @returns aktuální data
|
||||
*/
|
||||
export async function addChoice(login: string, trusted: boolean, location: Locations, foodIndex?: number, date?: Date) {
|
||||
await initIfNeeded();
|
||||
const selectedDate = formatDate(date ?? getToday());
|
||||
let data: ClientData = await storage.getData(selectedDate);
|
||||
const usedDate = date ?? getToday();
|
||||
await initIfNeeded(usedDate);
|
||||
const selectedDate = formatDate(usedDate);
|
||||
let data: DayData = await storage.getData(selectedDate);
|
||||
validateTrusted(data, login, trusted);
|
||||
// Pokud měníme pouze lokaci, mažeme případné předchozí
|
||||
if (foodIndex == null) {
|
||||
@ -255,7 +307,7 @@ export async function addChoice(login: string, trusted: boolean, location: Locat
|
||||
*/
|
||||
export async function updateDepartureTime(login: string, time?: string, date?: Date) {
|
||||
const selectedDate = formatDate(date ?? getToday());
|
||||
let clientData: ClientData = await storage.getData(selectedDate);
|
||||
let clientData: DayData = await storage.getData(selectedDate);
|
||||
const found = Object.values(clientData.choices).find(location => login in location);
|
||||
// TODO validace, že se jedná o restauraci
|
||||
if (found) {
|
||||
|
159
server/src/tests/dates.test.ts
Normal file
159
server/src/tests/dates.test.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getLastWorkDayOfWeek, getWeekNumber } from "../utils";
|
||||
|
||||
test('získání indexu dne v týdnu', () => {
|
||||
let date = new Date("2023-10-01");
|
||||
expect(getDayOfWeekIndex(date)).toBe(6);
|
||||
date = new Date("2023-10-02");
|
||||
expect(getDayOfWeekIndex(date)).toBe(0);
|
||||
date = new Date("2023-10-03");
|
||||
expect(getDayOfWeekIndex(date)).toBe(1);
|
||||
date = new Date("2023-10-04");
|
||||
expect(getDayOfWeekIndex(date)).toBe(2);
|
||||
date = new Date("2023-10-05");
|
||||
expect(getDayOfWeekIndex(date)).toBe(3);
|
||||
date = new Date("2023-10-06");
|
||||
expect(getDayOfWeekIndex(date)).toBe(4);
|
||||
date = new Date("2023-10-07");
|
||||
expect(getDayOfWeekIndex(date)).toBe(5);
|
||||
date = new Date("2023-10-08");
|
||||
expect(getDayOfWeekIndex(date)).toBe(6);
|
||||
date = new Date("2023-10-09");
|
||||
expect(getDayOfWeekIndex(date)).toBe(0);
|
||||
});
|
||||
|
||||
test('získání data prvního/posledního dne v týdnu', () => {
|
||||
let date = new Date("2023-10-02");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-10-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-10-06");
|
||||
date = new Date("2023-10-03");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-10-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-10-06");
|
||||
date = new Date("2023-10-04");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-10-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-10-06");
|
||||
date = new Date("2023-10-05");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-10-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-10-06");
|
||||
date = new Date("2023-10-06");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-10-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-10-06");
|
||||
date = new Date("2023-10-07");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-10-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-10-06");
|
||||
date = new Date("2023-10-08");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-10-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-10-06");
|
||||
|
||||
date = new Date("2023-01-01");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2022-12-26");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2022-12-30");
|
||||
date = new Date("2023-01-02");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-01-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-01-06");
|
||||
date = new Date("2023-01-03");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-01-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-01-06");
|
||||
date = new Date("2023-01-04");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-01-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-01-06");
|
||||
date = new Date("2023-01-05");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-01-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-01-06");
|
||||
date = new Date("2023-01-06");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-01-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-01-06");
|
||||
date = new Date("2023-01-07");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-01-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-01-06");
|
||||
date = new Date("2023-01-08");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-01-02");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-01-06");
|
||||
|
||||
date = new Date("2023-12-25");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-12-25");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-12-29");
|
||||
date = new Date("2023-12-26");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-12-25");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-12-29");
|
||||
date = new Date("2023-12-27");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-12-25");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-12-29");
|
||||
date = new Date("2023-12-28");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-12-25");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-12-29");
|
||||
date = new Date("2023-12-29");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-12-25");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-12-29");
|
||||
date = new Date("2023-12-30");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-12-25");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-12-29");
|
||||
date = new Date("2023-12-31");
|
||||
expect(formatDate(getFirstWorkDayOfWeek(date))).toBe("2023-12-25");
|
||||
expect(formatDate(getLastWorkDayOfWeek(date))).toBe("2023-12-29");
|
||||
});
|
||||
|
||||
test('získání čísla týdne v roce', () => {
|
||||
let date = new Date("2023-10-01");
|
||||
expect(getWeekNumber(date)).toBe(39);
|
||||
date = new Date("2023-10-02");
|
||||
expect(getWeekNumber(date)).toBe(40);
|
||||
date = new Date("2023-10-03");
|
||||
expect(getWeekNumber(date)).toBe(40);
|
||||
date = new Date("2023-10-04");
|
||||
expect(getWeekNumber(date)).toBe(40);
|
||||
date = new Date("2023-10-05");
|
||||
expect(getWeekNumber(date)).toBe(40);
|
||||
date = new Date("2023-10-06");
|
||||
expect(getWeekNumber(date)).toBe(40);
|
||||
date = new Date("2023-10-07");
|
||||
expect(getWeekNumber(date)).toBe(40);
|
||||
date = new Date("2023-10-08");
|
||||
expect(getWeekNumber(date)).toBe(40);
|
||||
date = new Date("2023-10-09");
|
||||
expect(getWeekNumber(date)).toBe(41);
|
||||
|
||||
date = new Date("2022-01-01");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
|
||||
date = new Date("2022-12-30");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2022-12-31");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2023-01-01");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2023-01-02");
|
||||
expect(getWeekNumber(date)).toBe(1);
|
||||
date = new Date("2023-01-03");
|
||||
expect(getWeekNumber(date)).toBe(1);
|
||||
date = new Date("2023-01-04");
|
||||
expect(getWeekNumber(date)).toBe(1);
|
||||
date = new Date("2023-01-05");
|
||||
expect(getWeekNumber(date)).toBe(1);
|
||||
date = new Date("2023-01-06");
|
||||
expect(getWeekNumber(date)).toBe(1);
|
||||
date = new Date("2023-01-07");
|
||||
expect(getWeekNumber(date)).toBe(1);
|
||||
date = new Date("2023-01-08");
|
||||
expect(getWeekNumber(date)).toBe(1);
|
||||
date = new Date("2023-01-09");
|
||||
expect(getWeekNumber(date)).toBe(2);
|
||||
|
||||
date = new Date("2023-12-24");
|
||||
expect(getWeekNumber(date)).toBe(51);
|
||||
date = new Date("2023-12-25");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2023-12-26");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2023-12-27");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2023-12-28");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2023-12-29");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2023-12-30");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2023-12-31");
|
||||
expect(getWeekNumber(date)).toBe(52);
|
||||
date = new Date("2024-01-01");
|
||||
expect(getWeekNumber(date)).toBe(1);
|
||||
});
|
@ -1,3 +1,5 @@
|
||||
import { Choices } from "../../types";
|
||||
|
||||
/** Vrátí datum v ISO formátu. */
|
||||
export function formatDate(date: Date) {
|
||||
let currentDay = String(date.getDate()).padStart(2, '0');
|
||||
@ -39,6 +41,29 @@ export function getIsWeekend(date: Date) {
|
||||
return index == 5 || index == 6;
|
||||
}
|
||||
|
||||
/** Vrátí první pracovní den v týdnu předaného data. */
|
||||
export function getFirstWorkDayOfWeek(date: Date) {
|
||||
const firstDay = new Date(date.getTime());
|
||||
firstDay.setDate(date.getDate() - getDayOfWeekIndex(date));
|
||||
return firstDay;
|
||||
}
|
||||
|
||||
/** Vrátí poslední pracovní den v týdnu předaného data. */
|
||||
export function getLastWorkDayOfWeek(date: Date) {
|
||||
const lastDay = new Date(date.getTime());
|
||||
lastDay.setDate(date.getDate() + (4 - getDayOfWeekIndex(date)));
|
||||
return lastDay;
|
||||
}
|
||||
|
||||
/** Vrátí pořadové číslo týdne předaného data v roce dle ISO 8601. */
|
||||
export function getWeekNumber(inputDate: Date) {
|
||||
var date = new Date(inputDate.getTime());
|
||||
date.setHours(0, 0, 0, 0);
|
||||
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
|
||||
var week1 = new Date(date.getFullYear(), 0, 4);
|
||||
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí JWT token z hlaviček, pokud ho obsahují.
|
||||
*
|
||||
@ -84,3 +109,22 @@ export const checkBodyParams = (req: any, paramNames: string[]) => {
|
||||
|
||||
// TODO umístit do samostatného souboru
|
||||
export class InsufficientPermissions extends Error { }
|
||||
|
||||
export const getUsersByLocation = (data: Choices, login: string): string[] => {
|
||||
const result: string[] = [];
|
||||
|
||||
for (const location in data) {
|
||||
if (data.hasOwnProperty(location)) {
|
||||
if (data[location][login]) {
|
||||
for (const username in data[location]) {
|
||||
if (data[location].hasOwnProperty(username)) {
|
||||
result.push(username);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
@ -67,24 +67,36 @@ interface PizzaDay {
|
||||
orders: Order[], // seznam objednávek jednotlivých lidí
|
||||
}
|
||||
|
||||
/** Veškerá data pro zobrazení na klientovi */
|
||||
export interface ClientData {
|
||||
date: string, // datum vybraného dne pro zobrazení
|
||||
isWeekend: boolean, // příznak, zda je zvolené datum víkend
|
||||
weekIndex: number, // index zvoleného dne v týdnu (0-6)
|
||||
todayWeekIndex?: number, // index dnešního dne v týdnu (0-6)
|
||||
choices: Choices, // seznam voleb
|
||||
/** Týdenní menu jednotlivých restaurací. */
|
||||
export interface WeekMenu {
|
||||
[dayIndex: number]: {
|
||||
[restaurant in Restaurants]?: DayMenu
|
||||
}
|
||||
}
|
||||
|
||||
/** Data vztahující se k jednomu konkrétnímu dni. */
|
||||
export interface DayData {
|
||||
date: string, // datum dne
|
||||
isWeekend: boolean, // příznak, zda je datum víkend
|
||||
weekIndex: number, // index dne v týdnu (0-6)
|
||||
choices: Choices, // seznam voleb uživatelů
|
||||
// TODO smazat
|
||||
departureTimes: DepartureTime[], // seznam možných časů odchodu
|
||||
menus?: { [restaurant in Restaurants]?: Menu }, // menu jednotlivých restaurací
|
||||
menus?: { [restaurant in Restaurants]?: DayMenu }, // menu jednotlivých restaurací
|
||||
pizzaDay?: PizzaDay, // pizza day pro dnešní den, pokud existuje
|
||||
pizzaList?: Pizza[], // seznam dostupných pizz pro dnešní den
|
||||
pizzaListLastUpdate?: Date, // datum a čas poslední aktualizace pizz
|
||||
}
|
||||
|
||||
/** Nabídka jídel jednoho podniku. */
|
||||
export interface Menu {
|
||||
/** Veškerá data pro zobrazení na klientovi. */
|
||||
export interface ClientData extends DayData {
|
||||
todayWeekIndex?: number, // index dnešního dne v týdnu (0-6)
|
||||
}
|
||||
|
||||
/** Nabídka jídel jednoho podniku pro jeden konkrétní den. */
|
||||
export interface DayMenu {
|
||||
lastUpdate: string, // human-readable čas poslední aktualizace menu
|
||||
closed: boolean, // příznak, zda je daný podnik aktuálně zavřený
|
||||
closed: boolean, // příznak, zda je daný podnik v tento den zavřený
|
||||
food: Food[], // seznam jídel v menu
|
||||
}
|
||||
|
||||
@ -109,7 +121,8 @@ export enum Locations {
|
||||
|
||||
export enum UdalostEnum {
|
||||
ZAHAJENA_PIZZA = "Zahájen pizza day",
|
||||
OBJEDNANA_PIZZA = "Objednána pizza"
|
||||
OBJEDNANA_PIZZA = "Objednána pizza",
|
||||
JDEME_OBED = "Jdeme oběd",
|
||||
}
|
||||
|
||||
export interface NotififaceInput {
|
||||
@ -121,6 +134,7 @@ export interface NotifikaceData {
|
||||
input: NotififaceInput,
|
||||
gotify?: boolean,
|
||||
teams?: boolean,
|
||||
ntfy?: boolean,
|
||||
}
|
||||
|
||||
export interface GotifyServer {
|
||||
|
Loading…
x
Reference in New Issue
Block a user