#498 ui for public data generation

This commit is contained in:
Hamid Reza Mohammadi 2026-08-14 20:23:11 +03:30
parent a1b7c2fa12
commit 6e44e4dec1
5 changed files with 79 additions and 3 deletions

View File

@ -231,6 +231,7 @@
<a role="button" onclick="rebuildStats()" class="up-btn up-btn--ghost up-btn--sm" title="بازسازی صفحات آمار و اوزان عروضی">بازسازی صفحات آمار و اوزان عروضی</a>
<a role="button" onclick="rebuildRelatedPages()" class="up-btn up-btn--ghost up-btn--sm" title="بازسازی صفحات استقبالها">بازسازی صفحات استقبالها</a>
<a role="button" onclick="rebuildDigitalSourcesStats()" class="up-btn up-btn--ghost up-btn--sm" title="بازسازی آمار منابع دیجیتال">بازسازی آمار منابع دیجیتال</a>
<a role="button" onclick="regeneratePublicData()" class="up-btn up-btn--ghost up-btn--sm" title="بازسازی و ارسال دادهٔ عمومی گنجور">بازسازی دادهٔ عمومی</a>
<a role="button" onclick="transilerate()" class="up-btn up-btn--ghost up-btn--sm" title="برگردان تاجیکی">برگردان تاجیکی</a>
</div>

View File

