Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch backend to ASP and Kestrel #25

Merged
merged 1 commit into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions GuildWarsPartySearch.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GuildWarsPartySearch.Common
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GuildWarsPartySearch.Tests", "GuildWarsPartySearch.Tests\GuildWarsPartySearch.Tests.csproj", "{9FCED959-3EE9-40B7-B6A8-AF13FAB54B9C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pipelines", "Pipelines", "{6C63473D-A6D9-400D-94AD-2E4DBA48B8B8}"
ProjectSection(SolutionItems) = preProject
.github\workflows\content-deploy.yaml = .github\workflows\content-deploy.yaml
.github\workflows\docker-deploy.yaml = .github\workflows\docker-deploy.yaml
.github\workflows\test.yaml = .github\workflows\test.yaml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GuildWarsPartySearch.FrontEnd", "GuildWarsPartySearch.FrontEnd\GuildWarsPartySearch.FrontEnd.csproj", "{3BC53FC6-A022-4D14-B917-5A679AF41EC6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GuildWarsPartySearch.FrontEnd", "GuildWarsPartySearch.FrontEnd\GuildWarsPartySearch.FrontEnd.csproj", "{3BC53FC6-A022-4D14-B917-5A679AF41EC6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
103 changes: 103 additions & 0 deletions GuildWarsPartySearch/BackgroundServices/ContentRetrievalService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs;
using GuildWarsPartySearch.Server.Options;
using System.Extensions;
using Microsoft.Extensions.Options;
using System.Core.Extensions;

namespace GuildWarsPartySearch.Server.BackgroundServices;

public sealed class ContentRetrievalService : BackgroundService
{
private readonly ContentOptions contentOptions;
private readonly StorageAccountOptions storageAccountOptions;
private readonly ILogger<ContentRetrievalService> logger;

public ContentRetrievalService(
IOptions<ContentOptions> contentOptions,
IOptions<StorageAccountOptions> storageAccountOptions,
ILogger<ContentRetrievalService> logger)
{
this.contentOptions = contentOptions.Value.ThrowIfNull();
this.storageAccountOptions = storageAccountOptions.Value.ThrowIfNull();
this.logger = logger.ThrowIfNull();
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.ExecuteAsync), string.Empty);
while(!stoppingToken.IsCancellationRequested)
{
try
{
await this.UpdateContent(stoppingToken);
}
catch (Exception ex)
{
scopedLogger.LogError(ex, "Encountered exception while retrieving content");
}

await Task.Delay(this.contentOptions.UpdateFrequency, stoppingToken);
}
}

private async Task UpdateContent(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.UpdateContent), string.Empty);
scopedLogger.LogInformation("Checking content to retrieve");
var serviceBlobClient = new BlobServiceClient(this.storageAccountOptions.ConnectionString);
var blobContainerClient = serviceBlobClient.GetBlobContainerClient(this.storageAccountOptions.ContainerName);
var blobs = blobContainerClient.GetBlobsAsync(cancellationToken: cancellationToken);

if (!Directory.Exists(this.contentOptions.StagingFolder))
{
Directory.CreateDirectory(this.contentOptions.StagingFolder);
}

scopedLogger.LogInformation($"Retrieving blobs");
var blobList = new List<BlobItem>();
await foreach (var blob in blobs)
{
blobList.Add(blob);
}

var stagingFolderFullPath = Path.GetFullPath(this.contentOptions.StagingFolder);
var stagedFiles = Directory.GetFiles(this.contentOptions.StagingFolder, "*", SearchOption.AllDirectories);
var filesToDelete = stagedFiles
.Select(f => Path.GetFullPath(f).Replace(stagingFolderFullPath, "").Replace('\\', '/').Trim('/'))
.Where(f => blobList.None(b => b.Name == f));
foreach (var file in filesToDelete)
{
scopedLogger.LogInformation($"[{file}] File not in blob. Deleting");
File.Delete($"{stagingFolderFullPath}\\{file}");
}

foreach (var blob in blobList)
{
var finalPath = Path.Combine(this.contentOptions.StagingFolder, blob.Name);
var fileInfo = new FileInfo(finalPath);
fileInfo.Directory!.Create();
if (fileInfo.Exists &&
fileInfo.CreationTimeUtc == blob.Properties.LastModified?.UtcDateTime &&
fileInfo.Length == blob.Properties.ContentLength)
{
scopedLogger.LogInformation($"[{blob.Name}] File unchanged. Skipping");
continue;
}

var blobClient = blobContainerClient.GetBlobClient(blob.Name);
using var fileStream = new FileStream(finalPath, FileMode.Create);
using var blobStream = await blobClient.OpenReadAsync(new BlobOpenReadOptions(false)
{
BufferSize = 1024,
}, cancellationToken);

scopedLogger.LogInformation($"[{blob.Name}] Downloading blob");
await blobStream.CopyToAsync(fileStream, cancellationToken);
scopedLogger.LogInformation($"[{blob.Name}] Downloaded blob");

fileInfo = new FileInfo(finalPath);
fileInfo.CreationTimeUtc = blob.Properties.LastModified?.UtcDateTime ?? DateTime.UtcNow;
}
}
}
4 changes: 2 additions & 2 deletions GuildWarsPartySearch/BuildDockerImage.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
dotnet publish -c Release
Copy-Item -Path Config.Release.json -Destination ./bin/Release/net8.0/publish/Config.json
dotnet publish -r linux-x64 -c Release -o Publish/
Copy-Item -Path Config.Release.json -Destination Publish/Config.json
docker build -t guildwarspartysearch.server .
docker tag guildwarspartysearch.server guildwarspartysearch.azurecr.io/guildwarspartysearch.server
9 changes: 8 additions & 1 deletion GuildWarsPartySearch/Config.Debug.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"EnvironmentOptions": {
"Name": "Debug"
},
Expand All @@ -15,6 +22,6 @@
},
"ContentOptions": {
"UpdateFrequency": "0:5:0",
"StagingFolder": "Content"
"StagingFolder": "Content"
}
}
7 changes: 7 additions & 0 deletions GuildWarsPartySearch/Config.Release.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"EnvironmentOptions": {
"Name": "Release"
},
Expand Down
20 changes: 5 additions & 15 deletions GuildWarsPartySearch/Converters/Base64ToCertificateConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,22 @@

