Applicants: auto-tags + deep search w/ highlight; never delete (archive instead)
CI/CD / CI · dotnet build (push) Successful in 2m1s
CI/CD / Deploy · hamkadr (push) Successful in 2m36s

- Tags: parser extracts cert/skill keywords (mmt, ICU/CCU, دیالیز, اتاق عمل,
  اورژانس, مسئول فنی, پروانه‌دار…) + role + city into TalentListing.Tags
  (+ migration); shown as chips on cards.
- Deep search on /Talent: «جستجوی عمیق» box does Postgres ILIKE across
  tags, description, person, area, role, city (every term must match);
  matches are highlighted with <mark> via SearchHighlight.
- Never delete: ShiftStatus.Archived + the admin «بایگانی گروهی» action now
  ARCHIVES aggregated posts (hidden from site, kept in DB) and leaves the
  raw crawl rows intact — a permanent archive for future analytics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-08 11:25:32 +03:30
parent e4dc5180ad
commit 6b657c7795
15 changed files with 1752 additions and 22 deletions
@@ -0,0 +1,27 @@
using System.Net;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Html;
namespace JobsMedical.Web.Services;
/// <summary>Wraps query terms in &lt;mark&gt; for result highlighting (HTML-safe).</summary>
public static class SearchHighlight
{
public static HtmlString Mark(string? text, string? query)
{
if (string.IsNullOrEmpty(text)) return HtmlString.Empty;
var encoded = WebUtility.HtmlEncode(text);
if (string.IsNullOrWhiteSpace(query)) return new HtmlString(encoded);
var terms = query.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Where(t => t.Length >= 2)
.Select(t => Regex.Escape(WebUtility.HtmlEncode(t)))
.Distinct()
.ToList();
if (terms.Count == 0) return new HtmlString(encoded);
var pattern = string.Join("|", terms);
var marked = Regex.Replace(encoded, pattern, m => $"<mark>{m.Value}</mark>", RegexOptions.IgnoreCase);
return new HtmlString(marked);
}
}