Wave/Wave/Components/Pages/ArticleView.razor

118 lines
4.9 KiB
Plaintext

@page "/article/{id:guid}"
@using Microsoft.EntityFrameworkCore
@using Wave.Data
@using System.Security.Claims
@using System.Diagnostics.CodeAnalysis
@inject IDbContextFactory<ApplicationDbContext> ContextFactory;
@inject NavigationManager Navigation
@inject IStringLocalizer<ArticleView> Localizer
<PageTitle>@(TitlePrefix + (Article?.Title ?? @Localizer["NotFound_Title"]))</PageTitle>
<ErrorBoundary>
<ChildContent>
<AuthorizeView Policy="ArticleEditOrReviewPermissions">
<Authorized>
<ArticleComponent Article="@GetArticleProtected(context.User)" />
<div class="flex gap-2 mt-3 flex-wrap">
<a class="btn btn-info w-full sm:btn-wide" href="article/@Article.Id/edit">@Localizer["Edit"]</a>
@if (Article.Status is ArticleStatus.Draft) {
<form @formname="submit-for-review" method="post" @onsubmit="SubmitForReview" class="max-sm:w-full">
<AntiforgeryToken />
<button type="submit" class="btn btn-primary w-full sm:btn-wide">@Localizer["Review_Submit"]</button>
</form>
} else if (Article.Status is ArticleStatus.InReview) {
<form @formname="submit-for-publish" method="post" @onsubmit="SubmitForPublish" class="max-sm:w-full">
<AntiforgeryToken />
<button type="submit" class="btn btn-primary w-full sm:btn-wide">@Localizer["Publish_Submit"]</button>
</form>
}
</div>
</Authorized>
<NotAuthorized>
<ArticleComponent Article="@GetArticlePublic()" />
</NotAuthorized>
</AuthorizeView>
</ChildContent>
<ErrorContent>
<h1 class="text-3xl lg:text-5xl font-light mb-6 text-primary">@Localizer["NotFound_Title"]</h1>
<p class="my-3">@Localizer["NotFound_Description"]</p>
<a class="btn btn-primary" href="/">@Localizer["NotFound_BackToHome_Label"]</a>
@if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")?.ToLower() == "development") {
<p class="mt-3">[DEBUG] EXCEPTION MESSAGE: @context.Message</p>
}
</ErrorContent>
</ErrorBoundary>
@code {
[CascadingParameter(Name = "TitlePrefix")]
private string TitlePrefix { get; set; } = default!;
[Parameter]
public Guid Id { get; set; }
private Article? Article { get; set; } = default!;
private Article GetArticlePublic() {
if (Article is null) throw new ApplicationException("Article not found.");
if (Article.Status >= ArticleStatus.Published && Article.PublishDate <= DateTimeOffset.UtcNow) {
return Article;
}
throw new ApplicationException("Article is not public.");
}
[SuppressMessage("ReSharper", "ConvertIfStatementToSwitchStatement")]
private Article GetArticleProtected(ClaimsPrincipal principal) {
if (Article is null) throw new ApplicationException("Article not found.");
// Admins always get access
if (principal.IsInRole("Admin")) {
return Article;
}
// You can only access your own drafts
if (Article.Status is ArticleStatus.Draft) {
if (Article.Author.Id == principal.FindFirst("Id")!.Value) {
return Article;
}
throw new ApplicationException("Cannot access draft article without being author or admin.");
}
// InReview Articles can only be accessed by reviewers
if (Article.Status is ArticleStatus.InReview) {
if (principal.IsInRole("Reviewer")) {
return Article;
}
throw new ApplicationException("Cannot access in-review article without being a reviewer or admin.");
}
throw new ApplicationException("User does not have access to this article.");
}
protected override void OnInitialized() {
// We need blocking calls here, bc otherwise Blazor will execute Render in parallel,
// running into a null pointer on the Article property and panicking
using var context = ContextFactory.CreateDbContext();
Article = context.Set<Article>()
.Include(a => a.Author)
.Include(a => a.Reviewer)
.FirstOrDefault(a => a.Id == Id);
}
private async Task SubmitForReview() {
await using var context = await ContextFactory.CreateDbContextAsync();
Article!.Status = ArticleStatus.InReview;
context.Update(Article);
await context.SaveChangesAsync();
Navigation.NavigateTo("/");
}
private async Task SubmitForPublish() {
await using var context = await ContextFactory.CreateDbContextAsync();
Article!.Status = ArticleStatus.Published;
context.Update(Article);
await context.SaveChangesAsync();
Navigation.NavigateTo("/");
}
}