namespace GuildWarsPartySearch.Server.Converters;

public sealed class Base64ToCertificateConverter : JsonConverter
public sealed class Base64ToCertificateConverter : JsonConverter<X509Certificate2>
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(X509Certificate2);
}

public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
public override X509Certificate2? ReadJson(JsonReader reader, Type objectType, X509Certificate2? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value is not string base64)
{
throw new InvalidOperationException($"Cannot convert {reader.Value} to {nameof(X509Certificate2)}");
throw new InvalidOperationException($"Cannot deserialize {nameof(X509Certificate2)} from {reader.Value}");
}

var bytes = Convert.FromBase64String(base64);
return new X509Certificate2(bytes);
}

public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, X509Certificate2? value, JsonSerializer serializer)
{
if (value is not X509Certificate2 certificate2)
{
throw new InvalidOperationException($"Cannot convert {value} as {nameof(X509Certificate2)}");
}

var base64 = Convert.ToBase64String(certificate2.GetRawCertData());
var base64 = Convert.ToBase64String(value!.GetRawCertData());
writer.WriteValue(base64);
}
}
19 changes: 0 additions & 19 deletions GuildWarsPartySearch/Converters/NoneConverter.cs

This file was deleted.

18 changes: 0 additions & 18 deletions GuildWarsPartySearch/Converters/NoneWebsocketMessageConverter.cs

This file was deleted.

This file was deleted.

This file was deleted.

4 changes: 2 additions & 2 deletions GuildWarsPartySearch/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
FROM mcr.microsoft.com/dotnet/runtime:8.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0

# Set the working directory inside the container
WORKDIR /app

EXPOSE 80 443

# Copy the build output to the working directory
COPY ./bin/Release/net8.0/publish/ .
COPY Publish/ .

# Command to run the executable
ENTRYPOINT ["dotnet", "GuildWarsPartySearch.Server.dll"]
36 changes: 0 additions & 36 deletions GuildWarsPartySearch/Endpoints/Kill.cs

This file was deleted.

Loading
Loading