ef15fd6247
Full backend implementation: - Multi-tenant cafe/restaurant management (menus, orders, tables, staff) - POS order flow with ZarinPal and Snappfood payment integration - OTP authentication via Kavenegar SMS - QR digital menu with public discover/finder endpoints - Customer loyalty, coupons, CRM - PostgreSQL via EF Core, Redis for caching/sessions - Background jobs, webhook handlers - Full migration history Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
102 lines
3.5 KiB
C#
102 lines
3.5 KiB
C#
using System.Text.Json;
|
|
|
|
namespace Meezi.Infrastructure.Data;
|
|
|
|
public static class MenuImageManifest
|
|
{
|
|
private static IReadOnlyDictionary<string, string>? _urls;
|
|
private static string? _defaultDrinkUrl;
|
|
private static string? _defaultFoodUrl;
|
|
|
|
private const string FallbackDrinkUrl =
|
|
"https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=600&auto=format&fit=crop";
|
|
|
|
private const string FallbackFoodUrl =
|
|
"https://images.unsplash.com/photo-1546793665-c74683f339c1?w=600";
|
|
|
|
public static string? GetImageUrl(string itemId)
|
|
{
|
|
EnsureLoaded();
|
|
return _urls!.TryGetValue(itemId, out var url) ? url : null;
|
|
}
|
|
|
|
public static string GetDefaultDrinkImageUrl()
|
|
{
|
|
EnsureLoaded();
|
|
return _defaultDrinkUrl ?? FallbackDrinkUrl;
|
|
}
|
|
|
|
public static string GetDefaultFoodImageUrl()
|
|
{
|
|
EnsureLoaded();
|
|
return _defaultFoodUrl ?? FallbackFoodUrl;
|
|
}
|
|
|
|
public static string ResolveImageUrl(string itemId, string fallback)
|
|
=> GetLocalImageOverride(itemId) ?? fallback;
|
|
|
|
/// <summary>Only imported Kaggle/upload paths override Food-101 fallbacks (not stale CDN URLs in manifest).</summary>
|
|
public static string? GetLocalImageOverride(string itemId)
|
|
{
|
|
var url = GetImageUrl(itemId);
|
|
if (string.IsNullOrWhiteSpace(url)) return null;
|
|
if (url.StartsWith("/uploads/", StringComparison.OrdinalIgnoreCase)
|
|
|| url.StartsWith("uploads/", StringComparison.OrdinalIgnoreCase))
|
|
return url.StartsWith('/') ? url : $"/{url}";
|
|
return null;
|
|
}
|
|
|
|
private static void EnsureLoaded()
|
|
{
|
|
if (_urls is not null) return;
|
|
LoadFromDisk();
|
|
}
|
|
|
|
private static void LoadFromDisk()
|
|
{
|
|
var paths = new[]
|
|
{
|
|
Path.Combine(AppContext.BaseDirectory, "data", "menu-image-manifest.json"),
|
|
Path.Combine(Directory.GetCurrentDirectory(), "data", "menu-image-manifest.json"),
|
|
Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "data", "menu-image-manifest.json")),
|
|
Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "data", "menu-image-manifest.json")),
|
|
};
|
|
|
|
foreach (var path in paths)
|
|
{
|
|
if (!File.Exists(path)) continue;
|
|
try
|
|
{
|
|
var json = File.ReadAllText(path);
|
|
var doc = JsonDocument.Parse(json);
|
|
if (doc.RootElement.TryGetProperty("defaults", out var defaults))
|
|
{
|
|
if (defaults.TryGetProperty("drink", out var drink))
|
|
_defaultDrinkUrl = drink.GetString();
|
|
if (defaults.TryGetProperty("food", out var food))
|
|
_defaultFoodUrl = food.GetString();
|
|
}
|
|
|
|
var map = new Dictionary<string, string>(StringComparer.Ordinal);
|
|
if (doc.RootElement.TryGetProperty("items", out var items))
|
|
{
|
|
foreach (var prop in items.EnumerateObject())
|
|
{
|
|
if (prop.Value.TryGetProperty("imageUrl", out var url))
|
|
map[prop.Name] = url.GetString() ?? "";
|
|
}
|
|
}
|
|
|
|
_urls = map;
|
|
return;
|
|
}
|
|
catch
|
|
{
|
|
// try next path
|
|
}
|
|
}
|
|
|
|
_urls = new Dictionary<string, string>(StringComparer.Ordinal);
|
|
}
|
|
}
|