feat: podpora více odkazů na podnik při objednávání
This commit is contained in:
13 files changed
+338
-81
No files matched your search
+60
-20
@@ -4,16 +4,68 @@ import getStorage from "./storage";
|
||||
const storage = getStorage();
|
||||
const STORES_KEY = 'stores';
|
||||
|
||||
/** Maximální počet URL na jeden obchod (ochrana proti nafouknutí dat). */
|
||||
const MAX_URLS = 10;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
* Podoby, v jakých mohou být obchody uloženy ve storage:
|
||||
* - řetězec — nejstarší formát (jen název),
|
||||
* - objekt s `url` — formát s jednou URL na nabídku,
|
||||
* - objekt s `urls` — aktuální formát s více URL.
|
||||
*/
|
||||
type StoredStore = string | { name: string; url?: string; urls?: string[] };
|
||||
|
||||
/**
|
||||
* Vrátí seznam povolených obchodů. Zachovává zpětnou kompatibilitu se staršími
|
||||
* formáty uložených dat (pole řetězců, resp. jediná URL v poli `url`) — vše
|
||||
* převede na aktuální podobu se seznamem `urls`.
|
||||
*/
|
||||
export async function getStores(): Promise<Store[]> {
|
||||
const raw = await storage.getData<(string | Store)[]>(STORES_KEY);
|
||||
const raw = await storage.getData<StoredStore[]>(STORES_KEY);
|
||||
if (!raw) {
|
||||
return [];
|
||||
}
|
||||
return raw.map(s => (typeof s === 'string' ? { name: s } : s));
|
||||
return raw.map(s => {
|
||||
if (typeof s === 'string') {
|
||||
return { name: s };
|
||||
}
|
||||
const urls = s.urls ?? (s.url ? [s.url] : []);
|
||||
return urls.length > 0 ? { name: s.name, urls } : { name: s.name };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ověří a upraví seznam URL obchodu — ořeže mezery, zahodí prázdné hodnoty,
|
||||
* odstraní duplicity a zkontroluje, že jde o platné http(s) odkazy.
|
||||
*/
|
||||
function normalizeUrls(urls?: string[]): string[] {
|
||||
if (!urls) {
|
||||
return [];
|
||||
}
|
||||
const result: string[] = [];
|
||||
for (const raw of urls) {
|
||||
const trimmed = raw?.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
// Povolíme pouze http(s), aby URL nemohla být zneužita (např. javascript: → XSS)
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(trimmed);
|
||||
} catch {
|
||||
throw new Error('Neplatná URL obchodu');
|
||||
}
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
throw new Error('URL musí začínat http:// nebo https://');
|
||||
}
|
||||
if (!result.some(u => u.toLowerCase() === trimmed.toLowerCase())) {
|
||||
result.push(trimmed);
|
||||
}
|
||||
}
|
||||
if (result.length > MAX_URLS) {
|
||||
throw new Error(`Obchod může mít nejvýše ${MAX_URLS} URL`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,9 +73,9 @@ export async function getStores(): Promise<Store[]> {
|
||||
*
|
||||
* @param name název obchodu
|
||||
* @param heslo admin heslo
|
||||
* @param url volitelná URL na nabídku podniku
|
||||
* @param urls volitelný seznam URL na nabídku podniku (různé dovozové služby)
|
||||
*/
|
||||
export async function addStore(name: string, heslo: string, url?: string): Promise<Store[]> {
|
||||
export async function addStore(name: string, heslo: string, urls?: string[]): Promise<Store[]> {
|
||||
const adminPassword = process.env.ADMIN_PASSWORD;
|
||||
if (!adminPassword || heslo !== adminPassword) {
|
||||
throw new Error('UNAUTHORIZED');
|
||||
@@ -36,20 +88,8 @@ export async function addStore(name: string, heslo: string, url?: string): Promi
|
||||
if (stores.some(s => s.name.toLowerCase() === trimmed.toLowerCase())) {
|
||||
throw new Error('Obchod s tímto názvem již existuje');
|
||||
}
|
||||
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 normalizedUrls = normalizeUrls(urls);
|
||||
const store: Store = normalizedUrls.length > 0 ? { name: trimmed, urls: normalizedUrls } : { name: trimmed };
|
||||
const updated = [...stores, store];
|
||||
await storage.setData(STORES_KEY, updated);
|
||||
return updated;
|
||||
|
||||
Reference in new issue
Block a user