From bcc5f148c40f588b7198a9b6d59cb43242754cd7 Mon Sep 17 00:00:00 2001 From: Toby Burns Date: Tue, 12 Dec 2023 22:22:15 -0500 Subject: [PATCH 1/7] Big refactor, moving to azure functions, making contact page --- BackendFunctions/.gitignore | 264 ++++++++++++++++++ .../EndPoints/SendCommunication.cs | 62 ++++ .../PersonalWebsite.BackendFunctions.csproj | 38 +++ BackendFunctions/Program.cs | 43 +++ .../Properties/launchSettings.json | 7 + .../Properties/serviceDependencies.json | 11 + .../Properties/serviceDependencies.local.json | 11 + BackendFunctions/Shared/EmailValidator.cs | 20 ++ BackendFunctions/host.json | 11 + Client/Model/Program.cs | 24 +- Client/Pages/Blog.razor | 1 + Client/Pages/Contact.razor | 56 ++++ Client/Pages/Index.razor | 4 +- Client/Pages/Projects.razor | 1 + Client/PersonalWebsite.Client.csproj | 15 +- Client/Shared/{ => CV}/CVServiceExtensions.cs | 21 +- Client/Shared/{ => CV}/CVView.razor | 2 +- Client/Shared/{ => CV}/CV_temp.razor | 0 Client/Shared/Icons_Extensions.cs | 7 +- Client/Shared/Socials.razor | 10 - .../{ => Socials}/MudDrawerWithSocials.razor | 6 +- Client/Shared/Socials/Socials.razor | 28 ++ Client/wwwroot/appsettings.json | 12 + PersonalWebsite.sln | 15 +- Server/Pages/Error.cshtml | 42 --- Server/Pages/Error.cshtml.cs | 27 -- Server/PersonalWebsite.Server.csproj | 23 -- Server/Program.cs | 36 --- Server/Properties/launchSettings.json | 40 --- Server/appsettings.Development.json | 8 - Server/appsettings.json | 9 - Shared/{ => CV}/CVSection.cs | 7 +- Shared/{ => CV}/CVService.cs | 36 +-- Shared/Contact/ContactService.cs | 80 ++++++ Shared/Contact/EmailSender.cs | 77 +++++ Shared/Contact/Messaging.cs | 18 ++ Shared/PersonalWebsite.Shared.csproj | 14 +- 37 files changed, 823 insertions(+), 263 deletions(-) create mode 100644 BackendFunctions/.gitignore create mode 100644 BackendFunctions/EndPoints/SendCommunication.cs create mode 100644 BackendFunctions/PersonalWebsite.BackendFunctions.csproj create mode 100644 BackendFunctions/Program.cs create mode 100644 BackendFunctions/Properties/launchSettings.json create mode 100644 BackendFunctions/Properties/serviceDependencies.json create mode 100644 BackendFunctions/Properties/serviceDependencies.local.json create mode 100644 BackendFunctions/Shared/EmailValidator.cs create mode 100644 BackendFunctions/host.json create mode 100644 Client/Pages/Contact.razor rename Client/Shared/{ => CV}/CVServiceExtensions.cs (63%) rename Client/Shared/{ => CV}/CVView.razor (97%) rename Client/Shared/{ => CV}/CV_temp.razor (100%) delete mode 100644 Client/Shared/Socials.razor rename Client/Shared/{ => Socials}/MudDrawerWithSocials.razor (77%) create mode 100644 Client/Shared/Socials/Socials.razor create mode 100644 Client/wwwroot/appsettings.json delete mode 100644 Server/Pages/Error.cshtml delete mode 100644 Server/Pages/Error.cshtml.cs delete mode 100644 Server/PersonalWebsite.Server.csproj delete mode 100644 Server/Program.cs delete mode 100644 Server/Properties/launchSettings.json delete mode 100644 Server/appsettings.Development.json delete mode 100644 Server/appsettings.json rename Shared/{ => CV}/CVSection.cs (73%) rename Shared/{ => CV}/CVService.cs (75%) create mode 100644 Shared/Contact/ContactService.cs create mode 100644 Shared/Contact/EmailSender.cs create mode 100644 Shared/Contact/Messaging.cs diff --git a/BackendFunctions/.gitignore b/BackendFunctions/.gitignore new file mode 100644 index 0000000..ff5b00c --- /dev/null +++ b/BackendFunctions/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/BackendFunctions/EndPoints/SendCommunication.cs b/BackendFunctions/EndPoints/SendCommunication.cs new file mode 100644 index 0000000..605e690 --- /dev/null +++ b/BackendFunctions/EndPoints/SendCommunication.cs @@ -0,0 +1,62 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; +using PersonalWebsite.BackendFunctions.Shared; +using PersonalWebsite.Shared.Contact; +using PersonalWebsite.Shared.Messaging; + +namespace PersonalWebsite.BackendFunctions.EndPoints +{ + public class SendCommunication(ICommunicationSender communicationSender, + ICommunicationValidator communicationValidator, + ILogger logger) + { + private readonly ICommunicationSender CommunicationSender = communicationSender; + private readonly ICommunicationValidator CommunicationValidator = communicationValidator; + private readonly ILogger Logger = logger; + + [Function("SendCommunication")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "sendcommunication")] HttpRequestData req) + { + Logger.LogInformation("SendCommunicationByEmail: C# HTTP trigger function received a request."); + var requestMessage = await req.ReadFromJsonAsync(); + + if (requestMessage == null) + { + var body = await req.ReadAsStringAsync(); + Logger.LogError("SendCommunicationByEmail: Cannot convert HTTP request body json to ContactRequest - {}", body); + return new BadRequestObjectResult(req); + } + + var errorMessages = CommunicationValidator.Validate(requestMessage); + if (errorMessages.Any()) + { + Logger.LogError("SendCommunicationByEmail: Invalid argument - {}", string.Join(", ", errorMessages)); + return new BadRequestObjectResult(req); + } + + Logger.LogInformation("SendCommunicationByEmail: Request is - {} : {} : {}", + requestMessage.From, requestMessage.Subject, requestMessage.Content); + + try + { + await CommunicationSender.SendAsync(requestMessage); + return new OkObjectResult("Message succesfully sent!"); + } + catch (ArgumentException ex) + { + Logger.LogError("SendCommunicationByEmail: Invalid argument - {}", ex); + return new BadRequestObjectResult(req); + } + catch (Exception ex) + { + Logger.LogError("SendCommunicationByEmail: The following exception has occured - {}", ex); + return new StatusCodeResult((int)HttpStatusCode.InternalServerError); + } + + } + } +} diff --git a/BackendFunctions/PersonalWebsite.BackendFunctions.csproj b/BackendFunctions/PersonalWebsite.BackendFunctions.csproj new file mode 100644 index 0000000..7df61ba --- /dev/null +++ b/BackendFunctions/PersonalWebsite.BackendFunctions.csproj @@ -0,0 +1,38 @@ + + + net8.0 + v4 + Exe + enable + enable + f42baedd-2465-4cf0-b86b-3bb52e71077a + + + + + + + + + + + + + + + + + + + PreserveNewest + + + Always + Never + + + + + + + diff --git a/BackendFunctions/Program.cs b/BackendFunctions/Program.cs new file mode 100644 index 0000000..2c1ca78 --- /dev/null +++ b/BackendFunctions/Program.cs @@ -0,0 +1,43 @@ +using System.Net; +using System.Net.Mail; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using PersonalWebsite.BackendFunctions.Shared; +using PersonalWebsite.Shared.Contact; + +var host = new HostBuilder() + .ConfigureFunctionsWebApplication() + .ConfigureAppConfiguration(con => + { + con.AddUserSecrets(optional: true, reloadOnChange: false); + }) + .ConfigureServices((builder, services) => + { + services.AddApplicationInsightsTelemetryWorkerService(); + services.ConfigureFunctionsApplicationInsights(); + + services.AddScoped((serviceProvider) => + { + var config = serviceProvider.GetRequiredService(); + return new SmtpClient() + { + Host = config.GetValue("SmtpOptions:Host"), + Port = config.GetValue("SmtpOptions:Port"), + Credentials = new NetworkCredential( + config.GetValue("SmtpOptions:Username"), + config.GetValue("SmtpOptions:Password") + ), + EnableSsl = true + }; + }); + services.AddOptions() + .Configure((settings, configuration) => configuration.GetSection(nameof(EmailSenderOptions)).Bind(settings)) + .ValidateDataAnnotations(); + services.AddTransient(); + services.AddTransient(); + }) + .Build(); + +host.Run(); \ No newline at end of file diff --git a/BackendFunctions/Properties/launchSettings.json b/BackendFunctions/Properties/launchSettings.json new file mode 100644 index 0000000..5be6384 --- /dev/null +++ b/BackendFunctions/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "PersonalWebsite.BackendFunctions": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/BackendFunctions/Properties/serviceDependencies.json b/BackendFunctions/Properties/serviceDependencies.json new file mode 100644 index 0000000..df4dcc9 --- /dev/null +++ b/BackendFunctions/Properties/serviceDependencies.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + }, + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/BackendFunctions/Properties/serviceDependencies.local.json b/BackendFunctions/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..b804a28 --- /dev/null +++ b/BackendFunctions/Properties/serviceDependencies.local.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + }, + "storage1": { + "type": "storage.emulator", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/BackendFunctions/Shared/EmailValidator.cs b/BackendFunctions/Shared/EmailValidator.cs new file mode 100644 index 0000000..90eb072 --- /dev/null +++ b/BackendFunctions/Shared/EmailValidator.cs @@ -0,0 +1,20 @@ +using PersonalWebsite.Shared.Messaging; + +namespace PersonalWebsite.BackendFunctions.Shared +{ + public interface ICommunicationValidator + { + IEnumerable Validate(ContactRequest request); + } + + public class EmailValidator : ICommunicationValidator + { + public IEnumerable Validate(ContactRequest request) + { + if (string.IsNullOrWhiteSpace(request.From)) + { + yield return "No from address supplied in request"; + } + } + } +} diff --git a/BackendFunctions/host.json b/BackendFunctions/host.json new file mode 100644 index 0000000..beb2e40 --- /dev/null +++ b/BackendFunctions/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + } +} \ No newline at end of file diff --git a/Client/Model/Program.cs b/Client/Model/Program.cs index 03910ee..749b255 100644 --- a/Client/Model/Program.cs +++ b/Client/Model/Program.cs @@ -1,18 +1,28 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using PersonalWebsite.Client; -using PersonalWebsite.Shared; using MudBlazor.Services; +using PersonalWebsite.Client; +using PersonalWebsite.Shared.Contact; +using PersonalWebsite.Shared.CV; var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.Services.AddLogging(builder => builder - .SetMinimumLevel(LogLevel.Debug)); + +builder.Logging.AddConfiguration( + builder.Configuration.GetSection("Logging")); builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); + builder.Services.AddMudServices(); -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -// TODO: Replace this with the proper json service -builder.Services.AddScoped(); +builder.Services.AddHttpClient("local", + (h) => h.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); +builder.Services.AddHttpClient("api", + (h) => h.BaseAddress = new Uri("http://localhost:7071")); + +// TODO: Replace this with a proper http rest service +builder.Services.AddHttpClient("local"); + +builder.Services.AddOptions().Bind(builder.Configuration.GetSection(nameof(HttpContactOptions))).ValidateDataAnnotations(); +builder.Services.AddHttpClient("api"); await builder.Build().RunAsync(); diff --git a/Client/Pages/Blog.razor b/Client/Pages/Blog.razor index cf94409..30b2f2c 100644 --- a/Client/Pages/Blog.razor +++ b/Client/Pages/Blog.razor @@ -1,4 +1,5 @@ @page "/blog" +@using PersonalWebsite.Client.Shared.Socials TheToby - Blog diff --git a/Client/Pages/Contact.razor b/Client/Pages/Contact.razor new file mode 100644 index 0000000..9203a3a --- /dev/null +++ b/Client/Pages/Contact.razor @@ -0,0 +1,56 @@ +@page "/contact" +@using System.ComponentModel.DataAnnotations +@using PersonalWebsite.Client.Shared.Socials +@using PersonalWebsite.Shared.Contact +@using PersonalWebsite.Shared.Messaging +@inject ISnackbar Snackbar +@inject IContactService ContactService +@inject HttpClient httpClient + +TheToby - Contact + + + + + Contact + Please use this form to send me an email or interact with me on any of my socials. + + + + + + +
+ Send Email +
+
+
+
+ + + +
+
+ + +@code{ + bool isFormValid; + string[] errors = { }; + MudForm? form; + ContactRequest message = new ContactRequest(); + + protected async Task SendCommunication() + { + if (!isFormValid) + { + Snackbar.Add($"The form is not valid: {String.Join(", ", errors)}", Severity.Error); + return; + } + Console.WriteLine(message.From); + + var result = await ContactService.SendAsync(message); + var severity = (((int)result.ResponseCode >= 200) && ((int)result.ResponseCode <= 299)) ? Severity.Success : Severity.Error; + Snackbar.Add(result.Response, severity); + } +} \ No newline at end of file diff --git a/Client/Pages/Index.razor b/Client/Pages/Index.razor index f557165..a1299eb 100644 --- a/Client/Pages/Index.razor +++ b/Client/Pages/Index.razor @@ -1,5 +1,7 @@ @page "/" -@using PersonalWebsite.Shared +@using PersonalWebsite.Shared.CV +@using PersonalWebsite.Client.Shared.CV +@using PersonalWebsite.Client.Shared.Socials @inject ICVService CVService @inject ILogger IndexLogger @inject IJSRuntime js diff --git a/Client/Pages/Projects.razor b/Client/Pages/Projects.razor index 75fa12d..add9b24 100644 --- a/Client/Pages/Projects.razor +++ b/Client/Pages/Projects.razor @@ -1,4 +1,5 @@ @page "/projects" +@using PersonalWebsite.Client.Shared.Socials TheToby - Projects diff --git a/Client/PersonalWebsite.Client.csproj b/Client/PersonalWebsite.Client.csproj index d312385..cd70cd8 100644 --- a/Client/PersonalWebsite.Client.csproj +++ b/Client/PersonalWebsite.Client.csproj @@ -1,16 +1,20 @@  - net7.0 + net8.0 + true enable enable - - + + + + + - + @@ -18,6 +22,9 @@ + + PreserveNewest + Always diff --git a/Client/Shared/CVServiceExtensions.cs b/Client/Shared/CV/CVServiceExtensions.cs similarity index 63% rename from Client/Shared/CVServiceExtensions.cs rename to Client/Shared/CV/CVServiceExtensions.cs index babcde3..d0573d3 100644 --- a/Client/Shared/CVServiceExtensions.cs +++ b/Client/Shared/CV/CVServiceExtensions.cs @@ -1,20 +1,21 @@ -using Microsoft.JSInterop; -using PersonalWebsite.Shared; -using System.Text.Json; +using System.Text.Json; +using Microsoft.JSInterop; -namespace PersonalWebsite.Client.Shared +namespace PersonalWebsite.Shared.CV { public static class CVServiceExtensions { + private static readonly JsonSerializerOptions _jsonSerializerOptions = new() + { + PropertyNameCaseInsensitive = true, + WriteIndented = true, + }; + public static async Task SaveCVAsync(this BaseCVService thisCVService, IJSRuntime js, CVSection cv) { try { - var result = JsonSerializer.SerializeToUtf8Bytes(cv, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - WriteIndented = true, - }); + var result = JsonSerializer.SerializeToUtf8Bytes(cv, _jsonSerializerOptions); if (result == null) { thisCVService.Logger.LogError("Error saving CV data: Result of JsonSerializer.SerializeToUtf8Bytes was null"); @@ -29,7 +30,7 @@ await js.InvokeAsync( catch (Exception ex) { // Log the error - thisCVService.Logger.LogError(thisCVService.LoggerString, "Error saving CV data: " + ex.Message); + thisCVService.Logger.LogError("{LoggerString}:{}", thisCVService.LoggerString, $"Error saving CV data: {ex.Message}"); } } } diff --git a/Client/Shared/CVView.razor b/Client/Shared/CV/CVView.razor similarity index 97% rename from Client/Shared/CVView.razor rename to Client/Shared/CV/CVView.razor index 4ed17c1..f6f3d5f 100644 --- a/Client/Shared/CVView.razor +++ b/Client/Shared/CV/CVView.razor @@ -1,4 +1,4 @@ -@using PersonalWebsite.Shared +@using PersonalWebsite.Shared.CV @if (CVSection != null) { diff --git a/Client/Shared/CV_temp.razor b/Client/Shared/CV/CV_temp.razor similarity index 100% rename from Client/Shared/CV_temp.razor rename to Client/Shared/CV/CV_temp.razor diff --git a/Client/Shared/Icons_Extensions.cs b/Client/Shared/Icons_Extensions.cs index 45ef08b..9c0af44 100644 --- a/Client/Shared/Icons_Extensions.cs +++ b/Client/Shared/Icons_Extensions.cs @@ -1,11 +1,8 @@ -using MudBlazor; -using System.IO; - -namespace PersonalWebsite.Client.Shared +namespace PersonalWebsite.Client.Shared { public class Custom_Icons { - public const string Bookwyrm = " \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n "; + public const string Bookwyrm = "\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n "; public const string Mastodon = ""; public const string Pixelfed = ""; public const string TheToby = ""; diff --git a/Client/Shared/Socials.razor b/Client/Shared/Socials.razor deleted file mode 100644 index 206128c..0000000 --- a/Client/Shared/Socials.razor +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - -@code { - [Parameter] - public Color IconColor { get; set; } = Color.Inherit; -} \ No newline at end of file diff --git a/Client/Shared/MudDrawerWithSocials.razor b/Client/Shared/Socials/MudDrawerWithSocials.razor similarity index 77% rename from Client/Shared/MudDrawerWithSocials.razor rename to Client/Shared/Socials/MudDrawerWithSocials.razor index ea84ae9..d6083d4 100644 --- a/Client/Shared/MudDrawerWithSocials.razor +++ b/Client/Shared/Socials/MudDrawerWithSocials.razor @@ -1,9 +1,9 @@ - + @ChildContent - + - + @code { diff --git a/Client/Shared/Socials/Socials.razor b/Client/Shared/Socials/Socials.razor new file mode 100644 index 0000000..c70db62 --- /dev/null +++ b/Client/Shared/Socials/Socials.razor @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + +@code { + [Parameter] + public float IconSize { get; set; } = 1.5f; + [Parameter] + public Color IconColor { get; set; } = Color.Inherit; + [Parameter] + public string IconGap { get; set; } = "0px"; +} \ No newline at end of file diff --git a/Client/wwwroot/appsettings.json b/Client/wwwroot/appsettings.json new file mode 100644 index 0000000..08bdfaa --- /dev/null +++ b/Client/wwwroot/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "HttpContactOptions": { + "RequestUri": "api/sendcommunication" + } +} \ No newline at end of file diff --git a/PersonalWebsite.sln b/PersonalWebsite.sln index c13604c..7540237 100644 --- a/PersonalWebsite.sln +++ b/PersonalWebsite.sln @@ -1,13 +1,12 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.7.34024.191 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersonalWebsite.Server", "Server\PersonalWebsite.Server.csproj", "{DC277516-F66F-498E-B62C-ED6333D6ACE5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PersonalWebsite.Client", "Client\PersonalWebsite.Client.csproj", "{5CFCFA19-AB36-442E-89ED-B0A52A0E7B08}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersonalWebsite.Client", "Client\PersonalWebsite.Client.csproj", "{5CFCFA19-AB36-442E-89ED-B0A52A0E7B08}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PersonalWebsite.Shared", "Shared\PersonalWebsite.Shared.csproj", "{8F4AE034-8D32-4608-A15F-561A0524FDF8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersonalWebsite.Shared", "Shared\PersonalWebsite.Shared.csproj", "{8F4AE034-8D32-4608-A15F-561A0524FDF8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersonalWebsite.BackendFunctions", "BackendFunctions\PersonalWebsite.BackendFunctions.csproj", "{D8C8725C-3847-4854-9969-8CE5CD8FA25A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,10 +14,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DC277516-F66F-498E-B62C-ED6333D6ACE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC277516-F66F-498E-B62C-ED6333D6ACE5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC277516-F66F-498E-B62C-ED6333D6ACE5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC277516-F66F-498E-B62C-ED6333D6ACE5}.Release|Any CPU.Build.0 = Release|Any CPU {5CFCFA19-AB36-442E-89ED-B0A52A0E7B08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5CFCFA19-AB36-442E-89ED-B0A52A0E7B08}.Debug|Any CPU.Build.0 = Debug|Any CPU {5CFCFA19-AB36-442E-89ED-B0A52A0E7B08}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -27,6 +22,10 @@ Global {8F4AE034-8D32-4608-A15F-561A0524FDF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F4AE034-8D32-4608-A15F-561A0524FDF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F4AE034-8D32-4608-A15F-561A0524FDF8}.Release|Any CPU.Build.0 = Release|Any CPU + {D8C8725C-3847-4854-9969-8CE5CD8FA25A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8C8725C-3847-4854-9969-8CE5CD8FA25A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8C8725C-3847-4854-9969-8CE5CD8FA25A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8C8725C-3847-4854-9969-8CE5CD8FA25A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Server/Pages/Error.cshtml b/Server/Pages/Error.cshtml deleted file mode 100644 index 12cd1d3..0000000 --- a/Server/Pages/Error.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@page -@model PersonalWebsite.Server.Pages.ErrorModel - - - - - - - - Error - - - - - -
-
-

Error.

-

An error occurred while processing your request.

- - @if (Model.ShowRequestId) - { -

- Request ID: @Model.RequestId -

- } - -

Development Mode

-

- Swapping to the Development environment displays detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

-
-
- - - diff --git a/Server/Pages/Error.cshtml.cs b/Server/Pages/Error.cshtml.cs deleted file mode 100644 index b786f72..0000000 --- a/Server/Pages/Error.cshtml.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using System.Diagnostics; - -namespace PersonalWebsite.Server.Pages -{ - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - [IgnoreAntiforgeryToken] - public class ErrorModel : PageModel - { - public string? RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } - } -} \ No newline at end of file diff --git a/Server/PersonalWebsite.Server.csproj b/Server/PersonalWebsite.Server.csproj deleted file mode 100644 index 4587341..0000000 --- a/Server/PersonalWebsite.Server.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net7.0 - enable - enable - - - - - - - - - - - - - - - - - diff --git a/Server/Program.cs b/Server/Program.cs deleted file mode 100644 index 9808111..0000000 --- a/Server/Program.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.AspNetCore.ResponseCompression; - -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. - -builder.Services.AddControllersWithViews(); -builder.Services.AddRazorPages(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseWebAssemblyDebugging(); -} -else -{ - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); -} - -app.UseHttpsRedirection(); - -app.UseBlazorFrameworkFiles(); -app.UseStaticFiles(); - -app.UseRouting(); - - -app.MapRazorPages(); -app.MapControllers(); -app.MapFallbackToFile("index.html"); - -app.Run(); diff --git a/Server/Properties/launchSettings.json b/Server/Properties/launchSettings.json deleted file mode 100644 index d03286a..0000000 --- a/Server/Properties/launchSettings.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:27396", - "sslPort": 44301 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5224", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:7242;http://localhost:5224", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } - } diff --git a/Server/appsettings.Development.json b/Server/appsettings.Development.json deleted file mode 100644 index 0c208ae..0000000 --- a/Server/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/Server/appsettings.json b/Server/appsettings.json deleted file mode 100644 index 10f68b8..0000000 --- a/Server/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/Shared/CVSection.cs b/Shared/CV/CVSection.cs similarity index 73% rename from Shared/CVSection.cs rename to Shared/CV/CVSection.cs index 5b461d4..380b331 100644 --- a/Shared/CVSection.cs +++ b/Shared/CV/CVSection.cs @@ -1,9 +1,6 @@ -using Microsoft.AspNetCore.Components; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; +using System.Collections.Generic; -namespace PersonalWebsite.Shared +namespace PersonalWebsite.Shared.CV { /// /// Represents a section in a CV which contains entries diff --git a/Shared/CVService.cs b/Shared/CV/CVService.cs similarity index 75% rename from Shared/CVService.cs rename to Shared/CV/CVService.cs index 2becdad..6ee77cb 100644 --- a/Shared/CVService.cs +++ b/Shared/CV/CVService.cs @@ -1,7 +1,11 @@ -using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Net.Http; using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; -namespace PersonalWebsite.Shared +namespace PersonalWebsite.Shared.CV { public interface ICVService { @@ -11,10 +15,10 @@ public interface ICVService public abstract class BaseCVService : ICVService { // ToDo: This should be protected and the extension service in client should log some other way - public ILogger Logger { get; set; } + public ILogger Logger { get; set; } public readonly string LoggerString = "ICVService: "; - protected BaseCVService(ILogger logger) + protected BaseCVService(ILogger logger) { Logger = logger; } @@ -25,8 +29,12 @@ protected BaseCVService(ILogger logger) public sealed class JsonCVService : BaseCVService { private HttpClient HttpClient { get; set; } + private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; - public JsonCVService(HttpClient httpClient, ILogger logger) : base(logger) + public JsonCVService(HttpClient httpClient, ILogger logger) : base(logger) { HttpClient = httpClient; } @@ -38,14 +46,12 @@ public override async Task GetCVAsync() var response = await HttpClient.GetAsync($"staticData/{typeof(CVSection).Name}.json"); response.EnsureSuccessStatusCode(); var data = await response.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize(data, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); + + CVSection? result = JsonSerializer.Deserialize(data, _jsonSerializerOptions); if (result == null) { // Log the error - Logger.LogError(LoggerString, "Error fetching CV data: Result of JsonSerializer.Deserialize was null"); + Logger.LogError("{LoggerString}:{}", LoggerString, "Error fetching CV data: Result of JsonSerializer.Deserialize was null"); return new CVSection(); } return result; @@ -53,18 +59,16 @@ public override async Task GetCVAsync() catch (Exception ex) { // Log the error - Logger.LogError(LoggerString, "Error fetching CV data: " + ex.Message); + Logger.LogError("{LoggerString}:{}", LoggerString, $"Error fetching CV data: {ex.Message}"); // Return a default CV object or throw an exception, depending on your requirements return new CVSection(); } } } - public sealed class FixedCVService : BaseCVService + public sealed class StubCVService : BaseCVService { - public FixedCVService(ILogger logger) : base(logger) - { - } + public StubCVService(ILogger logger) : base(logger) { } public override async Task GetCVAsync() { @@ -100,6 +104,6 @@ public override async Task GetCVAsync() cv.SubSections.Add(cvSection); } return await Task.FromResult(cv); - } + } } } \ No newline at end of file diff --git a/Shared/Contact/ContactService.cs b/Shared/Contact/ContactService.cs new file mode 100644 index 0000000..8707275 --- /dev/null +++ b/Shared/Contact/ContactService.cs @@ -0,0 +1,80 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using PersonalWebsite.Shared.Messaging; + +namespace PersonalWebsite.Shared.Contact +{ + public interface IContactService + { + Task SendAsync(ContactRequest message); + } + + public sealed class HttpContactOptions + { + [Required] + public string? RequestUri { get; set; } + } + + public sealed class HttpContactService : IContactService + { + private readonly ILogger Logger; + private readonly HttpClient HttpClient; + private readonly HttpContactOptions ContactOptions; + + public HttpContactService(HttpClient httpClient, ILogger logger, IOptions contactOptions) + { + ContactOptions = contactOptions.Value; + HttpClient = httpClient; + Logger = logger; + if (string.IsNullOrWhiteSpace(ContactOptions.RequestUri)) + { + throw new ArgumentException("A valid request uri must be supplied.", nameof(contactOptions)); + } + } + + private readonly string LoggerString = $"{nameof(HttpContactService)}: "; + + public async Task SendAsync(ContactRequest message) + { + var response = new ContactResponse(); + + try + { + Logger.LogInformation("{LoggerString}{}", LoggerString, $"Sending request to: {ContactOptions?.RequestUri}"); + var result = await HttpClient.PostAsJsonAsync(ContactOptions.RequestUri, message); + response.Response = await result.Content.ReadAsStringAsync(); + response.ResponseCode = result.StatusCode; + } + catch (Exception ex) + { + // Log the error + Logger.LogError("{LoggerString}{}", LoggerString, $"Error sending communication: {ex.Message}"); + response.ResponseCode = 0; + response.Response = ex.Message; + } + + return response; + } + } + + public sealed class StubContactService(ILogger logger) : IContactService + { + private ILogger Logger = logger; + + private readonly string LoggerString = $"{nameof(StubContactService)}: "; + + public async Task SendAsync(ContactRequest message) + { + var result = new ContactResponse + { + Response = "Success!" + }; + return await Task.FromResult(result); + } + } +} diff --git a/Shared/Contact/EmailSender.cs b/Shared/Contact/EmailSender.cs new file mode 100644 index 0000000..46ed29f --- /dev/null +++ b/Shared/Contact/EmailSender.cs @@ -0,0 +1,77 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Net.Mail; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using PersonalWebsite.Shared.Messaging; + +namespace PersonalWebsite.Shared.Contact +{ + public interface ICommunicationSender + { + Task SendAsync(ContactRequest request); + } + + public class EmailSenderOptions + { + [Required] + [EmailAddress(ErrorMessage = "Invalid Email Address")] + public string? ToAddress { get; set; } + public string? NoReplyAddress { get; set; } + } + + public class EmailSender : ICommunicationSender + { + private readonly SmtpClient _smtpClient; + private readonly MailAddress _toAddress; + private readonly MailAddress _noReplyAddress; + + public EmailSender(SmtpClient smtpClient, IOptions options) + { + _smtpClient = smtpClient; + _toAddress = new MailAddress(options.Value.ToAddress ?? ""); + _noReplyAddress = string.IsNullOrEmpty(options.Value.NoReplyAddress) ? + _toAddress : + new MailAddress(options.Value.NoReplyAddress); + } + + public async Task SendAsync(ContactRequest request) + { + var emailValidator = new EmailAddressAttribute(); + if (!emailValidator.IsValid(request.From)) + throw new ArgumentException("Email Address Invalid", nameof(request)); + + MailAddress fromAddress; + try + { + fromAddress = new MailAddress(request.From ?? string.Empty); + } + catch (Exception e) + { + throw new ArgumentException("Email Address Invalid", nameof(request), e); + } + + var content = $"You have received a message from: {request.From}.\n\n\"{request.Content}\""; + using MailMessage messageTo = new(_noReplyAddress, _toAddress) + { + Subject = request.Subject, + SubjectEncoding = System.Text.Encoding.UTF8, + Body = content, + BodyEncoding = System.Text.Encoding.UTF8 + }; + messageTo.ReplyToList.Add(fromAddress); + await _smtpClient.SendMailAsync(messageTo); + + var confirmationContent = $"This message is to confirm that an email has been sent to " + + $"{_toAddress.User} with the following content:\n\n{request.Content}"; + using MailMessage messageConfirmation = new(_noReplyAddress, fromAddress) + { + Subject = request.Subject, + SubjectEncoding = System.Text.Encoding.UTF8, + Body = confirmationContent, + BodyEncoding = System.Text.Encoding.UTF8 + }; + await _smtpClient.SendMailAsync(messageConfirmation); + } + } +} diff --git a/Shared/Contact/Messaging.cs b/Shared/Contact/Messaging.cs new file mode 100644 index 0000000..bd93aea --- /dev/null +++ b/Shared/Contact/Messaging.cs @@ -0,0 +1,18 @@ +using System.Net; + +namespace PersonalWebsite.Shared.Messaging +{ + public class ContactRequest + { + public string? From { get; set; } + public string? Subject { get; set; } + public string? Content { get; set; } + } + + public class ContactResponse + { + // Useful to use this enum but also kind of odd + public HttpStatusCode ResponseCode { get; set; } + public string? Response { get; set; } + } +} \ No newline at end of file diff --git a/Shared/PersonalWebsite.Shared.csproj b/Shared/PersonalWebsite.Shared.csproj index 7ba01e4..4221f2f 100644 --- a/Shared/PersonalWebsite.Shared.csproj +++ b/Shared/PersonalWebsite.Shared.csproj @@ -1,16 +1,14 @@ - + - net7.0 + net8.0 enable - enable - - - - - + + + + From 5a9a1c949c2e7daba67d05d6f307417ae5629e52 Mon Sep 17 00:00:00 2001 From: Toby Burns Date: Tue, 27 Aug 2024 19:22:36 -0400 Subject: [PATCH 2/7] The contact button --- Client/Shared/MainLayout.razor | 171 +++++++++++++++++---------------- 1 file changed, 87 insertions(+), 84 deletions(-) diff --git a/Client/Shared/MainLayout.razor b/Client/Shared/MainLayout.razor index 1f0264d..9ad2966 100644 --- a/Client/Shared/MainLayout.razor +++ b/Client/Shared/MainLayout.razor @@ -1,85 +1,88 @@ -@inherits LayoutComponentBase -@using MudBlazor.Services -@implements IBrowserViewportObserver -@implements IAsyncDisposable - - - - - - - - - - @* - Blog - *@ - - - - - - This website is under construction! - I'll be slowly adding new features as I learn Blazor. The aim of this is just to have a space in which to post blogs and host my projects. - - - @Body - - - - -@code { - public bool DrawerOpen { get; set; } = true; - - void DrawerToggle() - { - DrawerOpen = !DrawerOpen; - } - - [Inject] - IBrowserViewportService BrowserViewportService { get; set; } = default!; - - Guid IBrowserViewportObserver.Id { get; } = Guid.NewGuid(); - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - await BrowserViewportService.SubscribeAsync(this, fireImmediately: true); - } - - await base.OnAfterRenderAsync(firstRender); - } - - public async ValueTask DisposeAsync() => await BrowserViewportService.UnsubscribeAsync(this); - - ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new() - { - ReportRate = 50, - NotifyOnBreakpointOnly = true - }; - - Task IBrowserViewportObserver.NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs) - { - var breakpoint = browserViewportEventArgs.Breakpoint; - if (breakpoint == Breakpoint.Xs || breakpoint == Breakpoint.Sm) - { - DrawerOpen = false; - } - else - { - DrawerOpen = true; - } - - return InvokeAsync(StateHasChanged); - } +@inherits LayoutComponentBase +@using MudBlazor.Services +@implements IBrowserViewportObserver +@implements IAsyncDisposable + + + + + + + + + + @* + Blog + *@ + + + + + + This website is under construction! + I'll be slowly adding new features as I learn Blazor. The aim of this is just to have a space in which to post blogs and host my projects. + + + @Body + + + + +@code { + public bool DrawerOpen { get; set; } = true; + + void DrawerToggle() + { + DrawerOpen = !DrawerOpen; + } + + [Inject] + IBrowserViewportService BrowserViewportService { get; set; } = default!; + + Guid IBrowserViewportObserver.Id { get; } = Guid.NewGuid(); + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await BrowserViewportService.SubscribeAsync(this, fireImmediately: true); + } + + await base.OnAfterRenderAsync(firstRender); + } + + public async ValueTask DisposeAsync() => await BrowserViewportService.UnsubscribeAsync(this); + + ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new() + { + ReportRate = 50, + NotifyOnBreakpointOnly = true + }; + + Task IBrowserViewportObserver.NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs) + { + var breakpoint = browserViewportEventArgs.Breakpoint; + if (breakpoint == Breakpoint.Xs || breakpoint == Breakpoint.Sm) + { + DrawerOpen = false; + } + else + { + DrawerOpen = true; + } + + return InvokeAsync(StateHasChanged); + } } \ No newline at end of file From ab8cbc279abd70c122d3126cbf102cfb243dfaf6 Mon Sep 17 00:00:00 2001 From: Toby Burns Date: Tue, 27 Aug 2024 21:05:52 -0400 Subject: [PATCH 3/7] Version update and fixes to mudblazor --- .../PersonalWebsite.BackendFunctions.csproj | 14 +++++------ Client/Pages/Index.razor | 23 +++++++++++++++---- Client/PersonalWebsite.Client.csproj | 6 ++--- Client/Shared/CV/CV_temp.razor | 2 +- .../Shared/Socials/MudDrawerWithSocials.razor | 2 +- Shared/PersonalWebsite.Shared.csproj | 4 ++-- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/BackendFunctions/PersonalWebsite.BackendFunctions.csproj b/BackendFunctions/PersonalWebsite.BackendFunctions.csproj index 7df61ba..8e9d79f 100644 --- a/BackendFunctions/PersonalWebsite.BackendFunctions.csproj +++ b/BackendFunctions/PersonalWebsite.BackendFunctions.csproj @@ -10,13 +10,13 @@ - - - - - - - + + + + + + + diff --git a/Client/Pages/Index.razor b/Client/Pages/Index.razor index a1299eb..9d74372 100644 --- a/Client/Pages/Index.razor +++ b/Client/Pages/Index.razor @@ -11,9 +11,9 @@ Home - + - + @@ -38,7 +38,7 @@ -@code { +@code { private CVSection? CV; private CVSection? _selectedValue; @@ -50,11 +50,26 @@ } } private bool DetailedView { get { return SelectedValue != CV; } } + private TreeItemData TreeItems { get; set; } = new(); protected override async Task OnInitializedAsync() { - CV = await CVService.GetCVAsync(); + CV = await CVService.GetCVAsync(); + TreeItems = ConvertToTreeItemData(CV); + + SelectedValue = CV; + } + + private TreeItemData ConvertToTreeItemData(CVSection item) + { + var children = item.SubSections.Select(x => ConvertToTreeItemData(x)).ToList(); + var root = new TreeItemData + { + Value = item, + Children = children + }; + return root; } private void OnHomeButtonClick() diff --git a/Client/PersonalWebsite.Client.csproj b/Client/PersonalWebsite.Client.csproj index cd70cd8..4da1133 100644 --- a/Client/PersonalWebsite.Client.csproj +++ b/Client/PersonalWebsite.Client.csproj @@ -8,13 +8,13 @@ - - + + - + diff --git a/Client/Shared/CV/CV_temp.razor b/Client/Shared/CV/CV_temp.razor index be8925d..2d9b097 100644 --- a/Client/Shared/CV/CV_temp.razor +++ b/Client/Shared/CV/CV_temp.razor @@ -1,4 +1,4 @@ - + diff --git a/Client/Shared/Socials/MudDrawerWithSocials.razor b/Client/Shared/Socials/MudDrawerWithSocials.razor index d6083d4..5777b8e 100644 --- a/Client/Shared/Socials/MudDrawerWithSocials.razor +++ b/Client/Shared/Socials/MudDrawerWithSocials.razor @@ -1,4 +1,4 @@ - + @ChildContent diff --git a/Shared/PersonalWebsite.Shared.csproj b/Shared/PersonalWebsite.Shared.csproj index 4221f2f..45d3f92 100644 --- a/Shared/PersonalWebsite.Shared.csproj +++ b/Shared/PersonalWebsite.Shared.csproj @@ -6,8 +6,8 @@ - - + + From c2aac6c118725bc38da0a8b0005f2ad734258997 Mon Sep 17 00:00:00 2001 From: Toby Burns Date: Thu, 29 Aug 2024 16:47:03 -0400 Subject: [PATCH 4/7] Tidy up workflows and add dependabot --- .github/actions/build/action.yml | 34 -------------- .github/dependabot.yml | 7 +++ ...tic-web-apps-victorious-wave-0b0164910.yml | 46 ------------------- ...-static-web-apps-white-field-080421c10.yml | 46 ------------------- .github/workflows/deploy_azure.yml | 40 ---------------- 5 files changed, 7 insertions(+), 166 deletions(-) delete mode 100644 .github/actions/build/action.yml create mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/azure-static-web-apps-victorious-wave-0b0164910.yml delete mode 100644 .github/workflows/azure-static-web-apps-white-field-080421c10.yml delete mode 100644 .github/workflows/deploy_azure.yml diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml deleted file mode 100644 index 9fd26f8..0000000 --- a/.github/actions/build/action.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: build - -runs: - using: "composite" - steps: - - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 7.0.x - - - name: Build with dotnet - shell: bash - run: dotnet build --configuration Release - - - name: dotnet publish - shell: bash - run: dotnet publish -c Release --property:PublishDir='${{github.workspace}}/release' - - # ToDo: Custom domains in azure cost money - # copy the CNAME file into the release folder to allow a custom domain - # - name: copy CNAME to CNAME - # shell: bash - # run: cp CNAME '${{github.workspace}}/release/wwwroot/CNAME' - - # copy index.html to 404.html to serve the same file when a file is not found - - name: copy index.html to 404.html - shell: bash - run: cp '${{github.workspace}}/release/wwwroot/index.html' '${{github.workspace}}/release/wwwroot/404.html' - - - name: Upload artifact for deployment job - uses: actions/upload-artifact@v2 - with: - name: full-site - path: '${{github.workspace}}/release' \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..226d72e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/azure-static-web-apps-victorious-wave-0b0164910.yml b/.github/workflows/azure-static-web-apps-victorious-wave-0b0164910.yml deleted file mode 100644 index 40f0788..0000000 --- a/.github/workflows/azure-static-web-apps-victorious-wave-0b0164910.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Azure Static Web Apps CI/CD - -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened, closed] - branches: - - main - -jobs: - build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') - runs-on: ubuntu-latest - name: Build and Deploy Job - steps: - - uses: actions/checkout@v3 - with: - submodules: true - lfs: false - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_VICTORIOUS_WAVE_0B0164910 }} - repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) - action: "upload" - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: "Client" # App source code path - api_location: "BackendFunctions" # Api source code path - optional - output_location: "wwwroot" # Built app content directory - optional - ###### End of Repository/Build Configurations ###### - - close_pull_request_job: - if: github.event_name == 'pull_request' && github.event.action == 'closed' - runs-on: ubuntu-latest - name: Close Pull Request Job - steps: - - name: Close Pull Request - id: closepullrequest - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_VICTORIOUS_WAVE_0B0164910 }} - action: "close" diff --git a/.github/workflows/azure-static-web-apps-white-field-080421c10.yml b/.github/workflows/azure-static-web-apps-white-field-080421c10.yml deleted file mode 100644 index 03d2000..0000000 --- a/.github/workflows/azure-static-web-apps-white-field-080421c10.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Azure Static Web Apps CI/CD - -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened, closed] - branches: - - main - -jobs: - build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') - runs-on: ubuntu-latest - name: Build and Deploy Job - steps: - - uses: actions/checkout@v3 - with: - submodules: true - lfs: false - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_WHITE_FIELD_080421C10 }} - repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) - action: "upload" - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: "Client" # App source code path - api_location: "BackendFunctions" # Api source code path - optional - output_location: "wwwroot" # Built app content directory - optional - ###### End of Repository/Build Configurations ###### - - close_pull_request_job: - if: github.event_name == 'pull_request' && github.event.action == 'closed' - runs-on: ubuntu-latest - name: Close Pull Request Job - steps: - - name: Close Pull Request - id: closepullrequest - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_WHITE_FIELD_080421C10 }} - action: "close" diff --git a/.github/workflows/deploy_azure.yml b/.github/workflows/deploy_azure.yml deleted file mode 100644 index 61bf20c..0000000 --- a/.github/workflows/deploy_azure.yml +++ /dev/null @@ -1,40 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy ASP.Net Core app to Azure Web App - app-personalwebsite-prod-weu - -on: - push: - branches: [ main ] - -jobs: - build: - runs-on: windows-latest - steps: - # uses GitHub's checkout action to checkout code from the main branch - - name: Checkout - uses: actions/checkout@v3 - - name: Build - uses: ./.github/actions/build - - deploy: - runs-on: windows-latest - needs: build - environment: - name: 'production' - url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} - - steps: - - name: Download artifact from build job - uses: actions/download-artifact@v3 - with: - name: full-site - - - name: Deploy to Azure Web App - id: deploy-to-webapp - uses: azure/webapps-deploy@v2 - with: - app-name: 'app-personalwebsite-prod-weu' - slot-name: 'production' - publish-profile: ${{ secrets.AzureAppService_PublishProfile_0948ce7c46294ab4a15dfed71126407a }} - package: . \ No newline at end of file From 73d2ce8e6e11387524d258440b8f0199f15840b6 Mon Sep 17 00:00:00 2001 From: Toby Burns Date: Thu, 29 Aug 2024 16:50:10 -0400 Subject: [PATCH 5/7] Remove build and test --- .github/workflows/build_and_test.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/workflows/build_and_test.yml diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml deleted file mode 100644 index e520405..0000000 --- a/.github/workflows/build_and_test.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: build_and_test - -on: - pull_request: - branches: [ main, dev ] - -jobs: - build: - # use ubuntu-latest image to run steps on - runs-on: ubuntu-latest - steps: - # uses GitHub's checkout action to checkout code from the main branch - - name: Checkout - uses: actions/checkout@v3 - - name: Build - uses: ./.github/actions/build - # test: - # needs: build - # runs-on: ubuntu-latest \ No newline at end of file From 9478cc48e145b50da269642941d6e345cbdeb6ee Mon Sep 17 00:00:00 2001 From: Toby Burns Date: Thu, 29 Aug 2024 17:09:03 -0400 Subject: [PATCH 6/7] Add api uri --- Client/Model/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/Model/Program.cs b/Client/Model/Program.cs index 749b255..e1c9370 100644 --- a/Client/Model/Program.cs +++ b/Client/Model/Program.cs @@ -17,7 +17,7 @@ builder.Services.AddHttpClient("local", (h) => h.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); builder.Services.AddHttpClient("api", - (h) => h.BaseAddress = new Uri("http://localhost:7071")); + (h) => h.BaseAddress = new Uri(builder.Configuration["Api_Uri"] ?? "http://localhost:7071")); // TODO: Replace this with a proper http rest service builder.Services.AddHttpClient("local"); From e063225129468e08de08b05f7abcfffeb4e93c61 Mon Sep 17 00:00:00 2001 From: Toby Burns Date: Thu, 29 Aug 2024 17:12:45 -0400 Subject: [PATCH 7/7] Add api uri --- Client/Model/Program.cs | 3 ++- Client/wwwroot/appsettings.development.json | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Client/wwwroot/appsettings.development.json diff --git a/Client/Model/Program.cs b/Client/Model/Program.cs index e1c9370..0a735b0 100644 --- a/Client/Model/Program.cs +++ b/Client/Model/Program.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; using MudBlazor.Services; using PersonalWebsite.Client; using PersonalWebsite.Shared.Contact; @@ -17,7 +18,7 @@ builder.Services.AddHttpClient("local", (h) => h.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); builder.Services.AddHttpClient("api", - (h) => h.BaseAddress = new Uri(builder.Configuration["Api_Uri"] ?? "http://localhost:7071")); + (h) => h.BaseAddress = new Uri(builder.Configuration["Api_Uri"] ?? builder.HostEnvironment.BaseAddress)); // TODO: Replace this with a proper http rest service builder.Services.AddHttpClient("local"); diff --git a/Client/wwwroot/appsettings.development.json b/Client/wwwroot/appsettings.development.json new file mode 100644 index 0000000..9d653ac --- /dev/null +++ b/Client/wwwroot/appsettings.development.json @@ -0,0 +1,3 @@ +{ + "Api_Uri": "http://localhost:7071" +} \ No newline at end of file