Compare commits

..

9 Commits

Author SHA1 Message Date
Oliver Booth 9ea4425c26
Merge branch 'main' into feature/admin
.NET / Build & Test (push) Failing after 1m7s Details
2024-03-16 18:10:05 +00:00
Oliver Booth 9698a0b567
fix: add missing required properties to shields.io schema
.NET / Build & Test (push) Failing after 2m27s Details
2024-03-16 18:09:12 +00:00
Oliver Booth d7bc6a368c
Merge branch 'main' into feature/admin 2024-03-16 18:06:42 +00:00
Oliver Booth c62194ee70
feat: add shields.io compatible badge endpoint
.NET / Build & Test (push) Failing after 1m24s Details
2024-03-16 18:03:31 +00:00
Oliver Booth 1862fa3ab4
Merge branch 'main' into feature/admin 2024-03-16 14:36:58 +00:00
Oliver Booth 9dc12ba3b3
refactor: remove usa countdown timer
.NET / Build & Test (push) Failing after 1m20s Details
2024-03-16 12:34:35 +00:00
Oliver Booth 90f6d34af9
style: reduce nav link size 2024-03-16 12:28:39 +00:00
Oliver Booth a7f7d7862c
style: add rounded border to main container 2024-03-16 12:28:02 +00:00
Oliver Booth d9101034c3
chore: update nuget dependencies
- MailKit 4.4.0
- Markdig 0.36.2
- Microsoft.AspNetCore.Components.Web 8.0.3
- Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation 8.0.3
- Microsoft.Extensions.FileProviders.Embedded 8.0.3
- Pomelo.EntityFrameworkCore.MySql 8.0.2
2024-03-16 12:21:14 +00:00
8 changed files with 234 additions and 56 deletions

View File

@ -0,0 +1,112 @@
using System.Net.Http.Headers;
using System.Reflection;
using System.Text.Json.Serialization;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
namespace OliverBooth.Api.Controllers.v1;
[ApiController]
[Route("v{version:apiVersion}/api/badge")]
[Produces("application/json")]
[ApiVersion(1)]
public sealed class BadgeController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IHttpClientFactory _httpClientFactory;
private readonly string _version;
/// <summary>
/// Initializes a new instance of the <see cref="BadgeController" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="httpClientFactory">The HTTP client factory.</param>
public BadgeController(IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
_configuration = configuration;
_httpClientFactory = httpClientFactory;
var attribute = typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
_version = attribute?.InformationalVersion ?? "1.0.0";
}
/// <summary>
/// Returns a JSON object that is compatible with a shields.io custom endpoint.
/// </summary>
/// <param name="repo">The repository name.</param>
/// <param name="workflow">The workflow.</param>
/// <param name="owner">The owner. Defaults to <c>oliverbooth</c>.</param>
/// <returns>A JSON object.</returns>
[HttpGet("status/{repo}/{workflow}")]
[HttpGet("status/{owner}/{repo}/{workflow}")]
[EndpointDescription("Returns a JSON object that is compatible with a shields.io custom endpoint.")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> StatusAsync(string repo, string workflow, string owner = "oliverbooth")
{
string? githubToken = _configuration.GetSection("GitHub:Token").Value;
var url = $"https://api.github.com/repos/{owner}/{repo}/actions/workflows/{workflow}/runs";
Console.WriteLine(url);
using HttpClient client = _httpClientFactory.CreateClient();
using var request = new HttpRequestMessage();
request.RequestUri = new Uri(url);
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", $"Bearer {githubToken}");
request.Headers.Add("X-GitHub-Api-Version", "2022-11-28");
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("oliverbooth.dev", _version));
using HttpResponseMessage response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
var body = await response.Content.ReadFromJsonAsync<WorkflowRunSchema>();
WorkflowRun? run = body?.WorkflowRuns.FirstOrDefault(r => r.Status == WorkflowRunStatus.Completed);
if (run is not null)
{
var color = run.Conclusion switch
{
WorkflowRunConclusion.Failure => "e05d44",
WorkflowRunConclusion.Success => "44cc11",
_ => "lightgray"
};
var message = run.Conclusion switch
{
WorkflowRunConclusion.Failure => "failing",
WorkflowRunConclusion.Success => "passing",
_ => "unknown"
};
return Ok(new { schemaVersion = 1, label = "build", color, message });
}
return Ok(new { schemaVersion = 1, label = "build", color = "lightgray", message = "unknown" });
}
private class WorkflowRunSchema
{
[JsonPropertyName("workflow_runs"), JsonInclude]
public WorkflowRun[] WorkflowRuns { get; set; } = Array.Empty<WorkflowRun>();
}
private class WorkflowRun
{
[JsonPropertyName("conclusion"), JsonInclude]
[JsonConverter(typeof(JsonStringEnumConverter<WorkflowRunConclusion>))]
public WorkflowRunConclusion Conclusion { get; set; } = WorkflowRunConclusion.Unknown;
[JsonPropertyName("status"), JsonInclude]
[JsonConverter(typeof(JsonStringEnumConverter<WorkflowRunStatus>))]
public WorkflowRunStatus Status { get; set; } = WorkflowRunStatus.Unknown;
}
private enum WorkflowRunStatus
{
Unknown = -1,
Completed
}
private enum WorkflowRunConclusion
{
Unknown = -1,
Success,
Failure
}
}

