Replace entire repo content with code from /root/shahikitchen-google/

This commit is contained in:
root
2026-07-03 02:51:15 +00:00
parent c8b7dc95e4
commit 8ccf033329
79 changed files with 7611 additions and 439 deletions
+248
View File
@@ -0,0 +1,248 @@
import { mkdir, readFile, writeFile } from 'fs/promises';
import path from 'path';
import type { MenuCategory, MenuItem } from '@/domain/menu/entities';
import type { MenuVersionTrigger } from '@/domain/menu/versioning';
import { menuCategories as defaultMenuCategories } from './static-menu-data';
const MENU_DATA_DIR = path.join(process.cwd(), 'data');
const MENU_DATA_FILE = path.join(MENU_DATA_DIR, 'menu.json');
let memoryCache: MenuCategory[] | null = null;
async function ensureMenuFile(): Promise<void> {
await mkdir(MENU_DATA_DIR, { recursive: true });
try {
await readFile(MENU_DATA_FILE, 'utf8');
} catch {
await writeFile(MENU_DATA_FILE, JSON.stringify(defaultMenuCategories, null, 2), 'utf8');
}
}
function isMenuCategoryArray(value: unknown): value is MenuCategory[] {
return (
Array.isArray(value) &&
value.every(
(category) =>
typeof category === 'object' &&
category !== null &&
typeof (category as MenuCategory).id === 'string' &&
Array.isArray((category as MenuCategory).items),
)
);
}
export async function readMenuCategories(): Promise<MenuCategory[]> {
if (memoryCache) return memoryCache;
await ensureMenuFile();
const raw = await readFile(MENU_DATA_FILE, 'utf8');
const parsed = JSON.parse(raw) as unknown;
if (!isMenuCategoryArray(parsed)) {
memoryCache = defaultMenuCategories;
return memoryCache;
}
memoryCache = parsed;
return memoryCache;
}
export interface WriteMenuOptions {
snapshotLabel?: string;
snapshotTrigger?: MenuVersionTrigger;
skipSnapshot?: boolean;
}
export async function persistMenuCategories(categories: MenuCategory[]): Promise<void> {
await ensureMenuFile();
await writeFile(MENU_DATA_FILE, JSON.stringify(categories, null, 2), 'utf8');
memoryCache = categories;
}
export async function writeMenuCategories(
categories: MenuCategory[],
options?: WriteMenuOptions,
): Promise<void> {
await persistMenuCategories(categories);
if (!options?.skipSnapshot) {
const { createMenuSnapshot, ensureMenuBaseline } = await import('./menu-versioning');
await ensureMenuBaseline();
await createMenuSnapshot(
categories,
options?.snapshotLabel ?? 'Menu updated',
options?.snapshotTrigger ?? 'auto',
);
}
}
export async function updateMenuItem(
categoryId: string,
itemId: string,
updates: Partial<MenuItem>,
): Promise<MenuItem | null> {
const categories = await readMenuCategories();
let updatedItem: MenuItem | null = null;
const nextCategories = categories.map((category) => {
if (category.id !== categoryId) return category;
return {
...category,
items: category.items.map((item) => {
if (item.id !== itemId) return item;
updatedItem = { ...item, ...updates, id: item.id };
return updatedItem;
}),
};
});
if (!updatedItem) return null;
const dishName =
nextCategories
.find((category) => category.id === categoryId)
?.items.find((item) => item.id === itemId)?.name ?? itemId;
await writeMenuCategories(nextCategories, {
snapshotLabel: `Updated dish: ${dishName}`,
});
return updatedItem;
}
export function slugifyMenuId(text: string): string {
return text
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
function allItems(categories: MenuCategory[]): MenuItem[] {
return categories.flatMap((category) => category.items);
}
function isItemIdTaken(categories: MenuCategory[], itemId: string): boolean {
return allItems(categories).some((item) => item.id === itemId);
}
function uniqueItemId(categories: MenuCategory[], baseId: string): string {
if (!isItemIdTaken(categories, baseId)) return baseId;
let suffix = 2;
while (isItemIdTaken(categories, `${baseId}-${suffix}`)) suffix += 1;
return `${baseId}-${suffix}`;
}
export async function addMenuItem(categoryId: string, item: MenuItem): Promise<MenuItem | null> {
const categories = await readMenuCategories();
const categoryExists = categories.some((category) => category.id === categoryId);
if (!categoryExists) return null;
const id = uniqueItemId(categories, slugifyMenuId(item.id || item.name));
if (!id) return null;
const newItem: MenuItem = { ...item, id };
const nextCategories = categories.map((category) =>
category.id === categoryId
? { ...category, items: [...category.items, newItem] }
: category,
);
await writeMenuCategories(nextCategories, {
snapshotLabel: `Added dish: ${newItem.name}`,
});
return newItem;
}
export async function removeMenuItem(categoryId: string, itemId: string): Promise<boolean> {
const categories = await readMenuCategories();
let removed = false;
const nextCategories = categories.map((category) => {
if (category.id !== categoryId) return category;
const nextItems = category.items.filter((item) => {
if (item.id === itemId) {
removed = true;
return false;
}
return true;
});
return { ...category, items: nextItems };
});
if (!removed) return false;
const removedName =
categories
.find((category) => category.id === categoryId)
?.items.find((item) => item.id === itemId)?.name ?? itemId;
await writeMenuCategories(nextCategories, {
snapshotLabel: `Removed dish: ${removedName}`,
});
return true;
}
export async function addMenuCategory(category: MenuCategory): Promise<MenuCategory | null> {
const categories = await readMenuCategories();
const id = slugifyMenuId(category.id || category.name);
if (!id) return null;
if (categories.some((existing) => existing.id === id)) return null;
const newCategory: MenuCategory = {
id,
name: category.name.trim(),
items: category.items ?? [],
};
await writeMenuCategories([...categories, newCategory], {
snapshotLabel: `Added category: ${newCategory.name}`,
});
return newCategory;
}
export async function removeMenuCategory(categoryId: string): Promise<boolean> {
const categories = await readMenuCategories();
if (!categories.some((category) => category.id === categoryId)) return false;
const removedCategory = categories.find((category) => category.id === categoryId);
const nextCategories = categories.filter((category) => category.id !== categoryId);
await writeMenuCategories(nextCategories, {
snapshotLabel: `Removed category: ${removedCategory?.name ?? categoryId}`,
});
return true;
}
export async function updateMenuCategory(
categoryId: string,
updates: Partial<Pick<MenuCategory, 'name'>>,
): Promise<MenuCategory | null> {
const categories = await readMenuCategories();
let updatedCategory: MenuCategory | null = null;
const nextCategories = categories.map((category) => {
if (category.id !== categoryId) return category;
updatedCategory = {
...category,
...(typeof updates.name === 'string' ? { name: updates.name.trim() } : {}),
};
return updatedCategory;
});
if (!updatedCategory) return null;
const categoryName =
nextCategories.find((category) => category.id === categoryId)?.name ?? categoryId;
await writeMenuCategories(nextCategories, {
snapshotLabel: `Updated category: ${categoryName}`,
});
return updatedCategory;
}
export function clearMenuCache(): void {
memoryCache = null;
}