Commit Graph

150 Commits

Author SHA1 Message Date
soroush.asadi c46e628f6a [Profile] Show applicant avatar + resume to employers; profile-completeness nudge
CI/CD / CI · dotnet build (push) Successful in 1m33s
CI/CD / Deploy · hamkadr (push) Successful in 2m7s
Employer Listings: each applicant row now shows their avatar (or initials) and a «مشاهده رزومه» link when they uploaded one (served via /resume/{id}, already access-controlled to the receiving employer). Applicant projection avoids loading avatar/resume blobs. Me panel: a nudge banner prompts users to complete their profile (name/photo/resume) when any is missing, linking to /Me/Profile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 07:14:03 +03:30
soroush.asadi e633463906 [Profile] Editable profile (avatar + resume) + role-based profile dropdown menu
CI/CD / CI · dotnet build (push) Successful in 44s
CI/CD / Deploy · hamkadr (push) Successful in 57s
Every user gets a full editable profile at /Me/Profile: name, role, city, specialty/title, license, years, bio + avatar image upload + resume upload (PDF/image). Avatar/resume stored in-DB on User (migration, 5 nullable columns). Endpoints: /avatar/{id} (public) and /resume/{id} (owner, admin, or an employer who received that user's application). Nav: replaced the scattered action links with an avatar button + dropdown listing all of the user's pages by role (profile, کارجو panel, alerts, preferences, notifications; employer panel; admin panel + settings; logout) — shows the avatar image or initials; collapses into the burger menu on mobile; closes on outside-click.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:49:40 +03:30
soroush.asadi 167d263560 [Applications] Applicant pipeline: employer accept/reject + status to applicant
CI/CD / CI · dotnet build (push) Successful in 43s
CI/CD / Deploy · hamkadr (push) Successful in 43s
InterestEvent gains a Status (ApplicationStatus: Interested→Accepted/Rejected; migration, default Interested). Employer/Listings shows each applicant's status with پذیرفتن/رد buttons (ownership-checked handlers update the status and notify the applicant via bell/SSE/push linking to the listing). The کارجو panel (/Me) now shows a status badge (در انتظار بررسی / پذیرفته شد / رد شد) on each applied shift/job. Reusable _ApplicantRow partial for the employer list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:27:53 +03:30
soroush.asadi 60c1997642 [Notify] Notify the employer when someone applies to their listing
CI/CD / CI · dotnet build (push) Successful in 40s
CI/CD / Deploy · hamkadr (push) Successful in 55s
Applications were recorded but the facility owner was never pinged. NotificationService gains NotifyShiftApplicationAsync/NotifyJobApplicationAsync (look up the facility owner, notify via in-app bell + live SSE + push, linking to /Employer/Listings). InterestService fires them when a NEW Apply event is saved (after the duplicate guard, so no repeat pings; View/Save/Dismiss don't notify). No-op for admin-managed facilities with no owner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:43:52 +03:30
soroush.asadi 02d635415b [Local] Use free host ports for local stack (app 18080, db 5544)
CI/CD / CI · dotnet build (push) Successful in 48s
CI/CD / Deploy · hamkadr (push) Successful in 8s
8080/8088/5434 were occupied by other local containers; pick non-clashing ports and fix LOCAL.md encoding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:30:11 +03:30
soroush.asadi 6f02b1a0e9 [Local] Dockerized local test stack + always-show OTP in Development
CI/CD / CI · dotnet build (push) Successful in 32s
CI/CD / Deploy · hamkadr (push) Successful in 57s
Add docker-compose.local.yml + Dockerfile.local (public MS images + Liara NuGet) to run the whole app with a throwaway Postgres in one command for local testing, plus LOCAL.md. OtpService now never calls Kavenegar in the Development environment and always returns the code so the login page shows it on screen — guarantees local logins work with no SMS. Production behavior unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:21:47 +03:30
soroush.asadi 2170ba250c [UI] Mobile sticky action bar on shift/job details (native-app feel)
CI/CD / CI · dotnet build (push) Successful in 46s
CI/CD / Deploy · hamkadr (push) Successful in 1m6s
On mobile the action panel (CTA + map) stacked at the very bottom, so users had to scroll the whole page to act. Add a fixed bottom action bar (<=860px) with the primary «اعلام تمایل» button + a quick save, always reachable like a native app; when contact is revealed it becomes a «تماس با مرکز» tel: button. The in-aside primary CTA is hidden on mobile (.aside-apply) to avoid duplication, and pages get bottom padding (.has-action-bar) so the bar never covers content. Desktop layout unchanged (bar hidden, sidebar CTA shown).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:12:34 +03:30
soroush.asadi 86809190e7 [Map] Render real Neshan map on shift/job detail pages
CI/CD / CI · dotnet build (push) Successful in 1m6s
CI/CD / Deploy · hamkadr (push) Successful in 1m12s
The detail pages showed a 'map coming later' placeholder. Add a read-only Neshan web map (Leaflet SDK) showing the facility marker when NeshanMapKey is set, plus a 'مسیریابی در نشان' directions link; falls back to coordinates when no key. New _NeshanMap shared partial loads the SDK and inits #facmap. Shift/Job Details models now expose MapKey via SettingsService. Coordinates are emitted with InvariantCulture so the decimal point/digits don't break JS. The facility registration picker already used Neshan; this reuses the same key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:57:49 +03:30
soroush.asadi b1e474ba33 [Ingest] Per-source proxy toggle instead of one global switch
CI/CD / CI · dotnet build (push) Successful in 56s
CI/CD / Deploy · hamkadr (push) Successful in 1m6s
Each ingestion source now decides independently whether to route through the proxy: added TelegramUseProxy/BaleUseProxy/DivarUseProxy/MedjobsUseProxy/WebsitesUseProxy flags (migration). ScrapeHttpClients.For(s, useProxy) takes the source's own flag; a source is proxied only when its flag is on AND a proxy URL is set. Settings 'sources' tab: removed the global enable checkbox, kept the proxy address field, and added an «از پروکسی استفاده شود» checkbox under each source. Old IngestProxyEnabled column kept for compatibility but no longer gates routing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:46:48 +03:30
soroush.asadi cde6b68a39 [Admin] Redesign Settings as sidebar tabs + style password/toggle fields
CI/CD / CI · dotnet build (push) Successful in 35s
CI/CD / Deploy · hamkadr (push) Successful in 59s
Split the long settings page into 7 sidebar tabs (publish+AI, sources, channels, SMS, push, map, demo) with a single form so one Save persists everything; seed/clear/test are submit buttons targeting their handlers via asp-page-handler. Boolean settings now render as clean .toggle-row cards. CSS fix: the form input rule omitted input[type=password] (and url/email/search), so API-key/VAPID/token fields were unstyled — added them, plus accent-color + sizing for checkboxes/radios. Active tab persists across handler posts via sessionStorage; layout collapses to a horizontal tab strip on mobile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:25:06 +03:30
soroush.asadi 213faadf55 [Alerts] Customizable job alerts + Help capabilities showcase
CI/CD / CI · dotnet build (push) Successful in 1m8s
CI/CD / Deploy · hamkadr (push) Successful in 1m7s
Job alerts (هشدار شغلی): users save what they want — scope (shift/job/both), role, city, shift type, employment type, minimum pay — and get notified when an employer posts a match. New JobAlert model + AlertScope enum + DbContext (user-cascade, role set-null, IsActive index) + migration. /Me/Alerts page to create/pause/delete alerts; entry point added to the کارجو panel. NotificationService.NotifyNewShift/Job now unions preference matches with active-alert matches (deduped) so alert owners are notified on publish. Help page gains an 'امکانات همکادر' capability showcase grid (with a 'ساخت هشدار شغلی' CTA) so the page demonstrates what the app does.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:17:56 +03:30
soroush.asadi 42deac1261 [Fix] Mobile hamburger invisible + tour spotlighting hidden nav
CI/CD / CI · dotnet build (push) Successful in 52s
CI/CD / Deploy · hamkadr (push) Successful in 57s
Hamburger bars used the undefined var(--text) (this theme defines --ink), so they rendered transparent and the button looked blank on mobile. Switch the three var(--text) uses (burger bars, toast, tour bubble) to var(--ink). Tour: visible() used offsetParent/rect, which the collapsed mobile nav (visibility:hidden) still satisfies, so the tour tried to spotlight hidden links and looked broken on mobile. Use element.checkVisibility({checkVisibilityCSS}) (with a computed-style ancestor-walk fallback) so only truly-visible targets are toured — mobile now shows welcome + menu, desktop shows the full nav walkthrough.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:06:23 +03:30
soroush.asadi cea27c8684 [Ingest] Route scraping through an optional V2Ray/Xray proxy (Telegram in Iran)
CI/CD / CI · dotnet build (push) Successful in 53s
CI/CD / Deploy · hamkadr (push) Successful in 1m12s
Telegram and some sources are filtered in Iran. .NET cannot speak vmess/vless/trojan, so add an Xray sidecar (compose service 'xray', behind the 'proxy' profile) that converts the admin's config into a local SOCKS5 proxy (xray:10808). New ScrapeHttpClients provider builds a proxied or direct HttpClient (WebProxy supports socks5/socks4/http) cached per proxy URL; all five ingestion sources (Telegram/Bale/Divar/Medjobs/Websites) now use it. Admin settings gain IngestProxyEnabled + IngestProxyUrl (migration; UI under sources). Added deploy/xray/config.json template + README with vmess/vless/trojan examples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:53:17 +03:30
soroush.asadi 698565c460 [Help] Add help/learning page + interactive guided app tour
CI/CD / CI · dotnet build (push) Successful in 46s
CI/CD / Deploy · hamkadr (push) Successful in 1m10s
New /Help page: quick-start, separate guides for کادر درمان (search/near-me/preferences/اعلام تمایل/saved) and مراکز درمانی (register/post/verify-with-docs/applicants), notifications+install, report/complaint, and an FAQ accordion. Self-hosted tour.js (no CDN, RTL): spotlights elements via data-tour hooks in the nav, auto-runs once for new visitors on the home page (localStorage flag), re-runnable from the Help page button or ?tour=1; skips steps whose target is hidden so it works on mobile/other pages. Help linked from nav + footer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:39:03 +03:30
soroush.asadi 70bab6b916 [TEMP] Remove master OTP backdoor (956423)
Admin access is restored, so drop the temporary always-accepted login code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:39:03 +03:30
soroush.asadi 02eb761488 [TEMP] Master OTP code to recover admin access while SMS is broken
CI/CD / CI · dotnet build (push) Successful in 54s
CI/CD / Deploy · hamkadr (push) Successful in 55s
SMS (Kavenegar) is misconfigured so OTP codes are not delivered and Production does not show the code on screen, locking admins out. Accept a temporary master code (956423) for any phone in OtpService.Verify so we can log in and fix the gateway key. MUST be removed once SMS works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:22:03 +03:30
soroush.asadi c7e4bf059e [Legal] Add Privacy, Rules, and Terms of Use pages (Persian/RTL) + footer links
CI/CD / CI · dotnet build (push) Successful in 1m0s
CI/CD / Deploy · hamkadr (push) Successful in 1m11s
Three policy pages tailored to همکادر: /Privacy (data collected, use, sharing, no Google services, cookies, security), /Rules (accurate info, allowed listings, conduct, prohibited content, verification badge meaning, reports), /Terms (intermediary nature/no employment guarantee, account terms, scraped-listing disclaimer, liability, IP, governing law = Iran). Linked from the footer; .legal long-form reading style added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:48:10 +03:30
soroush.asadi acec73a3d2 [SMS] Diagnose Kavenegar failures; sanitize API key in URL path
CI/CD / CI · dotnet build (push) Successful in 1m2s
CI/CD / Deploy · hamkadr (push) Successful in 57s
A 404 from Kavenegar means a malformed URL path, and the API key sits in the path unescaped, so a stray space/newline/slash in the saved key breaks it. Strip whitespace/control chars from the key before building the URL and bail early if it contains a slash. Also read and log Kavenegar's response body and return.status: success now requires HTTP 2xx AND status==200 (a wrong key/template often returns HTTP 200 with an error status). Logs include the apiStatus, message, a Persian hint per error code, and a body snippet so the real cause is visible. No schema change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:40:05 +03:30
soroush.asadi 1f34fd126f [Verify+Complaints] Facility document review + facility complaints; card location line
CI/CD / CI · dotnet build (push) Successful in 1m27s
CI/CD / Deploy · hamkadr (push) Successful in 1m13s
Card: move location to its own line above the date in the shift card (job card already did). Verification workflow: employers upload documents (license/permit) on a new Employer/Verify page; uploading marks the facility Pending. Admins see pending facilities with their documents on Admin/Facilities, can download each doc, and approve (تأیید شد) or reject with a reason. Documents stored as bytea in the DB (survives deploys via the existing volume); served only to the owner or an admin via /facility-doc/{id}. Facility model gains Verification status enum + note + requested-at; IsVerified kept in sync. Complaints: registered users/visitors can file a شکایت about a facility from shift/job detail pages (targets ReportTargetType.Facility, surfaces in Admin/Reports as مرکز). Migration backfills existing verified facilities to Verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:26:15 +03:30
soroush.asadi 962196d5cb [UI] Make buttons inherit Vazirmatn font
CI/CD / CI · dotnet build (push) Successful in 1m1s
CI/CD / Deploy · hamkadr (push) Successful in 1m14s
Browsers do not inherit font-family on button/input/select/textarea, so all buttons (the contact-reveal box is mostly buttons) rendered in the UA default font instead of Vazirmatn, clashing with the rest of the page. Add a font-family: inherit reset for form controls and on .btn. Verified the CTA and action buttons now compute to Vazirmatn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:07:05 +03:30
soroush.asadi 8fad9c1bb6 [Admin] Notification channel toggles (web/SMS/push active-deactive)
CI/CD / CI · dotnet build (push) Successful in 50s
CI/CD / Deploy · hamkadr (push) Successful in 1m1s
Add a 'notification channels' card at the top of admin Settings with three master on/off checkboxes: web/in-app (new WebNotificationsEnabled, default true), SMS (existing SmsEnabled), and Web Push (existing PushEnabled). Removed the duplicate enable checkboxes from the SMS and Push sections so each binds once. NotificationService now gates the in-app + live SSE channel on WebNotificationsEnabled; push self-gates on PushEnabled. Migration defaults the new column to true so existing installs keep web notifications on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:56:40 +03:30
soroush.asadi 91c953ff5d [chore] Stop tracking dev run logs (gitignore run.log/run.err)
CI/CD / CI · dotnet build (push) Successful in 40s
CI/CD / Deploy · hamkadr (push) Successful in 1m0s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:42:47 +03:30
soroush.asadi 716433ce20 [Notify] Add live in-app notifications over SSE (Iran-friendly)
CI/CD / CI · dotnet build (push) Has been cancelled
CI/CD / Deploy · hamkadr (push) Has been cancelled
Web Push is delivered by the browser vendor's push service (Chrome to Google FCM), which is filtered in Iran, so background push is unreliable. Add a Server-Sent Events channel over our own origin that always reaches users while the tab/PWA is open: NotificationHub (in-memory pub/sub), a /notifications/stream SSE endpoint (auth-gated, keep-alive pings, nginx no-buffer), and NotificationService now publishes each saved notification to the hub. Client updates the bell badge instantly, shows a toast, and fires a local OS notification via the service worker when permission is granted (no push server). Web Push stays as best-effort for closed-app reach. Verified end-to-end: login, open stream, broadcast, event delivered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:42:16 +03:30
soroush.asadi d2a7b18cb3 [UI] Fix navbar bell Razor leak, add Settings link, tidy desktop bar
CI/CD / CI · dotnet build (push) Successful in 1m13s
CI/CD / Deploy · hamkadr (push) Successful in 56s
Wrap the bell label in a span so the conditional parses as Razor code instead of leaking as literal text (Persian word char before the at-sign triggered email detection). Bell is icon-only on desktop, label shows in the mobile dropdown. Add a Settings link to admin actions so the demo-mode page is reachable from the navbar. Tighten desktop nav/action spacing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:06:44 +03:30
soroush.asadi 943c3b7b3c [CI] Fix NuGet restore: use Liara mirror (Nexus TLS chain incomplete)
CI/CD / CI · dotnet build (push) Successful in 51s
CI/CD / Deploy · hamkadr (push) Successful in 57s
mirror.soroushasadi.com serves a leaf-only TLS chain (no intermediate).
.NET on Linux does not auto-fetch the intermediate via AIA like Windows
does, so CI/Docker restores fail with NU1301 PartialChain. Switch the
Linux build configs (CI inline config + nuget.docker.config) to the
Liara mirror, which serves a complete chain. Also disable NuGetAudit to
avoid the api.nuget.org (filtered) 100s timeout + NU1900 noise.

Local dev nuget.config keeps Nexus primary (Windows resolves the chain).
Re-add Nexus to the Linux configs once nginx serves fullchain.pem.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:36:00 +03:30
soroush.asadi 9e047c96ed [UI] Responsive hamburger nav for mobile
CI/CD / CI · dotnet build (push) Failing after 1m40s
CI/CD / Deploy · hamkadr (push) Has been skipped
- _Layout: wrap nav + actions in .nav-collapse; add CSS-only burger
  (checkbox toggle) + always-visible mobile bell next to it
- site.css: desktop layout unchanged; <=860px collapses nav into a
  full-width dropdown (column links, stacked actions), animated open
- Login (ورود) + logout render as full-width buttons in the menu
  instead of overflowing the header row

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:07:22 +03:30
soroush.asadi 0c0449c2b9 [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>
2026-06-04 13:43:07 +03:30
soroush.asadi eae38373b9 Admin suite: monitoring dashboard, user management/ban, broadcast, reports, SMS test
CI/CD / CI · dotnet build (push) Failing after 1m40s
CI/CD / Deploy · hamkadr (push) Has been skipped
- /Admin/Overview: platform monitoring stats (users by role, facilities, listings, applies, push subs, queue, reports, bans)
- /Admin/Users: search/filter + ban/unban (User.IsBanned + reason); banned users blocked at login
- /Admin/Broadcast: send announcement (in-app + web push) to all / staff / employers via NotificationService
- Reports: report button on shift/job detail → /report endpoint → /Admin/Reports (resolve/dismiss)
- Settings: 'send test SMS' button; admin cross-nav links; SMS API config already in place
- migration AdminBanReports; verified overview/users/broadcast/report persist

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 13:19:20 +03:30
soroush.asadi b46bd49c32 Wire Web Push broadcaster: lock-screen pushes ride the in-app notifications
CI/CD / CI · dotnet build (push) Failing after 1m40s
CI/CD / Deploy · hamkadr (push) Has been skipped
- nuget.config with Soroush Nexus + Liara mirrors (nuget.org filtered); added WebPush 1.0.12
- PushNotifier: VAPID send to a user's subscriptions, prunes dead (404/410); config from AppSetting
- NotificationService fans out a Web Push to matched users' subscribed browsers after creating in-app notifications (best-effort; no-op until admin enables push + sets VAPID)
- Build verified through the mirrors; app boots with PushNotifier wired

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:23:50 +03:30
soroush.asadi 10d4727bd5 Notify matching users when a new shift/job is posted (in-app notifications)
CI/CD / CI · dotnet build (push) Failing after 1m40s
CI/CD / Deploy · hamkadr (push) Has been skipped
- Notification entity + NotificationService: on publish, notify users whose saved prefs match the listing (role/city/+shift type); users with no preference aren't spammed
- Wired into PostShift, PostJob, and Admin Review publish
- 🔔 bell with unread count in the header (@inject) + /Me/Notifications page (mark-all-read on open)
- Reliable in-app delivery (works in Iran without FCM); Web Push can ride the same records later
- Verified: employee pref → employer posts matching shift → employee bell=۱ + 'شیفت جدید: پزشک عمومی'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:56:07 +03:30
soroush.asadi a02eb6a985 PWA: installable app (web/win/android/ios) + download/help page + push notifications
CI/CD / CI · dotnet build (push) Successful in 40s
CI/CD / Deploy · hamkadr (push) Successful in 55s
- manifest.webmanifest + service worker (offline shell + push + notificationclick) + PNG icons (192/512/apple) + iOS meta + SW registration → installable everywhere
- /Download page: per-OS install help (web/windows/android/ios), install button (beforeinstallprompt), 'enable notifications' flow, usage guide, Bazaar/TWA note; nav + footer links
- Web Push foundation: WebPushSubscription entity + /push/subscribe (stores), VAPID + push settings in /Admin/Settings, on-device local notification; server broadcast documented (WebPush via Nexus)
- docs/PWA-TWA.md: VAPID keygen, server-push wiring, Bubblewrap→Cafe Bazaar + assetlinks steps
- Verified: manifest/sw/icons served, download page, subscribe stores (200), layout wired

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:23:13 +03:30
soroush.asadi 9a92da42e6 Facility location: click-to-pick Neshan map + 'my current location'
CI/CD / CI · dotnet build (push) Successful in 37s
CI/CD / Deploy · hamkadr (push) Successful in 40s
- RegisterFacility: '📍 موقعیت فعلی من' (browser geolocation, always available) + Neshan Leaflet map (click/drag marker → fills lat/lng) when a Neshan web key is set; graceful fallback to manual coords without a key
- AppSetting.NeshanMapKey configured in /Admin/Settings (Google Maps is blocked in Iran); migration
- Verified: location button + inputs render always; map + SDK render once the key is saved

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:47:33 +03:30
soroush.asadi 17d38431bf Add SEO sitemap/robots + real SMS OTP (Kavenegar, admin-configured)
CI/CD / CI · dotnet build (push) Successful in 31s
CI/CD / Deploy · hamkadr (push) Successful in 56s
- /sitemap.xml (static pages + open shifts + fresh jobs, respecting expiry) + /robots.txt (blocks /Admin,/Employer); base URL from forwarded request → https://hamkadr.ir in prod
- ISmsSender + KavenegarSmsSender (verify/lookup template, sms/send fallback); SMS settings (enabled/apikey/template/sender) in /Admin/Settings; OtpService.IssueAsync sends SMS and stops revealing the code when enabled (dev still shows it); migration

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:27:21 +03:30
soroush.asadi 6d2ad6f87e Hide + archive stale listings (old jobs, expired shifts)
CI/CD / CI · dotnet build (push) Successful in 37s
CI/CD / Deploy · hamkadr (push) Successful in 48s
- ListingPolicy.JobFreshnessDays=30: public /Jobs and home hide jobs older than the cutoff (shifts already require Date>=today)
- ListingArchiver flips stale Open→Expired: shifts past their date, jobs older than the cutoff. Runs at startup and on every IngestionWorker cycle (independent of ingestion being enabled)
- Verified: backdated job dropped off /Jobs (6→5) and was archived to Expired on the sweep

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:57:06 +03:30
soroush.asadi 178e44c4da Anti-abuse hardening: hourly posting rate limit + captcha on facility registration
CI/CD / CI · dotnet build (push) Successful in 26s
CI/CD / Deploy · hamkadr (push) Successful in 40s
- SubmissionGuard.PostingRateExceededAsync: max 20 new listings (shifts+jobs) per account per rolling hour, enforced in PostJob + PostShift
- Captcha + spam-name screen added to /Employer/RegisterFacility

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:45:12 +03:30
soroush.asadi 0587e040d9 Add anti-abuse: built-in captcha + garbage/duplicate guard
CI/CD / CI · dotnet build (push) Successful in 1m22s
CI/CD / Deploy · hamkadr (push) Successful in 1m36s
- CaptchaService: stateless data-protected math captcha (no Google reCAPTCHA — blocked in Iran), TTL + Persian-digit tolerant; on PostJob + PostShift
- SubmissionGuard: duplicate-position detection (facility+role+date/time for shifts, facility+role+title for jobs), spam/garbage screen on title/description, double-apply prevention
- InterestService: Apply events deduped so an applicant can't apply to the same listing twice
- Verified: wrong captcha rejected, correct publishes, duplicate + garbage blocked

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 06:35:17 +03:30
soroush.asadi e2e26150cb Add medjobs.ir scraper + employer/employee choice at signup
CI/CD / CI · dotnet build (push) Successful in 1m22s
CI/CD / Deploy · hamkadr (push) Successful in 1m37s
- MedjobsListingSource: crawls medjobs.ir sitemaps (ad_listing-sitemapN) → fetches ad pages → title+description → engine (dedupe/parse/validate/publish as SEO job pages). Configured in /Admin/Settings (enable + max ads/run).
- Login/register now asks 'کادر درمان' vs 'کارفرما/مرکز': new accounts get Doctor vs FacilityAdmin role; post-login routes to /Me, /Employer, or /Admin accordingly.
- Verified live: medjobs run fetched real ads into the review queue; employer signup → /Employer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 06:12:10 +03:30
soroush.asadi d828ea9f35 nginx vhost: target mirror-nginx container, cert under mounted soroushasadi dir
CI/CD / CI · dotnet build (push) Successful in 3m24s
CI/CD / Deploy · hamkadr (push) Successful in 8s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 05:32:05 +03:30
soroush.asadi d05b329c7a Align deploy with central nginx: host-published 2569 + paste-in vhost (manual certs)
CI/CD / CI · dotnet build (push) Successful in 1m22s
CI/CD / Deploy · hamkadr (push) Successful in 8s
- Central nginx is containerized and proxies via host IP (171.22.25.73:port), not localhost → publish app on host :2569 (was 127.0.0.1)
- nginx vhost rewritten to match the monolithic config style (server blocks to paste into http{}, manual /etc/ssl/hamkadr certs, proxy_pass 171.22.25.73:2569, $connection_upgrade)
- DEPLOY.md: corrected architecture/ports, removed certbot+sites-available (use manual certs + single nginx.conf)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 05:09:39 +03:30
soroush.asadi f457e4b5ca Use dedicated host port 2569 for hamkadr (avoids the 8090 conflict)
CI/CD / CI · dotnet build (push) Successful in 7m14s
CI/CD / Deploy · hamkadr (push) Successful in 8s
docker-compose.yml HOST_PORT default → 2569; nginx vhost proxy_pass → 127.0.0.1:2569; DEPLOY.md updated. Set HOST_PORT=2569 in the ENV_FILE secret.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 04:58:27 +03:30
soroush.asadi 3c08c1a265 Move ingestion + Telegram/Bale/Divar config to DB-backed admin settings
CI/CD / CI · dotnet build (push) Successful in 6m22s
CI/CD / Deploy · hamkadr (push) Failing after 3s
- AppSetting gains source config: AutoIngestEnabled, IngestIntervalMinutes, Telegram/Bale/Divar enabled+channels/token/queries
- IListingSource.FetchAsync(AppSetting) — sources read config from DB, not IOptions/appsettings; sample source dev-only
- IngestionWorker reads AutoIngest+interval from DB each cycle (toggle at runtime, no redeploy)
- /Admin/Settings gets a 'منابع جمع‌آوری' section; removed Ingestion env/appsettings + compose env vars
- ENV_FILE shrinks to HOST_PORT + POSTGRES_* + ADMIN_PHONE (AI + sources are all in-admin); migration

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 00:44:11 +03:30
soroush.asadi 6cfdd16c42 Add gender requirement (آقا/خانم/فرقی نمی‌کند) + employee (کارجو) panel
CI/CD / CI · dotnet build (push) Successful in 6m23s
CI/CD / Deploy · hamkadr (push) Failing after 6m30s
- Gender enum + GenderRequirement on Shift/JobOpening + Gender on UserPreferences (migration)
- Employer PostShift/PostJob + admin Review have a gender select; parser detects آقا/خانم/مرد/زن
- Gender badge on cards + detail; gender filter on Shifts/Jobs; gender in preferences
- Recommendations exclude listings whose gender requirement conflicts with the person's gender
- Two panels: new /Me employee (کارجو) panel (recommendations + saved + applied + prefs) alongside /Employer; nav routes by role; /Account/Profile → /Me

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 00:19:32 +03:30
soroush.asadi 8f5d926d42 Align CI/CD with soroush method (DrSousan single-app pattern)
CI/CD / CI · dotnet build (push) Successful in 2m58s
CI/CD / Deploy · hamkadr (push) Failing after 6m23s
Audited against working Meezi/DrSousan pipelines. Fixes:
- Single docker-compose.yml is the production stack (api + internal db); folded in docker-compose.prod.yml; dev Postgres → docker-compose.dev.yml
- Dockerfile HEALTHCHECK (bash /dev/tcp) so deploy's docker-inspect Health.Status wait works
- Naming to convention: service api, container hamkadr_api/hamkadr_db, image mirror.soroushasadi.com/hamkadr/api:${API_TAG}
- Workflow rewritten to DrSousan pattern: ci build + deploy (rollback-tag before build, pg_dump backup, stop/rm/up, docker-inspect health-wait with crash detection, scoped image prune)
- environment: block with ${VAR:-default} substitution (no hard-failing env_file); HOST_PORT; .env excluded from image context
- nginx vhost + DEPLOY.md updated to match

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:38:22 +03:30
soroush.asadi 36bb165438 Real channel fetch (Telegram/Bale/Divar) + AI-audited automation engine + CI/CD
- Fetch: Telegram via t.me/s, Bale via Bot API, Divar via web-search (HttpClient, config-gated, graceful)
- AI layer: DB-backed AppSetting (mode auto/manual, thresholds, AI endpoint/model/key/prompt/framework, auto-approve); OpenAI-compatible IAiAuditor (self-host/Iranian endpoints; fails safe to manual)
- Pipeline: fetch → dedupe(hash) → parse → validate → AI audit → Discard/Flag/Queue/auto-publish (resolve-or-create facility)
- Admin: /Admin/Settings automation+AI panel; queue shows confidence + AI verdict; flagged section
- CI/CD: Dockerfile, docker-compose.prod.yml, .gitea/workflows/ci-cd.yml, nginx vhost, DEPLOY.md; forwarded headers + /healthz + prod reference-only seed; ports 22/80/443 only

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:41:02 +03:30
soroush.asadi 931b7b6ffb Add scrape/ingestion engine + validation, and 24h shift hour-range visualization
Scrape engine (Services/Scraping/): pluggable IListingSource (working sample + Telegram/Divar credential-ready stubs) → IngestionService (content-hash dedupe → parse → validate → review queue) → ListingValidator (completeness score + spam screen) → IngestionWorker (config-gated hosted service). RawListing gains ContentHash/Confidence/ValidationNotes; RawListingStatus.Flagged. Admin /Admin gets run-now, source list, confidence + flagged queue.

Hour-range viz: _HourBar 24h timeline bar (colored by type, overnight wrap) on shift cards, recommendation cards, and detail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 08:18:19 +03:30
soroush.asadi 69fa921fbd Add Gitea CI/CD for hamkadr.ir (Nexus build + self-hosted compose deploy)
- .gitea/workflows/ci-cd.yml: dotnet build via mirror.soroushasadi.com; self-hosted deploy with pg_dump backup, rollback tag, scoped recreate, /healthz wait, prune
- Dockerfile (sdk/aspnet 10 via Nexus) + nuget.docker.config + .dockerignore
- docker-compose.prod.yml: app on 127.0.0.1:APP_PORT, Postgres internal-only + named volume
- deploy/nginx-hamkadr.ir.conf + DEPLOY.md (ports: 22/80/443 only; DB never exposed)
- Prod seeds reference data only (no demo listings); ForwardedHeaders for nginx TLS; /healthz endpoint

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 07:52:42 +03:30
soroush.asadi 1647ce4c07 Fix responsive UI: mobile nav, fluid typography, pay-label & calendar overflow
- Mobile nav was display:none (no navigation on phones) → now a scrollable strip on a second header row
- Fluid typography via clamp() for hero/page/section headings + spacing
- Long pay/choice labels (… یا … به انتخاب شما) now wrap in card footers instead of overflowing
- Calendar table scrolls horizontally on small screens (cal-wrap + min-width)
- Filter sidebar non-sticky/full-width on mobile; tighter small-screen padding & buttons

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 07:07:44 +03:30
soroush.asadi 82c94c89a6 Add facility verification (admin verify/unverify → verified badge)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 06:31:15 +03:30
soroush.asadi 563a40d1f4 Add hiring, AI parser+admin, OTP auth, employer dashboard, profit-share pay
- Hiring (استخدام) listings: JobOpening + /Jobs browse/detail + home section
- Heuristic Persian listing-parser + admin queue (/Admin) → publish shift/job
- Phone-OTP cookie auth + visitor-history linking + profile; Admin role gate
- Employer side: self-serve facility registration, dashboard, post/manage shifts & jobs, applicants list with contact
- Compensation models: fixed / hourly / profit-share (درصدی) / negotiable / choice (به انتخاب شما); SharePercent + JalaliDate.PayLabel; parser + filter

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 06:26:54 +03:30
soroush.asadi 2fb86a435e Initial commit — Hamkadr (همکادر) healthcare-staffing marketplace
ASP.NET Core 10 Razor Pages + PostgreSQL/EF Core. RTL Persian, Jalali dates, self-hosted Vazirmatn, teal/coral brand.

Features:
- Shift listings: browse/filter (city, district, role, type, pay), weekly Jalali calendar, detail + interest handoff, near-me distance sort
- Hiring (استخدام) listings with employment type + salary range
- Pattern-engine recommendations + anonymous interest tracking (visitor cookie)
- Heuristic Persian listing-parser + admin queue (raw channel post → shift/job)
- Phone-OTP cookie auth + visitor-history linking + profile

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 01:44:24 +03:30