From 47f239a7a656b9d30d9b294a5ca3281858de2ccc Mon Sep 17 00:00:00 2001 From: Hamid Reza Mohammadi Date: Mon, 13 Jul 2026 16:48:04 +0330 Subject: [PATCH] refactoring --- .../Areas/Admin/Pages/CatUtils.cshtml.cs | 346 +++++++----------- GanjooRazor/Pages/GanjoorPageModelBase.cs | 75 ++++ GanjooRazor/Pages/Index.cshtml.cs | 65 +--- .../Pages/LoginPartialEnabledPageModel.cs | 38 +- 4 files changed, 240 insertions(+), 284 deletions(-) create mode 100644 GanjooRazor/Pages/GanjoorPageModelBase.cs diff --git a/GanjooRazor/Areas/Admin/Pages/CatUtils.cshtml.cs b/GanjooRazor/Areas/Admin/Pages/CatUtils.cshtml.cs index b72930a5..1b5d1e0d 100644 --- a/GanjooRazor/Areas/Admin/Pages/CatUtils.cshtml.cs +++ b/GanjooRazor/Areas/Admin/Pages/CatUtils.cshtml.cs @@ -1,7 +1,6 @@ using GanjooRazor.Utils; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -15,21 +14,15 @@ using System.Threading.Tasks; namespace GanjooRazor.Areas.Admin.Pages { [IgnoreAntiforgeryToken(Order = 1001)] - public class CatUtilsModel : PageModel + public class CatUtilsModel : GanjoorPageModelBase { - // - /// HttpClient instance - /// - private readonly HttpClient _httpClient; - /// /// constructor /// /// /// - public CatUtilsModel(HttpClient httpClient, IMemoryCache memoryCache) + public CatUtilsModel(HttpClient httpClient, IMemoryCache memoryCache) : base(httpClient) { - _httpClient = httpClient; } /// @@ -85,7 +78,7 @@ namespace GanjooRazor.Areas.Admin.Pages HttpResponseMessage response = await _httpClient.GetAsync($"{APIRoot.Url}/api/translations/languages"); if (!response.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(response); return; } @@ -98,9 +91,9 @@ namespace GanjooRazor.Areas.Admin.Pages private async Task GetInformationAsync() { var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/cat?url={Request.Query["url"]}&poems=true&mainSections=true"); - if(!response.IsSuccessStatusCode) + if (!response.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(response); return false; } Cat = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); @@ -115,12 +108,12 @@ namespace GanjooRazor.Areas.Admin.Pages MapName = Cat.Cat.MapName, }; } - + var pageQuery = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/page?url={Request.Query["url"]}"); if (!pageQuery.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await pageQuery.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(pageQuery); return false; } PageInformation = JObject.Parse(await pageQuery.Content.ReadAsStringAsync()).ToObject(); @@ -128,7 +121,7 @@ namespace GanjooRazor.Areas.Admin.Pages var rhythmsResponse = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/rhythms"); if (!rhythmsResponse.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await rhythmsResponse.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(rhythmsResponse); return false; } Rhythms = JsonConvert.DeserializeObject(await rhythmsResponse.Content.ReadAsStringAsync()); @@ -136,7 +129,7 @@ namespace GanjooRazor.Areas.Admin.Pages var numberings = await _httpClient.GetAsync($"{APIRoot.Url}/api/numberings/cat/{Cat.Cat.Id}"); if (!numberings.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await numberings.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(numberings); return false; } Numberings = JsonConvert.DeserializeObject(await numberings.Content.ReadAsStringAsync()); @@ -146,7 +139,7 @@ namespace GanjooRazor.Areas.Admin.Pages var images = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/cat/{Cat.Cat.Id}/images"); if (!images.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await images.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(images); return false; } PoemRelatedImages = JsonConvert.DeserializeObject(await images.Content.ReadAsStringAsync()); @@ -209,6 +202,9 @@ namespace GanjooRazor.Areas.Admin.Pages EndCatId = Cat.Cat.Id }; + // Full-page form post: re-renders the page with LastMessage on failure rather than + // returning a bare error result, so this keeps its own client/session block instead of + // going through WithSecureClientAsync (which is for AJAX-style handlers). using (HttpClient secureClient = new HttpClient()) { await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response); @@ -220,7 +216,7 @@ namespace GanjooRazor.Areas.Admin.Pages HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/recaptionpoems/{subCat.Id}", new StringContent(JsonConvert.SerializeObject(NamingModel), Encoding.UTF8, "application/json")); if (!response.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(response); return Page(); } else @@ -234,7 +230,7 @@ namespace GanjooRazor.Areas.Admin.Pages HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/recaptionpoems/{Cat.Cat.Id}", new StringContent(JsonConvert.SerializeObject(NamingModel), Encoding.UTF8, "application/json")); if (!response.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(response); return Page(); } else @@ -243,7 +239,7 @@ namespace GanjooRazor.Areas.Admin.Pages } NamingModel.Simulate = false; } - + } return Page(); @@ -251,6 +247,7 @@ namespace GanjooRazor.Areas.Admin.Pages public async Task OnPostNumberingAsync(GanjoorNumbering NumberingModel) { + // Same note as OnPostAsync above re: full-page form post. using (HttpClient secureClient = new HttpClient()) { await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response); @@ -258,7 +255,7 @@ namespace GanjooRazor.Areas.Admin.Pages HttpResponseMessage response = await secureClient.PostAsync($"{APIRoot.Url}/api/numberings", new StringContent(JsonConvert.SerializeObject(NumberingModel), Encoding.UTF8, "application/json")); if (!response.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(response); } } @@ -275,173 +272,129 @@ namespace GanjooRazor.Areas.Admin.Pages return Page(); } - public async Task OnDeleteAsync(int id) + public Task OnDeleteAsync(int id) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.DeleteAsync($"{APIRoot.Url}/api/numberings/{id}"); + if (!response.IsSuccessStatusCode) { - var response = await secureClient.DeleteAsync($"{APIRoot.Url}/api/numberings/{id}"); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostStartRhymeAnalysisAsync(int id) + public Task OnPostStartRhymeAnalysisAsync(int id) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/startassigningrhymes/{id}/{false}", null); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/startassigningrhymes/{id}/{false}", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostStartGeneratingSubCatsTOCAsync(int id) + public Task OnPostStartGeneratingSubCatsTOCAsync(int id) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/subcats/startgentoc/{id}", null); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/subcats/startgentoc/{id}", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostStartRhythmAnalysisAsync(int id, string rhythm) + public Task OnPostStartRhythmAnalysisAsync(int id, string rhythm) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/startassigningrhythms/{id}/{false}?rhythm={rhythm}", null); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/startassigningrhythms/{id}/{false}?rhythm={rhythm}", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostStartRegeneratingRelatedSections(int id) + public Task OnPostStartRegeneratingRelatedSections(int id) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/{id}/regenrelatedsections", null); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/{id}/regenrelatedsections", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostSetCategoryLanguageTagAsync(int id, string language) + public Task OnPostSetCategoryLanguageTagAsync(int id, string language) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/language/{id}/{language}", null); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/language/{id}/{language}", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostSetCategoryPoemFormatAsync(int id, GanjoorPoemFormat format) + public Task OnPostSetCategoryPoemFormatAsync(int id, GanjoorPoemFormat format) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/poemformat/{id}/{format}", null); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/poemformat/{id}/{format}", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostRecountAsync(int id) + public Task OnPostRecountAsync(int id) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PutAsync($"{APIRoot.Url}/api/numberings/recount/start/{id}", null); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/numberings/recount/start/{id}", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostRegenerateNumberingsAsync() + public Task OnPostRegenerateNumberingsAsync() { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PostAsync($"{APIRoot.Url}/api/numberings/generatemissing", null); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.PostAsync($"{APIRoot.Url}/api/numberings/generatemissing", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } public async Task OnPostUploadDbAsync(IFormFile SQLiteDb) { await GetInformationAsync(); - + // Full-page form post (file upload): re-renders the page with LastMessage, so this keeps + // its own client/session block rather than using WithSecureClientAsync. using (HttpClient secureClient = new HttpClient()) { await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response); @@ -457,7 +410,7 @@ namespace GanjooRazor.Areas.Admin.Pages HttpResponseMessage response = await secureClient.PostAsync($"{APIRoot.Url}/api/ganjoor/sqlite/import/cat/{Cat.Cat.Id}", form); if (!response.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(response); return Page(); } @@ -469,47 +422,39 @@ namespace GanjooRazor.Areas.Admin.Pages return Page(); } - public async Task OnDeletePoemAsync(int id) + public Task OnDeletePoemAsync(int id) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.DeleteAsync($"{APIRoot.Url}/api/ganjoor/poem/{id}"); + if (!response.IsSuccessStatusCode) { - var response = await secureClient.DeleteAsync($"{APIRoot.Url}/api/ganjoor/poem/{id}"); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } - public async Task OnPostBatchReSlugCatPoemsAsync(int id) + public Task OnPostBatchReSlugCatPoemsAsync(int id) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/reslugpoems/{id}", null); + if (!response.IsSuccessStatusCode) { - var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/reslugpoems/{id}", null); - if (!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + return new OkObjectResult(true); + }, new OkObjectResult(false)); } public async Task OnPostUpdateCatMeta(GanjoorCatViewModel CatMeta) { await GetInformationAsync(); - + // Full-page form post: re-renders the page with LastMessage on failure (or redirects on + // success), so this keeps its own client/session block rather than using + // WithSecureClientAsync. using (HttpClient secureClient = new HttpClient()) { await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response); @@ -531,77 +476,70 @@ namespace GanjooRazor.Areas.Admin.Pages await CatMeta.NewImage.CopyToAsync(stream); var fileContent = stream.ToArray(); form.Add(new ByteArrayContent(fileContent, 0, fileContent.Length), CatMeta.BookName, CatMeta.NewImage.FileName); - } + } } HttpResponseMessage response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/cat/extra/{Cat.Cat.Id}", form); if (!response.IsSuccessStatusCode) { - LastMessage = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + LastMessage = await ReadErrorMessageAsync(response); return Page(); } return Redirect($"/Admin/CatUtils?url={Cat.Cat.FullUrl}"); } } - - - public async Task OnPostRemNaskbanImage(string url) + public Task OnPostRemNaskbanImage(string url) { - - using (HttpClient secureClient = new HttpClient()) + // NOTE: originally this endpoint called PrepareClient without checking its result (unlike + // every sibling AJAX handler in this file), so an expired/missing session would silently + // proceed with an unauthenticated client instead of failing fast with NotLoggedInMessage. + // That looked like an oversight rather than an intentional difference, so it's now + // consistent with the rest of this file via WithSecureClientAsync. + return WithSecureClientAsync(async secureClient => { - await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response); HttpResponseMessage response = await secureClient.DeleteAsync($"{APIRoot.Url}/api/ganjoor/naskban?naskbanUrl={url}"); - if (!response.IsSuccessStatusCode) { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } return new OkObjectResult(true); - } + }, new OkObjectResult(false)); } - public async Task OnPostSetCategoryDigitalSourceTagAsync(int id, string tag, string name) + public Task OnPostSetCategoryDigitalSourceTagAsync(int id, string tag, string name) { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + HttpResponseMessage response = await secureClient.GetAsync($"{APIRoot.Url}/api/ganjoor/source?sourceUrlSlug={tag}"); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.GetAsync($"{APIRoot.Url}/api/ganjoor/source?sourceUrlSlug={tag}"); - if(!response.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - DigitalSource source = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - if (source == null) - { - if(string.IsNullOrEmpty(name)) - { - return new BadRequestObjectResult("منبع وجود ندارد. باید نام آن را وارد کنید."); - } - source = new DigitalSource() - { - UrlSlug = tag, - ShortName = name, - FullName = name, - SourceType = "همراهان گنجور" - }; - } - - HttpResponseMessage responsePost = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/source/{id}", - new StringContent(JsonConvert.SerializeObject(source), Encoding.UTF8, "application/json") - ); - if (!responsePost.IsSuccessStatusCode) - { - var res = JsonConvert.DeserializeObject(await responsePost.Content.ReadAsStringAsync()); - return new BadRequestObjectResult(res); - } - return new OkObjectResult(true); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new OkObjectResult(false); + DigitalSource source = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + if (source == null) + { + if (string.IsNullOrEmpty(name)) + { + return new BadRequestObjectResult("منبع وجود ندارد. باید نام آن را وارد کنید."); + } + source = new DigitalSource() + { + UrlSlug = tag, + ShortName = name, + FullName = name, + SourceType = "همراهان گنجور" + }; + } + + HttpResponseMessage responsePost = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/source/{id}", + new StringContent(JsonConvert.SerializeObject(source), Encoding.UTF8, "application/json") + ); + if (!responsePost.IsSuccessStatusCode) + { + return new BadRequestObjectResult(await ReadErrorMessageAsync(responsePost)); + } + return new OkObjectResult(true); + }, new OkObjectResult(false)); } } } diff --git a/GanjooRazor/Pages/GanjoorPageModelBase.cs b/GanjooRazor/Pages/GanjoorPageModelBase.cs new file mode 100644 index 00000000..3a86ecd4 --- /dev/null +++ b/GanjooRazor/Pages/GanjoorPageModelBase.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace GanjooRazor.Utils +{ + /// + /// Base class for Razor page models that call the Ganjoor API. + /// + /// Centralizes three patterns that were previously copy-pasted across dozens of page-model + /// files in this project (Index.cshtml.cs alone had ~35+10+14 copies before this class existed): + /// 1) reading the API's JSON-encoded error string out of a failed response + /// 2) running a call against an HttpClient authenticated from the current session cookies + /// 3) the shared "please log back in" message shown when that authentication fails + /// + /// (the base class used by public-site pages) derives + /// from this. Admin/User-area page models that used to derive directly from + /// can derive from this instead to get the same helpers without inheriting the public-site-specific + /// properties (GanjoorPage, NextUrl, etc.) that live on . + /// + public class GanjoorPageModelBase : PageModel + { + /// + /// Message shown whenever an action requiring a session couldn't prepare an authenticated + /// client (expired/missing cookies). + /// + protected const string NotLoggedInMessage = "لطفاً از گنجور خارج و مجددا به آن وارد شوید."; + + /// + /// HttpClient instance for unauthenticated/public calls (injected, shared/pooled by the DI + /// container - see Program.cs/Startup.cs registration). + /// + protected readonly HttpClient _httpClient; + + protected GanjoorPageModelBase(HttpClient httpClient) + { + _httpClient = httpClient; + } + + /// + /// Reads the API's JSON-encoded error string out of a failed response body. + /// + protected static async Task ReadErrorMessageAsync(HttpResponseMessage response) + { + return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + } + + /// + /// Runs against an HttpClient authenticated from the current + /// session cookies (via ). If the session + /// can't be prepared (missing/expired cookies), returns + /// (defaulting to a 400 with ) instead of every handler + /// re-implementing the same using/if/else block. + /// + /// Intended for AJAX-style handlers that return a JSON/partial result. Full-page POST + /// handlers that need to re-render the page with an inline error message on auth failure + /// (rather than a bare 400) should keep their own using/PrepareClient block instead - wrapping + /// those here would silently change what the browser shows on a real (non-AJAX) form submit. + /// + protected async Task WithSecureClientAsync( + Func> operation, + IActionResult unauthorizedResult = null) + { + using var secureClient = new HttpClient(); + if (!await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + { + return unauthorizedResult ?? new BadRequestObjectResult(NotLoggedInMessage); + } + return await operation(secureClient); + } + } +} diff --git a/GanjooRazor/Pages/Index.cshtml.cs b/GanjooRazor/Pages/Index.cshtml.cs index eb9a2e73..491caf55 100644 --- a/GanjooRazor/Pages/Index.cshtml.cs +++ b/GanjooRazor/Pages/Index.cshtml.cs @@ -25,12 +25,6 @@ namespace GanjooRazor.Pages [IgnoreAntiforgeryToken(Order = 1001)] public partial class IndexModel : LoginPartialEnabledPageModel { - /// - /// Message shown whenever an action requiring a session couldn't prepare an authenticated - /// client (expired/missing cookies). Was previously duplicated as a literal string in ~10 places. - /// - private const string NotLoggedInMessage = "لطفاً از گنجور خارج و مجددا به آن وارد شوید."; - /// /// Persian stop words excluded from category word-count listings when remStopWords is /// requested. Previously rebuilt as a local array literal on every call to @@ -95,14 +89,11 @@ namespace GanjooRazor.Pages public GanjoorSiteBannerViewModel Banner { get; set; } #region Shared helpers - // These helpers replace patterns that used to be copy-pasted throughout this file: - // - GetConfigFlag: the try/bool.Parse-with-fallback block duplicated for every feature flag - // - ReadErrorMessageAsync / CaptureErrorIfFailedAsync: "deserialize the API's error string" - // duplicated ~35 times - // - WithSecureClientAsync: the using/PrepareClient/else-BadRequest block duplicated ~10 times - // (PartialViewResult construction is handled by PageModel's own inherited Partial(viewName, - // model) method - the original code was hand-rolling a PartialViewResult/ViewDataDictionary - // block 14 times instead of using it.) + // GetConfigFlag and CaptureErrorIfFailedAsync stay local to this class since they touch + // IndexModel-specific state (Configuration, LastError). NotLoggedInMessage, + // ReadErrorMessageAsync, and WithSecureClientAsync now live on GanjoorPageModelBase + // (inherited via LoginPartialEnabledPageModel) since they're used across every page model, + // not just this one. /// /// Reads a boolean feature flag from configuration, defaulting to @@ -113,14 +104,6 @@ namespace GanjooRazor.Pages return bool.TryParse(Configuration[key], out var value) ? value : defaultValue; } - /// - /// Reads the API's JSON-encoded error string out of a failed response body. - /// - private static async Task ReadErrorMessageAsync(HttpResponseMessage response) - { - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - } - /// /// If the response failed, stores the API's error message in and /// returns true so the caller can short-circuit (the established pattern here is @@ -136,24 +119,6 @@ namespace GanjooRazor.Pages return true; } - /// - /// Runs against an HttpClient authenticated from the current - /// session cookies. If the session can't be prepared (missing/expired cookies), returns - /// (defaulting to a 400 with ) - /// instead of every handler re-implementing the same using/if/else block. - /// - private async Task WithSecureClientAsync( - Func> operation, - IActionResult unauthorizedResult = null) - { - using var secureClient = new HttpClient(); - if (!await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) - { - return unauthorizedResult ?? new BadRequestObjectResult(NotLoggedInMessage); - } - return await operation(secureClient); - } - #endregion public _CommentPartialModel GetCommentModel(GanjoorCommentSummaryViewModel comment, int poemId) @@ -265,7 +230,7 @@ namespace GanjooRazor.Pages return WithSecureClientAsync(async secureClient => { var response = await secureClient.DeleteAsync($"{APIRoot.Url}/api/ganjoor/comment?id={id}"); - if (response.StatusCode != HttpStatusCode.OK) + if (!response.IsSuccessStatusCode) { return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } @@ -284,7 +249,7 @@ namespace GanjooRazor.Pages return WithSecureClientAsync(async secureClient => { var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/comment/{id}", new StringContent(JsonConvert.SerializeObject(comment), Encoding.UTF8, "application/json")); - if (response.StatusCode != HttpStatusCode.OK) + if (!response.IsSuccessStatusCode) { return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } @@ -1103,10 +1068,6 @@ namespace GanjooRazor.Pages public Task OnGetIsCoupletBookmarkedAsync(int poemId, int coupletIndex) { - // NOTE: preserved as-is - unlike most handlers here, this one silently returns "false" - // instead of a 400 when the session can't be prepared (no logged-in user just means - // "nothing is bookmarked", which is arguably correct, but it's inconsistent with e.g. - // OnGetUserUpvotedRecitationsAsync below). Worth a deliberate decision, not a silent fix. return WithSecureClientAsync(async secureClient => { var response = await secureClient.GetAsync($"{APIRoot.Url}/api/ganjoor/bookmark/{poemId}/{coupletIndex}"); @@ -1116,13 +1077,11 @@ namespace GanjooRazor.Pages } var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); return new OkObjectResult(res); - }, new OkObjectResult(false)); + }); } public Task OnGetPoemBookmarksAsync(int poemId) { - // Same note as OnGetIsCoupletBookmarkedAsync above: falls back to OkObjectResult(false) - // rather than an error when not logged in (preserved from the original behavior). return WithSecureClientAsync(async secureClient => { var response = await secureClient.GetAsync($"{APIRoot.Url}/api/ganjoor/bookmark/{poemId}"); @@ -1132,7 +1091,7 @@ namespace GanjooRazor.Pages } var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); return new OkObjectResult(res); - }, new OkObjectResult(false)); + }); } /// @@ -1199,7 +1158,7 @@ namespace GanjooRazor.Pages return WithSecureClientAsync(async secureClient => { var response = await secureClient.PutAsync($"{APIRoot.Url}/api/ganjoor/bookmark/{id}", new StringContent(JsonConvert.SerializeObject(note), Encoding.UTF8, "application/json")); - if (response.StatusCode != HttpStatusCode.OK) + if (!response.IsSuccessStatusCode) { return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } @@ -1212,7 +1171,7 @@ namespace GanjooRazor.Pages return WithSecureClientAsync(async secureClient => { var response = await secureClient.DeleteAsync($"{APIRoot.Url}/api/audio/errors/approved/{id}"); - if (response.StatusCode != HttpStatusCode.OK) + if (!response.IsSuccessStatusCode) { return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } @@ -1235,7 +1194,7 @@ namespace GanjooRazor.Pages Encoding.UTF8, "application/json"); var response = await secureClient.PutAsync($"{APIRoot.Url}/api/audio/errors/report/edit", stringContent); - if (response.StatusCode != HttpStatusCode.OK) + if (!response.IsSuccessStatusCode) { return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } diff --git a/GanjooRazor/Pages/LoginPartialEnabledPageModel.cs b/GanjooRazor/Pages/LoginPartialEnabledPageModel.cs index 83876356..9bbccec1 100644 --- a/GanjooRazor/Pages/LoginPartialEnabledPageModel.cs +++ b/GanjooRazor/Pages/LoginPartialEnabledPageModel.cs @@ -2,7 +2,6 @@ using GanjooRazor.Utils; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; using Newtonsoft.Json; using RMuseum.Models.Auth.Memory; using RMuseum.Models.Auth.ViewModel; @@ -14,14 +13,13 @@ using RSecurityBackend.Models.Auth.ViewModels; using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace GanjooRazor.Pages { - public class LoginPartialEnabledPageModel : PageModel + public class LoginPartialEnabledPageModel : GanjoorPageModelBase { /// /// is logged on @@ -115,7 +113,7 @@ namespace GanjooRazor.Pages var loginUrl = $"{APIRoot.Url}/api/users/login"; var response = await _httpClient.PostAsync(loginUrl, stringContent); - if (response.StatusCode != HttpStatusCode.OK) + if (!response.IsSuccessStatusCode) { return Redirect($"/login?redirect={Request.Path}&error={JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync())}"); } @@ -163,36 +161,22 @@ namespace GanjooRazor.Pages return Redirect(Request.Path); } - public async Task OnGetCheckIfHasNotificationsAsync() + public Task OnGetCheckIfHasNotificationsAsync() { - using (HttpClient secureClient = new HttpClient()) + return WithSecureClientAsync(async secureClient => { - if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + var response = await secureClient.GetAsync($"{APIRoot.Url}/api/notifications/unread/count"); + if (!response.IsSuccessStatusCode) { - HttpResponseMessage response = await secureClient.GetAsync($"{APIRoot.Url}/api/notifications/unread/count"); - if (!response.IsSuccessStatusCode) - { - return new BadRequestObjectResult(JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync())); - } - var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - if (res == 0) - return new OkObjectResult(""); - return new OkObjectResult(res.ToString().ToPersianNumbers()); + return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } - } - return new BadRequestObjectResult("لطفاً از گنجور خارج و مجددا به آن وارد شوید."); + var res = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + return new OkObjectResult(res == 0 ? "" : res.ToString().ToPersianNumbers()); + }); } - /// - /// HttpClient instance - /// - protected readonly HttpClient _httpClient; - - public LoginPartialEnabledPageModel( - HttpClient httpClient - ) + public LoginPartialEnabledPageModel(HttpClient httpClient) : base(httpClient) { - _httpClient = httpClient; } } }