Implemented featured endpoint and embed script

This commit is contained in:
Mia Rose Winter 2024-02-29 16:42:05 +01:00
parent 1e10d41cad
commit 97a73f0a32
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
6 changed files with 176 additions and 1 deletions

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System.ComponentModel.DataAnnotations;
using Wave.Data;
using Wave.Data.Api;
namespace Wave.Controllers;
[ApiController]
[Route("/[controller]")]
public class ApiController(ApplicationDbContext context, IOptions<Customization> customizationOptions) : ControllerBase {
[HttpGet("article/featured")]
[Produces("application/json")]
public async Task<Results<Ok<ArticleDto>, NoContent>> GetArticleFeatured([FromQuery, Range(16, 800)] int profilePictureSize = 800) {
Response.Headers.AccessControlAllowOrigin = "*";
var article = await context.Set<Article>()
.IgnoreAutoIncludes()
.Include(a => a.Author).ThenInclude(a => a.Articles)
.Include(a => a.Reviewer)
.Include(a => a.Categories)
.OrderByDescending(a => a.PublishDate).ThenBy(a => a.Id)
.FirstOrDefaultAsync();
if (article is null) return TypedResults.NoContent();
return TypedResults.Ok(ArticleDto.GetFromArticle(article, GetHost(), profilePictureSize));
}
private Uri GetHost() {
string customUrl = customizationOptions.Value.AppUrl;
if (!string.IsNullOrEmpty(customUrl)) return new Uri(customUrl, UriKind.Absolute);
return new Uri($"{Request.Scheme}://{Request.Host}");
}
}

View file

@ -0,0 +1,28 @@
using Wave.Utilities;
namespace Wave.Data.Api;
public record ArticleDto(
string Title,
string ContentPreview,
string BrowserUrl,
DateTimeOffset PublishDate,
UserDto Author,
UserDto? Reviewer,
IList<CategoryDto> Categories) {
public static ArticleDto GetFromArticle(Article article, Uri host, int pfpSize) {
string browserLink = ArticleUtilities.GenerateArticleLink(article, host);
var author = UserDto.GetFromUser(article.Author, host, pfpSize);
var reviewer =
article.Reviewer is not null &&
article.Reviewer.Id != article.Author.Id
? UserDto.GetFromUser(article.Reviewer, host, pfpSize) : null;
var categories = article.Categories.Select(c => new CategoryDto(c)).ToArray();
string preview = article.BodyPlain[..Math.Min(article.BodyPlain.Length, 500)];
return new ArticleDto(article.Title, preview, browserLink, article.PublishDate, author, reviewer, categories);
}
}

View file

@ -0,0 +1,9 @@
using Wave.Utilities;
namespace Wave.Data.Api;
public record CategoryDto(string Name, string Role) {
public CategoryDto(Category category)
: this(category.Name, CategoryUtilities.GetCssClassPostfixForColor(category.Color)) {}
}

14
Wave/Data/Api/UserDto.cs Normal file
View file

@ -0,0 +1,14 @@
namespace Wave.Data.Api;
public record UserDto(
string Name,
string ProfilePictureUrl,
string? ProfileUrl) {
public static UserDto GetFromUser(ApplicationUser user, Uri host, int pfpSize) {
var pfpUrl = new Uri(host, $"/api/User/pfp/{user.Id}?size={pfpSize}");
var profileUrl = user.Articles.Count > 0 ? new Uri(host, $"/profile/{user.Id}") : null;
return new UserDto(user.FullName ?? "Guest Author", pfpUrl.AbsoluteUri, profileUrl?.AbsoluteUri);
}
}

86
Wave/wwwroot/featured.js Normal file
View file

@ -0,0 +1,86 @@
const doInsertText = function(dataName, content) {
const element = document.querySelector(`[data-wave-${dataName}]`);
if (element) {
if (element.tagName === "A") {
element.href = content;
} else if (element.tagName === "IMG") {
element.src = content;
} else {
element.innerText = content;
}
}
}
const script = document.getElementById("wave-script");
if (!script) {
throw new Error("[WAVE] no script with the id 'wave-script' exists.");
}
if (!script.src) {
throw new Error("[WAVE] failed to get src attribute of element with id 'wave-script'.");
}
const scriptUrl = new URL(script.src);
const host = `${scriptUrl.protocol}//${scriptUrl.host}`;
let pfpSize = 150;
const container = document.querySelector("[data-wave]");
if (container && container.dataset.wavePfpSize) {
const value = parseInt(container.dataset.wavePfpSize);
if (value && value > 800) console.log("[WAVE] WARNING: pfp sizes greater 800 are not supported.");
else if (value) pfpSize = value;
else console.log(
"[WAVE] WARNING: a custom pfp size has been provided with 'data-wave-pfp-size', " +
"but it's value could not be parsed as an integer.");
}
console.log("[WAVE] requesting featured article");
fetch(new URL("/api/article/featured?size=" + pfpSize, host),
{
method: "GET",
headers: new Headers({
"Accept": "application/json"
})
})
.then(response => response.json())
.then(function(result) {
if (container) {
const template = document.querySelector("[data-wave-template]");
if (template) {
container.innerHTML = "";
container.appendChild(template.content.cloneNode(true));
} else {
container.innerHTML = `
<div style="padding: 1em; border: 1px solid black; box-shadow: 4px 4px 0 0 currentColor; background: #ffb3c8;">
<h1 data-wave-title style="margin: 0 0 0.5em 0"></h1>
<img style="float: left; margin: 0 0.5em 0.5em 0; border: 1px solid transparent; border-radius: 0.25em"
data-wave-author-profilePictureUrl alt="" width="${pfpSize}" />
<p style="line-height: 1.4em; margin: 0">
<a data-wave-author-profileUrl target="_blank" style="text-decoration: none; color: black">
<small data-wave-author-name style="font-weight: bold"></small><br>
</a>
<small data-wave-publishDate></small><br>
<span data-wave-contentPreview></span><br>
<a data-wave-browserUrl target="_blank" style="color: black">Read More</a>
</p>
</div>
`;
}
for (let [key, value] of Object.entries(result)) {
doInsertText(key, value);
if (typeof value === "object" && value != null) {
for (let [innerKey, innerValue] of Object.entries(value)) {
doInsertText(key + "-" + innerKey, innerValue);
}
}
}
console.log("[WAVE] fetched feature successfully.");
} else {
console.log("[WAVE] no container found, to use featured you require an element with the data tag 'wave'.");
}
})
.catch(err => console.log(`[WAVE] failed to request featured article: ${err}.`));