refactoring

This commit is contained in:
Hamid Reza Mohammadi 2026-07-13 16:48:04 +03:30
parent 572deb5090
commit 47f239a7a6
4 changed files with 240 additions and 284 deletions

View File

@ -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
{
// <summary>
/// HttpClient instance
/// </summary>
private readonly HttpClient _httpClient;
/// <summary>
/// constructor
/// </summary>
/// <param name="httpClient"></param>
/// <param name="memoryCache"></param>
public CatUtilsModel(HttpClient httpClient, IMemoryCache memoryCache)
public CatUtilsModel(HttpClient httpClient, IMemoryCache memoryCache) : base(httpClient)
{
_httpClient = httpClient;
}
/// <summary>
@ -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<string>(await response.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(response);
return;
}
@ -98,9 +91,9 @@ namespace GanjooRazor.Areas.Admin.Pages
private async Task<bool> 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<string>(await response.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(response);
return false;
}
Cat = JsonConvert.DeserializeObject<GanjoorPoetCompleteViewModel>(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<string>(await pageQuery.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(pageQuery);
return false;
}
PageInformation = JObject.Parse(await pageQuery.Content.ReadAsStringAsync()).ToObject<GanjoorPageCompleteViewModel>();
@ -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<string>(await rhythmsResponse.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(rhythmsResponse);
return false;
}
Rhythms = JsonConvert.DeserializeObject<GanjoorMetre[]>(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<string>(await numberings.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(numberings);
return false;
}
Numberings = JsonConvert.DeserializeObject<GanjoorNumbering[]>(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<string>(await images.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(images);
return false;
}
PoemRelatedImages = JsonConvert.DeserializeObject<PoemRelatedImageEx[]>(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<string>(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<string>(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<IActionResult> 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<string>(await response.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(response);
}
}
@ -275,173 +272,129 @@ namespace GanjooRazor.Areas.Admin.Pages
return Page();
}
public async Task<IActionResult> OnDeleteAsync(int id)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostStartRhymeAnalysisAsync(int id)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostStartGeneratingSubCatsTOCAsync(int id)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostStartRhythmAnalysisAsync(int id, string rhythm)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostStartRegeneratingRelatedSections(int id)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostSetCategoryLanguageTagAsync(int id, string language)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostSetCategoryPoemFormatAsync(int id, GanjoorPoemFormat format)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostRecountAsync(int id)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostRegenerateNumberingsAsync()
public Task<IActionResult> 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<string>(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<IActionResult> 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<string>(await response.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(response);
return Page();
}
@ -469,47 +422,39 @@ namespace GanjooRazor.Areas.Admin.Pages
return Page();
}
public async Task<IActionResult> OnDeletePoemAsync(int id)
public Task<IActionResult> 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<string>(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<IActionResult> OnPostBatchReSlugCatPoemsAsync(int id)
public Task<IActionResult> 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<string>(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<IActionResult> 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<string>(await response.Content.ReadAsStringAsync());
LastMessage = await ReadErrorMessageAsync(response);
return Page();
}
return Redirect($"/Admin/CatUtils?url={Cat.Cat.FullUrl}");
}
}
public async Task<IActionResult> OnPostRemNaskbanImage(string url)
public Task<IActionResult> 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<string>(await response.Content.ReadAsStringAsync());
return new BadRequestObjectResult(res);
return new BadRequestObjectResult(await ReadErrorMessageAsync(response));
}
return new OkObjectResult(true);
}
}, new OkObjectResult(false));
}
public async Task<IActionResult> OnPostSetCategoryDigitalSourceTagAsync(int id, string tag, string name)
public Task<IActionResult> 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<string>(await response.Content.ReadAsStringAsync());
return new BadRequestObjectResult(res);
}
DigitalSource source = JsonConvert.DeserializeObject<DigitalSource>(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<string>(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<DigitalSource>(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));
}
}
}

View File

