feat: zvýraznění dnů v historii obsahujících objednávky
CI / Generate TypeScript types (push) Successful in 12s
CI / Server unit tests (push) Successful in 20s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 36s
CI / Playwright E2E tests (push) Successful in 1m17s
CI / Build and push Docker image (push) Successful in 40s
CI / Notify (push) Successful in 2s
CI / Generate TypeScript types (push) Successful in 12s
CI / Server unit tests (push) Successful in 20s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 36s
CI / Playwright E2E tests (push) Successful in 1m17s
CI / Build and push Docker image (push) Successful in 40s
CI / Notify (push) Successful in 2s
This commit is contained in:
@@ -28,6 +28,24 @@ function findGroup(data: ClientData, id: string): OrderGroup | undefined {
|
||||
return data.groups?.find(g => g.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí seznam ISO dat (YYYY-MM-DD), pro která existuje alespoň jedna objednávková skupina.
|
||||
* Slouží ke zvýraznění dnů v date pickeru na stránce objednávání.
|
||||
*/
|
||||
export async function getOrderDates(): Promise<string[]> {
|
||||
const EXTRA_SUFFIX = '_extra';
|
||||
const keys = await storage.listKeys(EXTRA_SUFFIX);
|
||||
const dates: string[] = [];
|
||||
for (const key of keys) {
|
||||
if (!key.endsWith(EXTRA_SUFFIX)) continue;
|
||||
const data = await storage.getData<ClientData>(key);
|
||||
if (data?.groups && data.groups.length > 0) {
|
||||
dates.push(key.slice(0, -EXTRA_SUFFIX.length));
|
||||
}
|
||||
}
|
||||
return dates.sort();
|
||||
}
|
||||
|
||||
export async function createGroup(creatorLogin: string, name: string, date?: Date): Promise<ClientData> {
|
||||
const stores = await getStores();
|
||||
if (!stores.some(s => s.toLowerCase() === name.trim().toLowerCase())) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import express, { Request } from "express";
|
||||
import { getLogin } from "../auth";
|
||||
import { parseToken } from "../utils";
|
||||
import { getWebsocket } from "../websocket";
|
||||
import { createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes, updateGroupFees } from "../groups";
|
||||
import { createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes, updateGroupFees, getOrderDates } from "../groups";
|
||||
import { GroupState } from "../../../types/gen/types.gen";
|
||||
|
||||
const router = express.Router();
|
||||
@@ -11,6 +11,13 @@ function broadcastExtra(data: any) {
|
||||
getWebsocket().emit("message", data);
|
||||
}
|
||||
|
||||
router.get("/dates", async (_req, res, next) => {
|
||||
try {
|
||||
const dates = await getOrderDates();
|
||||
res.status(200).json({ dates });
|
||||
} catch (e: any) { next(e); }
|
||||
});
|
||||
|
||||
router.post("/create", async (req: Request, res, next) => {
|
||||
const login = getLogin(parseToken(req));
|
||||
const { name } = req.body ?? {};
|
||||
|
||||
@@ -29,4 +29,10 @@ export interface StorageInterface {
|
||||
* @param data data pro uložení
|
||||
*/
|
||||
setData<Type>(key: string, data: Type): Promise<void>;
|
||||
|
||||
/**
|
||||
* Vrátí seznam všech klíčů, případně jen těch obsahujících předaný podřetězec.
|
||||
* @param contains volitelný podřetězec, který musí klíč obsahovat (např. '_extra')
|
||||
*/
|
||||
listKeys(contains?: string): Promise<string[]>;
|
||||
}
|
||||
@@ -29,4 +29,9 @@ export default class JsonStorage implements StorageInterface {
|
||||
db.set(key, data);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
listKeys(contains?: string): Promise<string[]> {
|
||||
const keys = Object.keys(db.JSON());
|
||||
return Promise.resolve(contains ? keys.filter(k => k.includes(contains)) : keys);
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,9 @@ export default class MemoryStorage implements StorageInterface {
|
||||
store.set(key, data);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
listKeys(contains?: string): Promise<string[]> {
|
||||
const keys = Array.from(store.keys());
|
||||
return Promise.resolve(contains ? keys.filter(k => k.includes(contains)) : keys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,16 @@ export default class RedisStorage implements StorageInterface {
|
||||
await client.json.set(key, '.', data as any);
|
||||
await client.json.get(key);
|
||||
}
|
||||
|
||||
async listKeys(contains?: string): Promise<string[]> {
|
||||
// SCAN je bezpečnější než KEYS na produkci (neblokuje server)
|
||||
const match = contains ? `*${contains}*` : '*';
|
||||
const keys: string[] = [];
|
||||
for await (const key of client.scanIterator({ MATCH: match, COUNT: 100 })) {
|
||||
// node-redis v4 vrací buď string, nebo (novější verze) pole stringů
|
||||
if (Array.isArray(key)) keys.push(...key);
|
||||
else keys.push(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,10 @@ const implementations: [string, () => StorageInterface, () => void][] = [
|
||||
inst.hasData = async (key: string) => Promise.resolve((inst as any).db.has(key));
|
||||
inst.getData = async (key: string) => (inst as any).db.get(key);
|
||||
inst.setData = async (key: string, data: any) => { (inst as any).db.set(key, data); return Promise.resolve(); };
|
||||
inst.listKeys = async (contains?: string) => {
|
||||
const keys = Object.keys((inst as any).db.JSON());
|
||||
return contains ? keys.filter((k: string) => k.includes(contains)) : keys;
|
||||
};
|
||||
return inst;
|
||||
}, () => {
|
||||
if (fs.existsSync(tempDbPath)) {
|
||||
@@ -76,6 +80,22 @@ describe.each(implementations)('%s splňuje StorageInterface kontrakt', (name, f
|
||||
expect((await storage.getData<{ val: string }>('a'))?.val).toBe('A');
|
||||
expect((await storage.getData<{ val: string }>('b'))?.val).toBe('B');
|
||||
});
|
||||
|
||||
test('listKeys vrátí všechny uložené klíče', async () => {
|
||||
await storage.setData('2024-01-01_extra', {});
|
||||
await storage.setData('2024-01-02', {});
|
||||
const keys = await storage.listKeys();
|
||||
expect(keys).toContain('2024-01-01_extra');
|
||||
expect(keys).toContain('2024-01-02');
|
||||
});
|
||||
|
||||
test('listKeys filtruje podle podřetězce', async () => {
|
||||
await storage.setData('2024-01-01_extra', {});
|
||||
await storage.setData('2024-01-02_extra', {});
|
||||
await storage.setData('2024-01-02', {});
|
||||
const keys = await storage.listKeys('_extra');
|
||||
expect(keys.sort()).toEqual(['2024-01-01_extra', '2024-01-02_extra']);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
||||
Reference in New Issue
Block a user