View File

@ -0,0 +1,112 @@
using System.Net.Http.Headers;
using System.Reflection;
using System.Text.Json.Serialization;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
namespace OliverBooth.Api.Controllers.v2;
[ApiController]
[Route("v{version:apiVersion}/api/badge")]
[Produces("application/json")]
[ApiVersion(2)]
public sealed class BadgeController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IHttpClientFactory _httpClientFactory;
private readonly string _version;
/// <summary>
/// Initializes a new instance of the <see cref="BadgeController" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="httpClientFactory">The HTTP client factory.</param>
public BadgeController(IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
_configuration = configuration;
_httpClientFactory = httpClientFactory;
var attribute = typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
_version = attribute?.InformationalVersion ?? "1.0.0";
}
/// <summary>
/// Returns a JSON object that is compatible with a shields.io custom endpoint.
/// </summary>
/// <param name="repo">The repository name.</param>
/// <param name="workflow">The workflow.</param>
/// <param name="owner">The owner. Defaults to <c>oliverbooth</c>.</param>
/// <returns>A JSON object.</returns>
[HttpGet("status/{repo}/{workflow}")]
[HttpGet("status/{owner}/{repo}/{workflow}")]
[EndpointDescription("Returns a JSON object that is compatible with a shields.io custom endpoint.")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> StatusAsync(string repo, string workflow, string owner = "oliverbooth")
{
string? githubToken = _configuration.GetSection("GitHub:Token").Value;
var url = $"https://api.github.com/repos/{owner}/{repo}/actions/workflows/{workflow}/runs";
Console.WriteLine(url);
using HttpClient client = _httpClientFactory.CreateClient();
using var request = new HttpRequestMessage();
request.RequestUri = new Uri(url);
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", $"Bearer {githubToken}");
request.Headers.Add("X-GitHub-Api-Version", "2022-11-28");
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("oliverbooth.dev", _version));
using HttpResponseMessage response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
var body = await response.Content.ReadFromJsonAsync<WorkflowRunSchema>();
WorkflowRun? run = body?.WorkflowRuns.FirstOrDefault(r => r.Status == WorkflowRunStatus.Completed);
if (run is not null)
{
var color = run.Conclusion switch
{
WorkflowRunConclusion.Failure => "e05d44",
WorkflowRunConclusion.Success => "44cc11",
_ => "lightgray"
};
var message = run.Conclusion switch
{
WorkflowRunConclusion.Failure => "failing",
WorkflowRunConclusion.Success => "passing",
_ => "unknown"
};
return Ok(new { schemaVersion = 1, label = "build", color, message });
}
return Ok(new { schemaVersion = 1, label = "build", color = "lightgray", message = "unknown" });
}
private class WorkflowRunSchema
{
[JsonPropertyName("workflow_runs"), JsonInclude]
public WorkflowRun[] WorkflowRuns { get; set; } = Array.Empty<WorkflowRun>();
}
private class WorkflowRun
{
[JsonPropertyName("conclusion"), JsonInclude]
[JsonConverter(typeof(JsonStringEnumConverter<WorkflowRunConclusion>))]
public WorkflowRunConclusion Conclusion { get; set; } = WorkflowRunConclusion.Unknown;
[JsonPropertyName("status"), JsonInclude]
[JsonConverter(typeof(JsonStringEnumConverter<WorkflowRunStatus>))]
public WorkflowRunStatus Status { get; set; } = WorkflowRunStatus.Unknown;
}
private enum WorkflowRunStatus
{
Unknown = -1,
Completed
}
private enum WorkflowRunConclusion
{
Unknown = -1,
Success,
Failure
}
}

