feat: proklik na stránky podniku ze stránky objednávek
CI / Generate TypeScript types (push) Successful in 9s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 25s
CI / Build client (push) Successful in 37s
CI / Playwright E2E tests (push) Successful in 1m18s
CI / Build and push Docker image (push) Successful in 39s
CI / Notify (push) Successful in 2s
CI / Generate TypeScript types (push) Successful in 9s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 25s
CI / Build client (push) Successful in 37s
CI / Playwright E2E tests (push) Successful in 1m18s
CI / Build and push Docker image (push) Successful in 39s
CI / Notify (push) Successful in 2s
This commit is contained in:
@@ -2,17 +2,19 @@ import { useState } from "react";
|
|||||||
import { Modal, Button, Form, ListGroup, Alert } from "react-bootstrap";
|
import { Modal, Button, Form, ListGroup, Alert } from "react-bootstrap";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faTrashCan } from "@fortawesome/free-regular-svg-icons";
|
import { faTrashCan } from "@fortawesome/free-regular-svg-icons";
|
||||||
import { addStore, deleteStore } from "../../../../types";
|
import { faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { addStore, deleteStore, Store } from "../../../../types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
stores: string[];
|
stores: Store[];
|
||||||
onStoresChanged: (stores: string[]) => void;
|
onStoresChanged: (stores: Store[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function StoreAdminModal({ isOpen, onClose, stores, onStoresChanged }: Readonly<Props>) {
|
export default function StoreAdminModal({ isOpen, onClose, stores, onStoresChanged }: Readonly<Props>) {
|
||||||
const [newName, setNewName] = useState('');
|
const [newName, setNewName] = useState('');
|
||||||
|
const [newUrl, setNewUrl] = useState('');
|
||||||
const [heslo, setHeslo] = useState('');
|
const [heslo, setHeslo] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -22,12 +24,13 @@ export default function StoreAdminModal({ isOpen, onClose, stores, onStoresChang
|
|||||||
setError(null);
|
setError(null);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await addStore({ body: { name: newName.trim(), heslo } });
|
const res = await addStore({ body: { name: newName.trim(), url: newUrl.trim() || undefined, heslo } });
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
setError((res.error as any).error || 'Nastala chyba');
|
setError((res.error as any).error || 'Nastala chyba');
|
||||||
} else if (res.data) {
|
} else if (res.data) {
|
||||||
onStoresChanged(res.data as string[]);
|
onStoresChanged(res.data as Store[]);
|
||||||
setNewName('');
|
setNewName('');
|
||||||
|
setNewUrl('');
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setError(e.message || 'Nastala chyba');
|
setError(e.message || 'Nastala chyba');
|
||||||
@@ -44,7 +47,7 @@ export default function StoreAdminModal({ isOpen, onClose, stores, onStoresChang
|
|||||||
if (res.error) {
|
if (res.error) {
|
||||||
setError((res.error as any).error || 'Nastala chyba');
|
setError((res.error as any).error || 'Nastala chyba');
|
||||||
} else if (res.data) {
|
} else if (res.data) {
|
||||||
onStoresChanged(res.data as string[]);
|
onStoresChanged(res.data as Store[]);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setError(e.message || 'Nastala chyba');
|
setError(e.message || 'Nastala chyba');
|
||||||
@@ -78,14 +81,22 @@ export default function StoreAdminModal({ isOpen, onClose, stores, onStoresChang
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<h6>Přidat obchod</h6>
|
<h6>Přidat obchod</h6>
|
||||||
<div className="d-flex gap-2 mb-3">
|
|
||||||
<Form.Control
|
<Form.Control
|
||||||
|
className="mb-2"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Název obchodu"
|
placeholder="Název obchodu"
|
||||||
value={newName}
|
value={newName}
|
||||||
onChange={e => setNewName(e.target.value)}
|
onChange={e => setNewName(e.target.value)}
|
||||||
onKeyDown={e => { e.stopPropagation(); if (e.key === 'Enter') handleAdd(); }}
|
onKeyDown={e => { e.stopPropagation(); if (e.key === 'Enter') handleAdd(); }}
|
||||||
/>
|
/>
|
||||||
|
<div className="d-flex gap-2 mb-3">
|
||||||
|
<Form.Control
|
||||||
|
type="url"
|
||||||
|
placeholder="URL na nabídku (volitelné, např. Bolt Food/Wolt)"
|
||||||
|
value={newUrl}
|
||||||
|
onChange={e => setNewUrl(e.target.value)}
|
||||||
|
onKeyDown={e => { e.stopPropagation(); if (e.key === 'Enter') handleAdd(); }}
|
||||||
|
/>
|
||||||
<Button variant="primary" onClick={handleAdd} disabled={loading || !newName.trim() || !heslo}>
|
<Button variant="primary" onClick={handleAdd} disabled={loading || !newName.trim() || !heslo}>
|
||||||
Přidat
|
Přidat
|
||||||
</Button>
|
</Button>
|
||||||
@@ -97,13 +108,20 @@ export default function StoreAdminModal({ isOpen, onClose, stores, onStoresChang
|
|||||||
) : (
|
) : (
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
{stores.map(s => (
|
{stores.map(s => (
|
||||||
<ListGroup.Item key={s} className="d-flex justify-content-between align-items-center">
|
<ListGroup.Item key={s.name} className="d-flex justify-content-between align-items-center">
|
||||||
{s}
|
<span>
|
||||||
|
{s.name}
|
||||||
|
{s.url && /^https?:\/\//i.test(s.url) && (
|
||||||
|
<a href={s.url} target="_blank" rel="noopener noreferrer" className="ms-2" title="Otevřít nabídku v nové záložce">
|
||||||
|
<FontAwesomeIcon icon={faUpRightFromSquare} />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faTrashCan}
|
icon={faTrashCan}
|
||||||
className="action-icon"
|
className="action-icon"
|
||||||
title="Odebrat"
|
title="Odebrat"
|
||||||
onClick={() => handleRemove(s)}
|
onClick={() => handleRemove(s.name)}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
/>
|
/>
|
||||||
</ListGroup.Item>
|
</ListGroup.Item>
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ export default function OrderGroupsPage() {
|
|||||||
style={{ maxWidth: 260 }}
|
style={{ maxWidth: 260 }}
|
||||||
>
|
>
|
||||||
<option value="">— vyberte obchod —</option>
|
<option value="">— vyberte obchod —</option>
|
||||||
{stores.map(s => <option key={s} value={s}>{s}</option>)}
|
{stores.map(s => <option key={s.name} value={s.name}>{s.name}</option>)}
|
||||||
</Form.Select>
|
</Form.Select>
|
||||||
<Button variant="primary" onClick={handleCreate} disabled={creating || !newGroupName}>
|
<Button variant="primary" onClick={handleCreate} disabled={creating || !newGroupName}>
|
||||||
Vytvořit skupinu
|
Vytvořit skupinu
|
||||||
@@ -429,6 +429,10 @@ export default function OrderGroupsPage() {
|
|||||||
const isLocked = group.state === GroupState.LOCKED;
|
const isLocked = group.state === GroupState.LOCKED;
|
||||||
const memberEntries = Object.entries(group.members) as [string, OrderGroupMember][];
|
const memberEntries = Object.entries(group.members) as [string, OrderGroupMember][];
|
||||||
const editingTimes = group.id in editTimes;
|
const editingTimes = group.id in editTimes;
|
||||||
|
// URL na nabídku podniku (pokud ji má dohledatelný obchod vyplněnou).
|
||||||
|
// Povolíme jen http(s), aby odkaz nemohl být zneužit (např. javascript:).
|
||||||
|
const rawStoreUrl = stores.find(s => s.name === group.name)?.url;
|
||||||
|
const storeUrl = rawStoreUrl && /^https?:\/\//i.test(rawStoreUrl) ? rawStoreUrl : undefined;
|
||||||
|
|
||||||
const totalFees = (group.fees ?? 0) + (group.shipping ?? 0) + (group.tip ?? 0);
|
const totalFees = (group.fees ?? 0) + (group.shipping ?? 0) + (group.tip ?? 0);
|
||||||
// Poplatky se dělí jen mezi aktivní strávníky (kdo si reálně něco objednal).
|
// Poplatky se dělí jen mezi aktivní strávníky (kdo si reálně něco objednal).
|
||||||
@@ -442,7 +446,15 @@ export default function OrderGroupsPage() {
|
|||||||
<Card key={group.id} className="mb-3 fade-in">
|
<Card key={group.id} className="mb-3 fade-in">
|
||||||
<Card.Header className="d-flex justify-content-between align-items-center">
|
<Card.Header className="d-flex justify-content-between align-items-center">
|
||||||
<div className="d-flex align-items-center gap-2">
|
<div className="d-flex align-items-center gap-2">
|
||||||
|
{storeUrl ? (
|
||||||
|
<strong>
|
||||||
|
<a href={storeUrl} target="_blank" rel="noopener noreferrer" title="Otevřít nabídku v nové záložce">
|
||||||
|
{group.name}
|
||||||
|
</a>
|
||||||
|
</strong>
|
||||||
|
) : (
|
||||||
<strong>{group.name}</strong>
|
<strong>{group.name}</strong>
|
||||||
|
)}
|
||||||
{stateBadge(group.state)}
|
{stateBadge(group.state)}
|
||||||
<small className="text-muted">zakladatel: {group.creatorLogin}</small>
|
<small className="text-muted">zakladatel: {group.creatorLogin}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
"Proklik na nabídku podniku ze stránky objednávek"
|
||||||
|
]
|
||||||
@@ -49,11 +49,11 @@ export async function getOrderDates(): Promise<string[]> {
|
|||||||
|
|
||||||
export async function createGroup(creatorLogin: string, name: string, date?: Date): Promise<ClientData> {
|
export async function createGroup(creatorLogin: string, name: string, date?: Date): Promise<ClientData> {
|
||||||
const stores = await getStores();
|
const stores = await getStores();
|
||||||
if (!stores.some(s => s.toLowerCase() === name.trim().toLowerCase())) {
|
if (!stores.some(s => s.name.toLowerCase() === name.trim().toLowerCase())) {
|
||||||
throw new Error('Obchod není v seznamu povolených obchodů');
|
throw new Error('Obchod není v seznamu povolených obchodů');
|
||||||
}
|
}
|
||||||
const data = await getExtraData(date);
|
const data = await getExtraData(date);
|
||||||
const canonical = stores.find(s => s.toLowerCase() === name.trim().toLowerCase())!;
|
const canonical = stores.find(s => s.name.toLowerCase() === name.trim().toLowerCase())!.name;
|
||||||
const group: OrderGroup = {
|
const group: OrderGroup = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
name: canonical,
|
name: canonical,
|
||||||
|
|||||||
@@ -11,15 +11,18 @@ router.get("/", async (_req, res, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post("/add", async (req, res, next) => {
|
router.post("/add", async (req, res, next) => {
|
||||||
const { name, heslo } = req.body ?? {};
|
const { name, heslo, url } = req.body ?? {};
|
||||||
if (!name || typeof name !== 'string') {
|
if (!name || typeof name !== 'string') {
|
||||||
return res.status(400).json({ error: 'Nebyl předán název obchodu' });
|
return res.status(400).json({ error: 'Nebyl předán název obchodu' });
|
||||||
}
|
}
|
||||||
if (!heslo || typeof heslo !== 'string') {
|
if (!heslo || typeof heslo !== 'string') {
|
||||||
return res.status(400).json({ error: 'Nebylo předáno heslo' });
|
return res.status(400).json({ error: 'Nebylo předáno heslo' });
|
||||||
}
|
}
|
||||||
|
if (url != null && typeof url !== 'string') {
|
||||||
|
return res.status(400).json({ error: 'Neplatná URL obchodu' });
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const stores = await addStore(name, heslo);
|
const stores = await addStore(name, heslo, url);
|
||||||
res.status(200).json(stores);
|
res.status(200).json(stores);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.message === 'UNAUTHORIZED') {
|
if (e.message === 'UNAUTHORIZED') {
|
||||||
|
|||||||
+43
-7
@@ -1,13 +1,29 @@
|
|||||||
|
import { Store } from "../../types/gen/types.gen";
|
||||||
import getStorage from "./storage";
|
import getStorage from "./storage";
|
||||||
|
|
||||||
const storage = getStorage();
|
const storage = getStorage();
|
||||||
const STORES_KEY = 'stores';
|
const STORES_KEY = 'stores';
|
||||||
|
|
||||||
export async function getStores(): Promise<string[]> {
|
/**
|
||||||
return (await storage.getData<string[]>(STORES_KEY)) ?? [];
|
* Vrátí seznam povolených obchodů. Zachovává zpětnou kompatibilitu se starším
|
||||||
|
* formátem, kdy byly obchody uloženy jako pole řetězců (převede je na objekty).
|
||||||
|
*/
|
||||||
|
export async function getStores(): Promise<Store[]> {
|
||||||
|
const raw = await storage.getData<(string | Store)[]>(STORES_KEY);
|
||||||
|
if (!raw) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return raw.map(s => (typeof s === 'string' ? { name: s } : s));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addStore(name: string, heslo: string): Promise<string[]> {
|
/**
|
||||||
|
* Přidá nový obchod do seznamu povolených.
|
||||||
|
*
|
||||||
|
* @param name název obchodu
|
||||||
|
* @param heslo admin heslo
|
||||||
|
* @param url volitelná URL na nabídku podniku
|
||||||
|
*/
|
||||||
|
export async function addStore(name: string, heslo: string, url?: string): Promise<Store[]> {
|
||||||
const adminPassword = process.env.ADMIN_PASSWORD;
|
const adminPassword = process.env.ADMIN_PASSWORD;
|
||||||
if (!adminPassword || heslo !== adminPassword) {
|
if (!adminPassword || heslo !== adminPassword) {
|
||||||
throw new Error('UNAUTHORIZED');
|
throw new Error('UNAUTHORIZED');
|
||||||
@@ -17,21 +33,41 @@ export async function addStore(name: string, heslo: string): Promise<string[]> {
|
|||||||
throw new Error('Název obchodu nesmí být prázdný');
|
throw new Error('Název obchodu nesmí být prázdný');
|
||||||
}
|
}
|
||||||
const stores = await getStores();
|
const stores = await getStores();
|
||||||
if (stores.some(s => s.toLowerCase() === trimmed.toLowerCase())) {
|
if (stores.some(s => s.name.toLowerCase() === trimmed.toLowerCase())) {
|
||||||
throw new Error('Obchod s tímto názvem již existuje');
|
throw new Error('Obchod s tímto názvem již existuje');
|
||||||
}
|
}
|
||||||
const updated = [...stores, trimmed];
|
const trimmedUrl = url?.trim();
|
||||||
|
if (trimmedUrl) {
|
||||||
|
// Povolíme pouze http(s), aby URL nemohla být zneužita (např. javascript: → XSS)
|
||||||
|
let parsed: URL;
|
||||||
|
try {
|
||||||
|
parsed = new URL(trimmedUrl);
|
||||||
|
} catch {
|
||||||
|
throw new Error('Neplatná URL obchodu');
|
||||||
|
}
|
||||||
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||||
|
throw new Error('URL musí začínat http:// nebo https://');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const store: Store = trimmedUrl ? { name: trimmed, url: trimmedUrl } : { name: trimmed };
|
||||||
|
const updated = [...stores, store];
|
||||||
await storage.setData(STORES_KEY, updated);
|
await storage.setData(STORES_KEY, updated);
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeStore(name: string, heslo: string): Promise<string[]> {
|
/**
|
||||||
|
* Odebere obchod ze seznamu povolených (dle názvu).
|
||||||
|
*
|
||||||
|
* @param name název obchodu
|
||||||
|
* @param heslo admin heslo
|
||||||
|
*/
|
||||||
|
export async function removeStore(name: string, heslo: string): Promise<Store[]> {
|
||||||
const adminPassword = process.env.ADMIN_PASSWORD;
|
const adminPassword = process.env.ADMIN_PASSWORD;
|
||||||
if (!adminPassword || heslo !== adminPassword) {
|
if (!adminPassword || heslo !== adminPassword) {
|
||||||
throw new Error('UNAUTHORIZED');
|
throw new Error('UNAUTHORIZED');
|
||||||
}
|
}
|
||||||
const stores = await getStores();
|
const stores = await getStores();
|
||||||
const updated = stores.filter(s => s.toLowerCase() !== name.trim().toLowerCase());
|
const updated = stores.filter(s => s.name.toLowerCase() !== name.trim().toLowerCase());
|
||||||
await storage.setData(STORES_KEY, updated);
|
await storage.setData(STORES_KEY, updated);
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { resetMemoryStorage } from '../storage/memory';
|
import { resetMemoryStorage } from '../storage/memory';
|
||||||
|
import getStorage from '../storage';
|
||||||
import { getStores, addStore, removeStore } from '../stores';
|
import { getStores, addStore, removeStore } from '../stores';
|
||||||
|
|
||||||
const ADMIN_PW = 'testadmin';
|
const ADMIN_PW = 'testadmin';
|
||||||
|
|
||||||
|
/** Vrátí pole názvů obchodů pro snadné porovnání v testech. */
|
||||||
|
const names = (stores: { name: string }[]) => stores.map(s => s.name);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
resetMemoryStorage();
|
resetMemoryStorage();
|
||||||
process.env.ADMIN_PASSWORD = ADMIN_PW;
|
process.env.ADMIN_PASSWORD = ADMIN_PW;
|
||||||
@@ -17,12 +21,37 @@ describe('getStores', () => {
|
|||||||
const stores = await getStores();
|
const stores = await getStores();
|
||||||
expect(stores).toEqual([]);
|
expect(stores).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('převede starý formát (pole řetězců) na objekty', async () => {
|
||||||
|
// Simulace dat uložených ve starším formátu (před zavedením URL)
|
||||||
|
await getStorage().setData('stores', ['McDonald\'s', 'KFC']);
|
||||||
|
const stores = await getStores();
|
||||||
|
expect(stores).toEqual([{ name: 'McDonald\'s' }, { name: 'KFC' }]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('addStore', () => {
|
describe('addStore', () => {
|
||||||
test('přidá obchod se správným heslem', async () => {
|
test('přidá obchod se správným heslem', async () => {
|
||||||
const stores = await addStore('McDonald\'s', ADMIN_PW);
|
const stores = await addStore('McDonald\'s', ADMIN_PW);
|
||||||
expect(stores).toContain('McDonald\'s');
|
expect(names(stores)).toContain('McDonald\'s');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uloží volitelnou URL a ořízne mezery', async () => {
|
||||||
|
const stores = await addStore('Bistro', ADMIN_PW, ' https://wolt.com/bistro ');
|
||||||
|
expect(stores).toContainEqual({ name: 'Bistro', url: 'https://wolt.com/bistro' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bez URL uloží obchod bez pole url', async () => {
|
||||||
|
const stores = await addStore('KFC', ADMIN_PW, ' ');
|
||||||
|
expect(stores).toContainEqual({ name: 'KFC' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('odmítne URL s nepovoleným schématem (javascript:)', async () => {
|
||||||
|
await expect(addStore('Zlo', ADMIN_PW, 'javascript:alert(1)')).rejects.toThrow('http');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('odmítne nevalidní URL', async () => {
|
||||||
|
await expect(addStore('Zlo', ADMIN_PW, 'nfdjska')).rejects.toThrow('Neplatná URL');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('vyhodí UNAUTHORIZED s nesprávným heslem', async () => {
|
test('vyhodí UNAUTHORIZED s nesprávným heslem', async () => {
|
||||||
@@ -47,8 +76,8 @@ describe('addStore', () => {
|
|||||||
await addStore('McDonald\'s', ADMIN_PW);
|
await addStore('McDonald\'s', ADMIN_PW);
|
||||||
const stores = await addStore('KFC', ADMIN_PW);
|
const stores = await addStore('KFC', ADMIN_PW);
|
||||||
expect(stores).toHaveLength(2);
|
expect(stores).toHaveLength(2);
|
||||||
expect(stores).toContain('McDonald\'s');
|
expect(names(stores)).toContain('McDonald\'s');
|
||||||
expect(stores).toContain('KFC');
|
expect(names(stores)).toContain('KFC');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,12 +88,12 @@ describe('removeStore', () => {
|
|||||||
|
|
||||||
test('odebere obchod se správným heslem', async () => {
|
test('odebere obchod se správným heslem', async () => {
|
||||||
const stores = await removeStore('McDonald\'s', ADMIN_PW);
|
const stores = await removeStore('McDonald\'s', ADMIN_PW);
|
||||||
expect(stores).not.toContain('McDonald\'s');
|
expect(names(stores)).not.toContain('McDonald\'s');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('case-insensitive odebrání', async () => {
|
test('case-insensitive odebrání', async () => {
|
||||||
const stores = await removeStore('MCDONALD\'S', ADMIN_PW);
|
const stores = await removeStore('MCDONALD\'S', ADMIN_PW);
|
||||||
expect(stores).not.toContain('McDonald\'s');
|
expect(names(stores)).not.toContain('McDonald\'s');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('vyhodí UNAUTHORIZED s nesprávným heslem', async () => {
|
test('vyhodí UNAUTHORIZED s nesprávným heslem', async () => {
|
||||||
@@ -73,6 +102,6 @@ describe('removeStore', () => {
|
|||||||
|
|
||||||
test('odebrání neexistujícího obchodu nic nerozbije', async () => {
|
test('odebrání neexistujícího obchodu nic nerozbije', async () => {
|
||||||
const stores = await removeStore('Neexistuje', ADMIN_PW);
|
const stores = await removeStore('Neexistuje', ADMIN_PW);
|
||||||
expect(stores).toContain('McDonald\'s');
|
expect(names(stores)).toContain('McDonald\'s');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ post:
|
|||||||
name:
|
name:
|
||||||
description: Název obchodu/restaurace
|
description: Název obchodu/restaurace
|
||||||
type: string
|
type: string
|
||||||
|
url:
|
||||||
|
description: Volitelná URL na nabídku podniku (např. Bolt Food/Wolt/Foodora)
|
||||||
|
type: string
|
||||||
heslo:
|
heslo:
|
||||||
description: Admin heslo (ADMIN_PASSWORD)
|
description: Admin heslo (ADMIN_PASSWORD)
|
||||||
type: string
|
type: string
|
||||||
@@ -25,4 +28,4 @@ post:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
$ref: "../../schemas/_index.yml#/Store"
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ post:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
$ref: "../../schemas/_index.yml#/Store"
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ get:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
$ref: "../../schemas/_index.yml#/Store"
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ ClientData:
|
|||||||
description: Seznam povolených obchodů/restaurací pro extra objednávky
|
description: Seznam povolených obchodů/restaurací pro extra objednávky
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
$ref: "#/Store"
|
||||||
|
|
||||||
# --- OBĚDY ---
|
# --- OBĚDY ---
|
||||||
UserLunchChoice:
|
UserLunchChoice:
|
||||||
@@ -708,6 +708,20 @@ ClearMockDataRequest:
|
|||||||
$ref: "#/DayIndex"
|
$ref: "#/DayIndex"
|
||||||
|
|
||||||
# --- SKUPINOVÉ OBJEDNÁVKY ---
|
# --- SKUPINOVÉ OBJEDNÁVKY ---
|
||||||
|
Store:
|
||||||
|
description: Povolený obchod/restaurace pro extra objednávky.
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: Název obchodu/restaurace
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Volitelná URL na nabídku podniku (např. Bolt Food/Wolt/Foodora)
|
||||||
|
type: string
|
||||||
|
|
||||||
GroupState:
|
GroupState:
|
||||||
description: Stav skupiny objednávky
|
description: Stav skupiny objednávky
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
Reference in New Issue
Block a user