using GanjooRazor.Utils; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OutputCaching; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using RMuseum.Models.Ganjoor.ViewModels; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; namespace GanjooRazor.Pages { /// /// The site's home page only. Used to also serve every poem/poet/category page (dispatching /// internally on Request.Path == "/") - that responsibility moved to GanjoorPageModel, which now /// owns the site's catch-all route (see Startup.cs). This page keeps its own automatic "/" route, /// unaffected by that change. /// [OutputCache(PolicyName = "GanjoorPublicPage")] public class IndexModel : LoginPartialEnabledPageModel { private readonly PoetCacheService _poetCache; /// /// constructor /// public IndexModel(IConfiguration configuration, HttpClient httpClient, PoetCacheService poetCache ) : base(httpClient, configuration) { _poetCache = poetCache; } public bool OfflineMode => GetConfigFlag("OfflineMode"); public bool ReadOnlyMode => GetConfigFlag("ReadOnlyMode"); /// /// last error /// public string LastError { get; set; } /// /// If the response failed, stores the API's error message in and /// returns true so the caller can short-circuit. /// private async Task CaptureErrorIfFailedAsync(HttpResponseMessage response) { if (response.IsSuccessStatusCode) { return false; } LastError = await ReadErrorMessageAsync(response); return true; } /// /// Poets (for the home page's own search-form author dropdown) /// public List Poets { get; set; } private async Task preparePoets() { var (success, poets, error) = await _poetCache.GetPoetsAsync(EditorCacheBypass); if (!success) { LastError = error; return false; } Poets = poets; return true; } /// /// poets grouped by century, shown on the home page /// public List PoetGroups { get; set; } private async Task _PreparePoetGroups() { try { var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/centuries"); if (await CaptureErrorIfFailedAsync(response)) { return false; } PoetGroups = JArray.Parse(await response.Content.ReadAsStringAsync()).ToObject>(); return true; } catch { LastError = "خطا در دسترسی به وب سرویس گنجور"; return false; } } /// /// Handles the legacy "?p=<id>" query-string form by resolving it to the page's real /// URL and redirecting there. Needed here too (not just on GanjoorPageModel): query strings /// don't affect ASP.NET Core route matching, so a link like "/?p=123" always has /// Path == "/" and is always routed to this page regardless of the split. /// private async Task RedirectByPageIdAsync(string pageId) { var pageUrlResponse = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/pageurl?id={pageId}"); if (await CaptureErrorIfFailedAsync(pageUrlResponse)) { return Page(); } var pageUrl = JsonConvert.DeserializeObject(await pageUrlResponse.Content.ReadAsStringAsync()); return Redirect(pageUrl); } /// /// JSON endpoint for the home page's "مرور کتابها" horizontal book shelf (see bk.js's /// initHomeBooksShelf()). Fetched client-side via "?Handler=BookCatalog" rather than hitting /// the api/ganjoor/book-catalog API route directly from browser JS, same as every other /// client-side data fetch on this site - the browser only ever talks to this page's own /// handlers, which proxy to the API server-side. /// public async Task OnGetBookCatalogAsync() { var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/book-catalog"); if (!response.IsSuccessStatusCode) { return new BadRequestObjectResult(await ReadErrorMessageAsync(response)); } var books = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); return new OkObjectResult(books); } public async Task OnGetAsync() { var maintenanceResult = TryGetMaintenanceModeResult(); if (maintenanceResult != null) { return maintenanceResult; } LastError = ""; InitializeCommonPageState(); if (!string.IsNullOrEmpty(Request.Query["p"])) { return await RedirectByPageIdAsync(Request.Query["p"]); } if (!await preparePoets()) { return Page(); } if (!await _PreparePoetGroups()) { return Page(); } // A fresh/forked install has an empty (or nearly empty) database: no poets, or no // century grouping yet. The view below assumes at least the "popular poets" group // (Id == 0) exists — PoetGroups.Where(g => g.Id == 0).Single() — and throws on an // empty database instead of rendering something useful. Steer the visitor toward // fixing that instead of letting them hit an unhandled exception: log in first if // needed, then straight to the admin page that can seed real content. if (IsDatabaseEffectivelyEmpty()) { const string targetUrl = "/Admin/PublicDataImport"; return LoggedIn ? Redirect(targetUrl) : Redirect($"/login?redirect={targetUrl}"); } ViewData["Title"] = "گنجور"; return Page(); } /// /// true if there's nothing meaningful to show on the home page yet — no poets at all, or /// no century grouping (the "popular poets" group with Id == 0 that the view relies on). /// private bool IsDatabaseEffectivelyEmpty() { return Poets == null || Poets.Count == 0 || PoetGroups == null || !PoetGroups.Any(g => g.Id == 0); } public async Task OnGetPoetInformationAsync(int id) { if (id == 0) { return new OkObjectResult(null); } var (success, poet, error) = await _poetCache.GetPoetAsync(id, EditorCacheBypass); if (!success) { return BadRequest(error); } return new OkObjectResult(poet); } } }