Compare commits
9 Commits
869ee77446
...
9ea4425c26
Author | SHA1 | Date | |
---|---|---|---|
9ea4425c26 | |||
9698a0b567 | |||
d7bc6a368c | |||
c62194ee70 | |||
1862fa3ab4 | |||
9dc12ba3b3 | |||
90f6d34af9 | |||
a7f7d7862c | |||
d9101034c3 |
112
OliverBooth.Api/Controllers/v1/BadgeController.cs
Normal file
112
OliverBooth.Api/Controllers/v1/BadgeController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
112
OliverBooth.Api/Controllers/v2/BadgeController.cs
Normal file
112
OliverBooth.Api/Controllers/v2/BadgeController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -11,11 +11,11 @@
|
|||||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0"/>
|
<PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0"/>
|
||||||
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
|
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
|
<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="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
|
||||||
<PackageReference Include="NetBarcode" Version="1.7.0"/>
|
<PackageReference Include="NetBarcode" Version="1.7.0"/>
|
||||||
<PackageReference Include="Otp.NET" Version="1.3.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" Version="3.1.1"/>
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1"/>
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1"/>
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0"/>
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0"/>
|
||||||
|
@ -31,11 +31,11 @@
|
|||||||
<PackageReference Include="FluentFTP" Version="49.0.2"/>
|
<PackageReference Include="FluentFTP" Version="49.0.2"/>
|
||||||
<PackageReference Include="FluentFTP.Logging" Version="1.0.0"/>
|
<PackageReference Include="FluentFTP.Logging" Version="1.0.0"/>
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.59"/>
|
<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="MailKitSimplified.Sender" Version="2.9.0"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.2"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.2"/>
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.2"/>
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.3"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -118,20 +118,6 @@
|
|||||||
|
|
||||||
<div style="margin:50px 0;"></div>
|
<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">
|
<div class="container">
|
||||||
@if (doAprilFools)
|
@if (doAprilFools)
|
||||||
{
|
{
|
||||||
|
@ -26,6 +26,7 @@ body {
|
|||||||
main.container {
|
main.container {
|
||||||
background: #333;
|
background: #333;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -109,7 +110,7 @@ nav {
|
|||||||
ul.site-nav {
|
ul.site-nav {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 auto;
|
margin: 20px auto 0 auto;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -120,7 +121,7 @@ nav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
|
|
||||||
&:link, &:visited, &:hover, &:active {
|
&:link, &:visited, &:hover, &:active {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -142,6 +143,7 @@ nav {
|
|||||||
article {
|
article {
|
||||||
background: #333333;
|
background: #333333;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
*:last-child {
|
*:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -82,33 +82,6 @@ class UI {
|
|||||||
UI.updateProjectCards(element);
|
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.
|
* Adds Bootstrap tooltips to all elements with a title attribute.
|
||||||
* @param element The element to search for elements with a title attribute in.
|
* @param element The element to search for elements with a title attribute in.
|
||||||
|
@ -98,13 +98,6 @@ declare const Prism: any;
|
|||||||
|
|
||||||
UI.updateUI();
|
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;
|
let avatarType = 0;
|
||||||
const headshot = document.getElementById("index-headshot") as HTMLImageElement;
|
const headshot = document.getElementById("index-headshot") as HTMLImageElement;
|
||||||
if (headshot) {
|
if (headshot) {
|
||||||
|
Loading…
Reference in New Issue
Block a user