[Demo] Add admin demo-mode toggle + generic website ingest source
CI/CD / CI · dotnet build (push) Failing after 1m40s
CI/CD / Deploy · hamkadr (push) Has been skipped

- AppSetting: DemoMode, WebsitesEnabled, WebsiteUrls
- Facility.IsDemo flag; SeedData split into SeedReferenceAsync (always)
  + SeedDemoAsync/ClearDemoAsync (idempotent, toggleable at runtime)
- WebsiteListingSource: scrape any admin-configured URL (og:title + content)
- Admin Settings: seed/clear demo card, demo-mode checkbox, website source
  fields; Program.cs seeds demo when DemoMode on (or in Development)
- EF migration DemoModeAndWebsites

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 13:43:07 +03:30
parent eae38373b9
commit 0c0449c2b9
11 changed files with 1341 additions and 149 deletions
@@ -12,6 +12,15 @@
</div>
<div class="container section" style="max-width:680px;">
@if (Model.DemoMsg is not null) { <div class="alert alert-success">@Model.DemoMsg</div> }
<div class="card card-pad" style="margin-bottom:14px;">
<h3 style="margin-top:0;">حالت نمایشی (Demo)</h3>
<p class="muted" style="font-size:13px; margin-top:0;">داده‌های نمونه‌ی تهران را برای نمایش/دمو روی سایت قرار بده یا حذف کن. (تیک «حالت نمایشی» پایین را هم بزن تا پس از هر استقرار دوباره ساخته شود.)</p>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<form method="post"><button asp-page-handler="SeedDemo" class="btn btn-outline">ساخت داده نمونه</button></form>
<form method="post" onsubmit="return confirm('همه داده‌های نمونه حذف شوند؟');"><button asp-page-handler="ClearDemo" class="btn btn-outline" style="color:var(--danger); border-color:var(--danger);">حذف داده نمونه</button></form>
</div>
</div>
@if (Model.SmsTest is not null) { <div class="alert alert-success">@Model.SmsTest</div> }
<form method="post" class="card card-pad" style="margin-bottom:14px; display:flex; gap:8px; align-items:end; flex-wrap:wrap;">
<div class="filter-group" style="margin:0; flex:1; min-width:160px;">
@@ -123,6 +132,27 @@
<p class="muted" style="font-size:12px; margin:4px 0 0;">آگهی‌های تکراری به‌صورت خودکار رد می‌شوند؛ هر اجرا فقط آگهی‌های جدید را می‌آورد.</p>
</div>
<div class="filter-group">
<label style="display:flex; align-items:center; gap:8px; font-weight:700;">
<input type="checkbox" name="WebsitesEnabled" value="true" style="width:auto;" checked="@Model.WebsitesEnabled" />
وب‌سایت‌ها (آدرس‌های دلخواه)
</label>
<label style="margin-top:6px;">آدرس صفحه‌ها (هر خط یک URL)</label>
<textarea name="WebsiteUrls" rows="3" dir="ltr" placeholder="https://example.ir/jobs/123">@Model.WebsiteUrls</textarea>
<p class="muted" style="font-size:12px; margin:4px 0 0;">موتور هر آدرس را می‌خواند و متن آگهی را استخراج می‌کند (عنوان og + بدنه محتوا). برای هر صفحه شغلی، آرشیو کانال یا آگهی طبقه‌بندی.</p>
</div>
<hr style="border:none; border-top:1px solid var(--line); margin:18px 0;" />
<h3 style="margin-top:0;">حالت نمایشی (Demo)</h3>
<div class="filter-group">
<label style="display:flex; align-items:center; gap:8px; font-weight:700;">
<input type="checkbox" name="DemoMode" value="true" style="width:auto;" checked="@Model.DemoMode" />
حالت نمایشی فعال باشد — داده‌های نمونه پس از هر استقرار به‌صورت خودکار ساخته شوند
</label>
<p class="muted" style="font-size:12px; margin:4px 0 0;">برای ساخت/حذف فوری داده‌های نمونه از کارت بالای همین صفحه استفاده کن.</p>
</div>
<hr style="border:none; border-top:1px solid var(--line); margin:18px 0;" />
<h3 style="margin-top:0;">پیامک ورود (OTP) — کاوه‌نگار</h3>