@ -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
{
/// <summary>
/// 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
///
/// <see cref="LoginPartialEnabledPageModel"/> (the base class used by public-site pages) derives
/// from this. Admin/User-area page models that used to derive directly from <see cref="PageModel"/>
/// can derive from this instead to get the same helpers without inheriting the public-site-specific
/// properties (GanjoorPage, NextUrl, etc.) that live on <see cref="LoginPartialEnabledPageModel"/>.
/// </summary>
public class GanjoorPageModelBase : PageModel
{
/// <summary>
/// Message shown whenever an action requiring a session couldn't prepare an authenticated
/// client (expired/missing cookies).
/// </summary>
protected const string NotLoggedInMessage = "لطفاً از گنجور خارج و مجددا به آن وارد شوید.";
/// <summary>
/// HttpClient instance for unauthenticated/public calls (injected, shared/pooled by the DI
/// container - see Program.cs/Startup.cs registration).
/// </summary>
protected readonly HttpClient _httpClient;
protected GanjoorPageModelBase(HttpClient httpClient)
{
_httpClient = httpClient;
}
/// <summary>
/// Reads the API's JSON-encoded error string out of a failed response body.
/// </summary>
protected static async Task<string> ReadErrorMessageAsync(HttpResponseMessage response)
{
return JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync());
}
/// <summary>
/// Runs <paramref name="operation"/> against an HttpClient authenticated from the current
/// session cookies (via <see cref="GanjoorSessionChecker.PrepareClient"/>). If the session
/// can't be prepared (missing/expired cookies), returns <paramref name="unauthorizedResult"/>
/// (defaulting to a 400 with <see cref="NotLoggedInMessage"/>) 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.
/// </summary>
protected async Task<IActionResult> WithSecureClientAsync(
Func<HttpClient, Task<IActionResult>> 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);
}
}
}

View File

@ -25,12 +25,6 @@ namespace GanjooRazor.Pages
[IgnoreAntiforgeryToken(Order = 1001)]
public partial class IndexModel : LoginPartialEnabledPageModel
{
/// <summary>
/// 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.
/// </summary>
private const string NotLoggedInMessage = "لطفاً از گنجور خارج و مجددا به آن وارد شوید.";
/// <summary>
/// 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.
/// <summary>
/// Reads a boolean feature flag from configuration, defaulting to <paramref name="defaultValue"/>
@ -113,14 +104,6 @@ namespace GanjooRazor.Pages
return bool.TryParse(Configuration[key], out var value) ? value : defaultValue;
}
/// <summary>
/// Reads the API's JSON-encoded error string out of a failed response body.
/// </summary>
private static async Task<string> ReadErrorMessageAsync(HttpResponseMessage response)
{
return JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync());
}
/// <summary>
/// If the response failed, stores the API's error message in <see cref="LastError"/> and
/// returns true so the caller can short-circuit (the established pattern here is
@ -136,24 +119,6 @@ namespace GanjooRazor.Pages
return true;
}
/// <summary>
/// Runs <paramref name="operation"/> against an HttpClient authenticated from the current
/// session cookies. If the session can't be prepared (missing/expired cookies), returns
/// <paramref name="unauthorizedResult"/> (defaulting to a 400 with <see cref="NotLoggedInMessage"/>)
/// instead of every handler re-implementing the same using/if/else block.
/// </summary>
private async Task<IActionResult> WithSecureClientAsync(
Func<HttpClient, Task<IActionResult>> 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<IActionResult> 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<bool>(await response.Content.ReadAsStringAsync());
return new OkObjectResult(res);
}, new OkObjectResult(false));
});
}
public Task<IActionResult> 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<GanjoorUserBookmarkViewModel[]>(await response.Content.ReadAsStringAsync());
return new OkObjectResult(res);
}, new OkObjectResult(false));
});
}
/// <summary>
@ -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));
}

View File

@ -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
{
/// <summary>
/// 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<string>(await response.Content.ReadAsStringAsync())}");
}
@ -163,36 +161,22 @@ namespace GanjooRazor.Pages
return Redirect(Request.Path);
}
public async Task<IActionResult> OnGetCheckIfHasNotificationsAsync()
public Task<IActionResult> 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<string>(await response.Content.ReadAsStringAsync()));
}
var res = JsonConvert.DeserializeObject<int>(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<int>(await response.Content.ReadAsStringAsync());
return new OkObjectResult(res == 0 ? "" : res.ToString().ToPersianNumbers());
});
}
/// <summary>
/// HttpClient instance
/// </summary>
protected readonly HttpClient _httpClient;
public LoginPartialEnabledPageModel(
HttpClient httpClient
)
public LoginPartialEnabledPageModel(HttpClient httpClient) : base(httpClient)
{
_httpClient = httpClient;
}
}
}