@ -339,6 +339,28 @@ namespace GanjooRazor.Areas.Admin.Pages
return new OkObjectResult(false);
}
/// <summary>
/// (re)generate the public git-tracked data export (poets/categories/poems as JSON,
/// pushed to the configured git remote) — runs as a background job, same as the other
/// rebuild/regenerate tools on this page; check the Jobs page for progress
/// </summary>
public async Task<IActionResult> OnPostRegeneratePublicDataAsync()
{
using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response)))
{
if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response))
{
HttpResponseMessage response = await secureClient.PostAsync($"{APIRoot.Url}/api/ganjoor/publicdata/batchexport", null);
if (!response.IsSuccessStatusCode)
{
return BadRequest(JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync()));
}
return new OkObjectResult(true);
}
}
return new OkObjectResult(false);
}
public async Task<IActionResult> OnPostRefillSectionsCoupletCountsAsync()
{

View File

@ -186,6 +186,25 @@
});
}
async function regeneratePublicData() {
var ok = await upConfirm('آیا از بازسازی و ارسال دادهٔ عمومی گنجور (مخزن گیت) اطمینان دارید؟ ممکن است مدتی طول بکشد.');
if (!ok) return;
var url = '?handler=RegeneratePublicData';
$.ajax({
type: "POST",
url: url,
error: function () {
upToast('خطا رخ داد.', 'error');
},
success: function () {
upToast('فرایند بازسازی دادهٔ عمومی در پس‌زمینه شروع شد.', 'success');
},
});
}
async function refillCoupletCounts() {

View File

@ -8,8 +8,8 @@
},
"AllowedHosts": "*",
"TrackingScript": "<script src='https://cdn.kntr.ir/tracker.js'></script><script>kntrTracking.startTracking(\"2\", \"loggedon\");</script>",
"APIRoot": "http://localhost:3439",
"GlobalAPIRoot": "http://localhost:3439",
"APIRoot": "https://api.ganjoor.net",
"GlobalAPIRoot": "https://api.ganjoor.net",
"SiteUrl": "http://localhost:33081",
"MockSpotify": "False",
"Spotify": {

View File

@ -20601,6 +20601,15 @@
</summary>
<returns></returns>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorService._RegenerateHalfCenturies(RMuseum.DbContext.RMuseumDbContext)">
<summary>
same as <see cref="M:RMuseum.Services.Implementation.GanjoorService.RegenerateHalfCenturiesAsync"/>, but against an explicitly-passed
context instead of the injected <see cref="F:RMuseum.Services.Implementation.GanjoorService._context"/> — needed so background jobs
that construct their own short-lived <see cref="T:RMuseum.DbContext.RMuseumDbContext"/> (because the
request-scoped <see cref="F:RMuseum.Services.Implementation.GanjoorService._context"/> may already be disposed by the time a background
job runs) can call this too, e.g. right after a public-data import finishes.
</summary>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorService.RateCommentAsync(System.Guid,System.Int32,System.Int16)">
<summary>
like / dislike / clear rating for a comment
@ -21150,7 +21159,18 @@
of what actually gets written.
</summary>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorService.ExportCatTreeToJson(RMuseum.DbContext.RMuseumDbContext,System.String,RMuseum.Models.Ganjoor.GanjoorCat,System.Collections.Generic.Dictionary{System.Int32,System.String},System.Collections.Generic.Dictionary{System.Int32,System.String})">
<member name="M:RMuseum.Services.Implementation.GanjoorService.ExportPoetContent(RMuseum.DbContext.RMuseumDbContext,System.String,RMuseum.Models.Ganjoor.GanjoorPoet,RMuseum.Models.Ganjoor.GanjoorCat,System.Collections.Generic.Dictionary{System.Int32,RMuseum.Models.Ganjoor.GanjoorMetre},System.Collections.Generic.Dictionary{System.Int32,System.String},System.Collections.Generic.Dictionary{System.Int32,System.String})">
<summary>
Batch-loads this poet's entire sections/verses in two queries (instead of the
two-per-poem queries the old per-poem approach ran), then walks the poet's category
tree writing everything from memory. This is the fix for the export consistently
taking about as long on every run regardless of how much content changed — the
"skip unchanged files" logic in DeterministicJsonWriter only ever saved disk writes,
never the DB round-trips, which were the actual dominant cost (roughly 3 sequential
queries per poem — tens of thousands of round-trips for the full corpus, every run).
</summary>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorService.ExportCatTreeToJson(RMuseum.DbContext.RMuseumDbContext,System.String,RMuseum.Models.Ganjoor.GanjoorCat,System.Collections.Generic.Dictionary{System.Int32,RMuseum.Models.Ganjoor.GanjoorMetre},System.Collections.Generic.Dictionary{System.Int32,System.Collections.Generic.List{RMuseum.Models.Ganjoor.GanjoorPoemSection}},System.Collections.Generic.Dictionary{System.Int32,System.Collections.Generic.List{RMuseum.Models.Ganjoor.GanjoorVerse}},System.Collections.Generic.Dictionary{System.Int32,System.String},System.Collections.Generic.Dictionary{System.Int32,System.String})">
<summary>
recursively writes _cat.json for <paramref name="cat"/> and every poem directly
under it, then recurses into child categories. Returns the number of poems written
@ -21161,6 +21181,12 @@
either; every category/poem under a published poet is exported.
</summary>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorService.ExportPoemToJson(System.String,RMuseum.Models.Ganjoor.GanjoorPoem,RMuseum.Models.Ganjoor.GanjoorMetre,System.Collections.Generic.List{RMuseum.Models.Ganjoor.GanjoorPoemSection},System.Collections.Generic.List{RMuseum.Models.Ganjoor.GanjoorVerse})">
<summary>
Pure in-memory write — no DB access. Sections/verses/metre are pre-loaded by the caller
(see <see cref="M:RMuseum.Services.Implementation.GanjoorService.ExportPoetContent(RMuseum.DbContext.RMuseumDbContext,System.String,RMuseum.Models.Ganjoor.GanjoorPoet,RMuseum.Models.Ganjoor.GanjoorCat,System.Collections.Generic.Dictionary{System.Int32,RMuseum.Models.Ganjoor.GanjoorMetre},System.Collections.Generic.Dictionary{System.Int32,System.String},System.Collections.Generic.Dictionary{System.Int32,System.String})"/>) instead of queried per poem.
</summary>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorService.BuildApiMarkdown(RMuseum.Models.Ganjoor.PublicExport.PublicExportManifestDto)">
<summary>
generates the repo-root API.md every run, so the docs can never drift out of sync with
@ -21168,6 +21194,14 @@
prose, add it here, not in the generated file.
</summary>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorService.BuildReadmeMarkdown(RMuseum.Models.Ganjoor.PublicExport.PublicExportManifestDto)">
<summary>
generates the repo-root README.md every run — the landing document GitHub shows by
default, so this is where a first-time visitor's "where do I even start?" question
needs to be answered in the first few lines, before they'd ever think to look for
API.md.
</summary>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorService.SyntheticCatPageId(System.Int32)">
<summary>
Category (and root-poet) pages don't carry a real production page id in the public