fix: sidebar accordion + koja slug + support ticket LINQ crash
CI/CD / CI · API (dotnet build + test) (push) Successful in 5m50s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m3s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 48s
CI/CD / Deploy · all services (push) Has been cancelled
CI/CD / CI · API (dotnet build + test) (push) Successful in 5m50s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m3s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 48s
CI/CD / Deploy · all services (push) Has been cancelled
Sidebar:
- All groups start collapsed on first load (v4 storage key resets old state)
- Opening one group closes all others (accordion)
- Navigating to a section opens only that section's group
Koja slug:
- SlugHelper: Persian->Latin transliteration, slug validation
- Registration accepts optional custom slug; auto-derives from cafe name
- Slug can be updated from dashboard Settings -> Profile
- Settings PATCH validates uniqueness (SLUG_TAKEN) and format (INVALID_SLUG)
- koja.meezi.ir/{slug} now redirects to /fa/cafe/{slug} (short URL support)
Bug fix:
- SupportTicketService: cafeId/status filters applied before Select() projection
to fix EF "could not be translated" crash on the support tickets page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Meezi.Core.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Converts Persian/Arabic café names to URL-safe Latin slugs.
|
||||
/// Used for Koja profile URLs (koja.meezi.ir/fa/cafe/{slug}).
|
||||
/// </summary>
|
||||
public static partial class SlugHelper
|
||||
{
|
||||
private static readonly Dictionary<char, string> PersianToLatin = new()
|
||||
{
|
||||
// Alef variants
|
||||
{ 'آ', "a" }, { 'ا', "a" }, { 'أ', "a" }, { 'إ', "a" },
|
||||
// Ba, Pa, Ta, Tha
|
||||
{ 'ب', "b" }, { 'پ', "p" }, { 'ت', "t" }, { 'ث', "s" },
|
||||
// Jim, Che, He, Khe
|
||||
{ 'ج', "j" }, { 'چ', "ch" }, { 'ح', "h" }, { 'خ', "kh" },
|
||||
// Dal, Zal, Re, Ze, Zhe
|
||||
{ 'د', "d" }, { 'ذ', "z" }, { 'ر', "r" }, { 'ز', "z" }, { 'ژ', "zh" },
|
||||
// Sin, Shin, Sad, Zad
|
||||
{ 'س', "s" }, { 'ش', "sh" }, { 'ص', "s" }, { 'ض', "z" },
|
||||
// Ta, Za, Ain, Ghain
|
||||
{ 'ط', "t" }, { 'ظ', "z" }, { 'ع', "a" }, { 'غ', "gh" },
|
||||
// Fa, Ghaf, Kaf (Arabic+Persian), Gaf
|
||||
{ 'ف', "f" }, { 'ق', "gh" }, { 'ک', "k" }, { 'ك', "k" }, { 'گ', "g" },
|
||||
// Lam, Mim, Nun, Vav, He, Ye
|
||||
{ 'ل', "l" }, { 'م', "m" }, { 'ن', "n" }, { 'و', "v" },
|
||||
{ 'ه', "h" }, { 'ی', "i" }, { 'ي', "i" },
|
||||
// Special
|
||||
{ 'ئ', "y" }, { 'ء', "" }, { 'ة', "t" }, { 'ى', "a" }, { 'ؤ', "o" },
|
||||
// Persian digits
|
||||
{ '۰', "0" }, { '۱', "1" }, { '۲', "2" }, { '۳', "3" }, { '۴', "4" },
|
||||
{ '۵', "5" }, { '۶', "6" }, { '۷', "7" }, { '۸', "8" }, { '۹', "9" },
|
||||
// Arabic-Indic digits
|
||||
{ '٠', "0" }, { '١', "1" }, { '٢', "2" }, { '٣', "3" }, { '٤', "4" },
|
||||
{ '٥', "5" }, { '٦', "6" }, { '٧', "7" }, { '٨', "8" }, { '٩', "9" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts a café name (Persian or Latin) to a URL-safe lowercase slug.
|
||||
/// Returns an empty string if no valid characters can be extracted.
|
||||
/// </summary>
|
||||
public static string Slugify(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input)) return string.Empty;
|
||||
|
||||
var sb = new StringBuilder(input.Length * 2);
|
||||
foreach (var ch in input)
|
||||
{
|
||||
if (PersianToLatin.TryGetValue(ch, out var latin))
|
||||
{
|
||||
sb.Append(latin);
|
||||
}
|
||||
else if (char.IsAsciiLetterOrDigit(ch))
|
||||
{
|
||||
sb.Append(char.ToLowerInvariant(ch));
|
||||
}
|
||||
else if (ch is ' ' or '-' or '_' or '\t')
|
||||
{
|
||||
sb.Append('-');
|
||||
}
|
||||
// else: skip punctuation/unsupported characters
|
||||
}
|
||||
|
||||
// Collapse consecutive hyphens and trim
|
||||
return MultipleHyphen().Replace(sb.ToString(), "-").Trim('-');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the slug is a valid Koja URL slug:
|
||||
/// 2–80 lowercase letters, digits, or internal hyphens. Must start and end with a letter/digit.
|
||||
/// </summary>
|
||||
public static bool IsValidSlug(string? slug)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(slug)) return false;
|
||||
if (slug.Length < 2 || slug.Length > 80) return false;
|
||||
return ValidSlugPattern().IsMatch(slug);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"-{2,}")]
|
||||
private static partial Regex MultipleHyphen();
|
||||
|
||||
[GeneratedRegex(@"^[a-z0-9][a-z0-9\-]*[a-z0-9]$")]
|
||||
private static partial Regex ValidSlugPattern();
|
||||
}
|
||||
Reference in New Issue
Block a user