- poet ImageUrl always points at the API image endpoint
- endpoint serves a neutral SVG placeholder when a poet has no portrait
- public data export: poet image URLs use our API (WebServiceUrl), not ganjoor.net
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- drop 7-day/top visits tabs (Ganjoor's kntr analytics)
- page language options: Urdu (default), Persian, Arabic; drop Turkish/Kurdish
- fa-IR -> ur-PK as default language/culture (site + API); JS digits ur-PK-u-nu-arabext
- unlink blog/museum/ava/... .ganjoor.net help links (text kept)
- random verse: data-driven random poem from our DB/API instead of c.ganjoor.net widget
and hard-coded Persian poet id ranges
- word tooltip: Urdu Wiktionary instead of vajehyab; drop abjad.ganjoor.net
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pages, API endpoints, services and assets removed; music DB models/tables kept
so the feature can be rebuilt for Urdu later.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- delete TajikGanjoor project (removed from solution)
- drop Tajik endpoints/services/DTOs/entities; migration DivanRemoveTajik drops their tables
- remove Tajik admin buttons/handlers and appsettings sections
- README is now Divan's; upstream README kept in docs/GANJOOR-README.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rebrand گنجور -> دیوان across GanjooRazor; lang=ur; Urdu home page and footer
- Hijri century groups with Urdu names
- Noto Nastaliq Urdu (default) / Noto Naskh Arabic switch
- Remove footer links to Ganjoor-only services; link Wikisource, data and code
- Linux deployment: Dockerfile, docker-compose (SQL Server 2022, API, site, Caddy)
- DIVAN.md: changes and deploy guide
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StartImportFromPublicDataRepo captured the injected, request-scoped HttpClient
inside the queued work item. The request completes long before the background
job runs, so the client was already disposed and the import died immediately
with ObjectDisposedException. Let the job own a client for its lifetime.
The Spotify Web API integration stopped working, leaving /spotify unable to
suggest songs at all. Replace the artist/album/track search flow with a single
box where the user pastes a link to the track on a music service.
Links are checked against a hardcoded allow-list of legal streaming and store
domains so links to ripped audio cannot be submitted. The check lives in the
service layer, not just the page: any authenticated user can POST to
/api/ganjoor/song directly and bypass the UI. Hosts are matched against the
full host or a dot-prefixed suffix, so look-alikes such as
open.spotify.com.evil.com are rejected; https is required, and userinfo and
non-default ports are refused. Accepted URLs are canonicalised - https,
lowercased host, tracking parameters stripped - so the same track always yields
the same stored URL and duplicate detection actually works.
Links are stored under one new type, PoemMusicTrackType.MusicUrl, with the
platform derived from the host at render time. That needs no migration or
backfill, and supporting another service later needs no new enum value. The
duplicate check no longer keys on TrackType, which closes a gap where the same
URL could be resubmitted as a different type. Several links per poem remain
allowed; only an identical URL for the same poem is refused.
The Spotify search page and its OAuth plumbing are kept and simply redirect to
/musiclink while the existing SpotifyWorking flag is false, so the old flow can
be restored if that API ever works again.
Track URLs are no longer written through Html.Raw into href attributes. They
previously came from the Spotify API; now that they are user supplied, encoding
them prevents stored XSS.
Also fixes two latent bugs in SuggestSong that this flow would have hit: a null
dereference when TrackUrl is empty, and a singer lookup that matched any singer
with an empty Url.