[backend/razor] Add basic user page public preview, handle user id redirects correctly
This commit is contained in:
parent
80c4e9dd88
commit
9f2cb34d0e
6 changed files with 194 additions and 1 deletions
42
Iceshrimp.Backend/Controllers/Razor/RedirectController.cs
Normal file
42
Iceshrimp.Backend/Controllers/Razor/RedirectController.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using System.Net.Mime;
|
||||
using Iceshrimp.Backend.Controllers.Shared.Attributes;
|
||||
using Iceshrimp.Backend.Core.Configuration;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Middleware;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Razor;
|
||||
|
||||
[ApiController]
|
||||
[Authenticate]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Produces(MediaTypeNames.Application.Json)]
|
||||
public class RedirectController(IOptionsSnapshot<Config.SecuritySection> config, DatabaseContext db) : ControllerBase
|
||||
{
|
||||
// @formatter:off
|
||||
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataUsage", Justification = "IncludeCommonProperties")]
|
||||
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataQuery", Justification = "IncludeCommonProperties")]
|
||||
// @formatter:on
|
||||
[HttpGet("/users/{id}")]
|
||||
[ProducesResults(HttpStatusCode.OK)]
|
||||
[ProducesErrors(HttpStatusCode.Forbidden, HttpStatusCode.NotFound)]
|
||||
public async Task<IActionResult> GetUser(string id)
|
||||
{
|
||||
var localUser = HttpContext.GetUser();
|
||||
if (config.Value.PublicPreview == Enums.PublicPreview.Lockdown && localUser == null)
|
||||
throw GracefulException.Forbidden("Public preview is disabled on this instance");
|
||||
|
||||
var user = await db.Users.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == id) ??
|
||||
throw GracefulException.NotFound("User not found");
|
||||
|
||||
return user.IsLocalUser
|
||||
? Redirect($"/@{user.Username}")
|
||||
: Redirect(user.UserProfile?.Url ?? user.Uri ?? throw new Exception("Remote user must have an URI"));
|
||||
}
|
||||
}
|
|
@ -101,6 +101,11 @@ public static class MvcBuilderExtensions
|
|||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IMvcBuilder AddRouteOverrides(this IMvcBuilder builder)
|
||||
{
|
||||
return builder.AddRazorPagesOptions(o => { o.Conventions.AddPageRoute("/User", "@{user}@{host}"); });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
41
Iceshrimp.Backend/Pages/User.cshtml
Normal file
41
Iceshrimp.Backend/Pages/User.cshtml
Normal file
|
@ -0,0 +1,41 @@
|
|||
@page
|
||||
@attribute [RazorCompiledItemMetadata("RouteTemplate", "/@{user}")]
|
||||
@using Microsoft.AspNetCore.Razor.Hosting
|
||||
@model UserModel
|
||||
|
||||
@if (Model.User == null)
|
||||
{
|
||||
Response.StatusCode = 404;
|
||||
<div>
|
||||
<h2>Not found</h2>
|
||||
<p>This user doesn't appear to exist on this server</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewData["title"] = $"@{Model.User.Username} - Iceshrimp.NET";
|
||||
@section head
|
||||
{
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="og:site_name" content="Iceshrimp.NET">
|
||||
<meta name="og:title" content="@@@Model.User.Username">
|
||||
@if (Model.User.UserProfile?.Description is { } bio)
|
||||
{
|
||||
<meta name="og:description" content="@bio)">
|
||||
}
|
||||
<meta name="og:image" content="@(Model.User.AvatarUrl ?? $"/identicon/{Model.User.Id}")">
|
||||
}
|
||||
|
||||
<div class="user">
|
||||
<div class="header">
|
||||
<img src="@(Model.User.AvatarUrl ?? $"/identicon/{Model.User.Id}")" class="avatar" alt="User avatar"/>
|
||||
<div class="title">
|
||||
<span>@(Model.User.DisplayName ?? Model.User.Username)</span>
|
||||
</div>
|
||||
<span class="acct">@@@Model.User.Acct</span>
|
||||
</div>
|
||||
<div class="bio">
|
||||
@Html.Raw(Model.Bio ?? "<i>This user hasn't added a bio yet.</i>")
|
||||
</div>
|
||||
</div>
|
||||
}
|
61
Iceshrimp.Backend/Pages/User.cshtml.cs
Normal file
61
Iceshrimp.Backend/Pages/User.cshtml.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Iceshrimp.Backend.Core.Configuration;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
||||
using Iceshrimp.Backend.Core.Middleware;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Iceshrimp.Backend.Pages;
|
||||
|
||||
public class UserModel(
|
||||
DatabaseContext db,
|
||||
IOptions<Config.SecuritySection> security,
|
||||
IOptions<Config.InstanceSection> instance,
|
||||
MfmConverter mfm
|
||||
) : PageModel
|
||||
{
|
||||
public new User? User;
|
||||
public string? Bio;
|
||||
|
||||
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataQuery",
|
||||
Justification = "IncludeCommonProperties")]
|
||||
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataUsage", Justification = "Same as above")]
|
||||
public async Task<IActionResult> OnGet(string user, string? host)
|
||||
{
|
||||
if (Request.Cookies.ContainsKey("session") || Request.Cookies.ContainsKey("sessions"))
|
||||
return Partial("Shared/FrontendSPA");
|
||||
|
||||
if (security.Value.PublicPreview == Enums.PublicPreview.Lockdown)
|
||||
throw GracefulException.Forbidden("Public preview is disabled on this instance.",
|
||||
"The instance administrator has intentionally disabled this feature for privacy reasons.");
|
||||
|
||||
//TODO: login button
|
||||
//TODO: user note view (respect public preview settings - don't show renotes of remote notes if set to restricted or lower)
|
||||
//TODO: emoji
|
||||
|
||||
user = user.ToLowerInvariant();
|
||||
host = host?.ToLowerInvariant();
|
||||
|
||||
if (host == instance.Value.AccountDomain || host == instance.Value.WebDomain)
|
||||
host = null;
|
||||
|
||||
User = await db.Users.IncludeCommonProperties()
|
||||
.Where(p => p.UsernameLower == user && p.Host == host)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (User is { IsRemoteUser: true })
|
||||
return RedirectPermanent(User.UserProfile?.Url ??
|
||||
User.Uri ??
|
||||
throw new Exception("User is remote but has no uri"));
|
||||
|
||||
if (User?.UserProfile?.Description is { } bio)
|
||||
Bio = await mfm.ToHtmlAsync(bio, User.UserProfile.Mentions, User.Host, divAsRoot: true);
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
44
Iceshrimp.Backend/Pages/User.cshtml.css
Normal file
44
Iceshrimp.Backend/Pages/User.cshtml.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
.bio {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding-top: inherit;
|
||||
}
|
||||
|
||||
.user {
|
||||
background-color: #f7f7f7;
|
||||
background-color: var(--background-alt);
|
||||
padding: 12px;
|
||||
margin: 1em 0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(3em, 3em));
|
||||
grid-template-rows: repeat(2, 1.5em);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
grid-column: 1;
|
||||
grid-row: 1/3;
|
||||
border-radius: 5px;
|
||||
object-fit: cover;
|
||||
width: 3em;
|
||||
max-height: 3em;
|
||||
}
|
||||
|
||||
.title {
|
||||
grid-column: 2/-1;
|
||||
grid-row: 1;
|
||||
padding-left: 10px;
|
||||
color: var(--text-bright);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.acct {
|
||||
grid-column: 2/-1;
|
||||
grid-row: 2;
|
||||
padding-left: 10px;
|
||||
}
|
|
@ -30,7 +30,7 @@ builder.Services.AddAuthorizationPolicies();
|
|||
builder.Services.AddAuthenticationServices();
|
||||
builder.Services.AddSignalR().AddMessagePackProtocol();
|
||||
builder.Services.AddResponseCompression();
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddRazorPages().AddRouteOverrides();
|
||||
|
||||
builder.Services.AddServices(builder.Configuration);
|
||||
builder.Services.ConfigureServices(builder.Configuration);
|
||||
|
|
Loading…
Add table
Reference in a new issue