View File

@ -11,11 +11,11 @@
<PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0"/>
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
<PackageReference Include="Markdig" Version="0.35.0"/>
<PackageReference Include="Markdig" Version="0.36.2"/>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
<PackageReference Include="NetBarcode" Version="1.7.0"/>
<PackageReference Include="Otp.NET" Version="1.3.0"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>
<PackageReference Include="Serilog" Version="3.1.1"/>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1"/>
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0"/>

View File

@ -31,11 +31,11 @@
<PackageReference Include="FluentFTP" Version="49.0.2"/>
<PackageReference Include="FluentFTP.Logging" Version="1.0.0"/>
<PackageReference Include="HtmlAgilityPack" Version="1.11.59"/>
<PackageReference Include="MailKit" Version="4.3.0"/>
<PackageReference Include="MailKit" Version="4.4.0"/>
<PackageReference Include="MailKitSimplified.Sender" Version="2.9.0"/>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.2"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.2"/>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.2"/>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3"/>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.3"/>
</ItemGroup>
<ItemGroup>

View File

@ -118,20 +118,6 @@
<div style="margin:50px 0;"></div>
@if (!doAprilFools && DateTimeOffset.UtcNow < new DateTime(2024, 03, 08))
{
<div id="usa-countdown" class="container">
<div class="row">
<div class="col-3 usa-countdown-element" id="usa-countdown-days">00</div>
<div class="col-3 usa-countdown-element" id="usa-countdown-hours">00</div>
<div class="col-3 usa-countdown-element" id="usa-countdown-minutes">00</div>
<div class="col-3 usa-countdown-element" id="usa-countdown-seconds">00</div>
</div>
</div>
}
<div style="margin:50px 0;"></div>
<div class="container">
@if (doAprilFools)
{

View File

@ -26,6 +26,7 @@ body {
main.container {
background: #333;
padding: 20px;
border-radius: 5px;
}
a {
@ -109,7 +110,7 @@ nav {
ul.site-nav {
list-style-type: none;
padding: 0;
margin: 0 auto;
margin: 20px auto 0 auto;
li {
display: inline-block;
@ -120,7 +121,7 @@ nav {
}
a {
font-size: 24px;
font-size: 20px;
&:link, &:visited, &:hover, &:active {
text-decoration: none;
@ -142,6 +143,7 @@ nav {
article {
background: #333333;
padding: 20px;
border-radius: 5px;
*:last-child {
margin-bottom: 0;

View File

@ -82,33 +82,6 @@ class UI {
UI.updateProjectCards(element);
}
public static updateUsaCountdown(element?: Element){
element = element || document.getElementById("usa-countdown");
const daysElement = element.querySelector("#usa-countdown-days");
const hoursElement = element.querySelector("#usa-countdown-hours");
const minutesElement = element.querySelector("#usa-countdown-minutes");
const secondsElement = element.querySelector("#usa-countdown-seconds");
const start = new Date().getTime();
const end = Date.UTC(2024, 2, 7, 13, 20);
const diff = end - start;
let days = Math.floor(diff / (1000 * 60 * 60 * 24));
let hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
let seconds = Math.floor((diff % (1000 * 60)) / 1000);
if (days < 0) days = 0
if (hours < 0) hours = 0;
if (minutes < 0) minutes = 0;
if (seconds < 0) seconds = 0;
daysElement.innerHTML = days.toString().padStart(2, '0');
hoursElement.innerHTML = hours.toString().padStart(2, '0');
minutesElement.innerHTML = minutes.toString().padStart(2, '0');
secondsElement.innerHTML = seconds.toString().padStart(2, '0');
}
/**
* Adds Bootstrap tooltips to all elements with a title attribute.
* @param element The element to search for elements with a title attribute in.

View File

@ -98,13 +98,6 @@ declare const Prism: any;
UI.updateUI();
const usaCountdown = document.getElementById("usa-countdown");
if (usaCountdown) {
usaCountdown.addEventListener("click", () => window.location.href = "/blog/2024/02/19/the-american");
UI.updateUsaCountdown(usaCountdown);
setInterval(() => UI.updateUsaCountdown(usaCountdown), 1000);
}
let avatarType = 0;
const headshot = document.getElementById("index-headshot") as HTMLImageElement;
if (headshot) {