Skip to content

Commit bc5738e

Browse files
committed
Added feedback api integration to web client
1 parent 6ea7c93 commit bc5738e

File tree

17 files changed

+295
-28
lines changed

17 files changed

+295
-28
lines changed

infra/pulumi-infra-deploy/Program.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,18 @@
2222
var storageApiBuilder = new StorageApiStackBuilder(globalConfig);
2323
var storageApiInfra = storageApiBuilder.GenerateResources();
2424

25-
var websiteBuilder = new WebsiteStackBuilder(globalConfig, storageApiInfra);
26-
var websiteInfra = websiteBuilder.GenerateResources();
27-
28-
var routeFilterWorkerBuilder = new RouteFilterStackBuilder(globalConfig, storageApiInfra);
29-
var routeFilterWorkerInfra = routeFilterWorkerBuilder.GenerateResources();
30-
3125
var azureRgBuilder = new AzureResourceGroupStackBuilder(globalConfig);
3226
var azureRgInfra = azureRgBuilder.GenerateResources();
3327

3428
var feedbackFunctionsBuilder = new FeedbackApiStackBuilder(globalConfig, azureRgInfra, clientConfig);
3529
var feedbackFunctionsInfra = feedbackFunctionsBuilder.GenerateResources();
3630

31+
var websiteBuilder = new WebsiteStackBuilder(globalConfig, storageApiInfra, feedbackFunctionsInfra);
32+
var websiteInfra = websiteBuilder.GenerateResources();
33+
34+
var routeFilterWorkerBuilder = new RouteFilterStackBuilder(globalConfig, storageApiInfra);
35+
var routeFilterWorkerInfra = routeFilterWorkerBuilder.GenerateResources();
36+
3737
return GenerateOutputs(websiteInfra, storageApiInfra, globalConfig);
3838
});
3939

infra/pulumi-infra-deploy/StackBuilders/Website/WebsiteStackBuilder.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
using ProgrammerAl.Site.IaC.Utilities;
1717
using ProgrammerAl.Site.IaC.Config.GlobalConfigs;
1818
using ProgrammerAl.Site.IaC.StackBuilders.StorageApi;
19+
using ProgrammerAl.Site.IaC.StackBuilders.FeedbackApi;
1920

2021
public record WebsiteStackBuilder(GlobalConfig GlobalConfig,
21-
StorageApiInfrastructure StorageApiInfra)
22+
StorageApiInfrastructure StorageApiInfra,
23+
FeedbackApiInfrastructure FeedbackFunctionsInfra)
2224
{
2325
public WebsiteInfrastructure GenerateResources()
2426
{
@@ -98,15 +100,16 @@ private Output<string> PrepareAppFilesForUpload()
98100
{
99101
return Output.Tuple(
100102
StorageApiInfra.Domain.HttpsEndpoint,
101-
Output.Create("")//Just to make it a tuple so we can add things later with less code changes
103+
FeedbackFunctionsInfra.FeedbackApiFunctionsInfra.HttpsEndpoint
102104
)
103105
.Apply(x =>
104106
{
105107
var storageApiHttpsEndpoint = x.Item1;
108+
var feedbackApiHttpsEndpoint = x.Item2;
106109

107110
JsonNode appSettingsJson = JsonNode.Parse("{}")!;
108111

109-
AddApiConfigValues(appSettingsJson, storageApiHttpsEndpoint);
112+
AddApiConfigValues(appSettingsJson, storageApiHttpsEndpoint, feedbackApiHttpsEndpoint);
110113

111114
var contents = StringContentUtilities.GenerateCompressedStringContent(appSettingsJson);
112115

@@ -120,11 +123,12 @@ private Output<string> PrepareAppFilesForUpload()
120123
});
121124
}
122125

123-
private void AddApiConfigValues(JsonNode appSettingsJson, string httpsStorageApiEndpoint)
126+
private void AddApiConfigValues(JsonNode appSettingsJson, string httpsStorageApiEndpoint, string feedbackApiHttpsEndpoint)
124127
{
125128
var jsonObject = new JsonObject
126129
{
127130
["StorageApiBaseEndpoint"] = $"{httpsStorageApiEndpoint}",
131+
["FeedbackApiBaseEndpoint"] = $"{feedbackApiHttpsEndpoint}",
128132
["HttpTimeout"] = "00:10:00"
129133
};
130134

src/ProgrammerAl.Site/DynamicContentUpdater/Outputters/PostMetadataOutputter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ public void Output(RuntimeConfig runtimeConfig, ImmutableArray<PostEntry> allPos
2323
{
2424
var comicLink = post.TryGetComicSvgLink(out var outComicLink) ? outComicLink : null;
2525
var metadata = new PostMetadata(
26-
title: post.TitleHumanReadable,
27-
comicImageLink: comicLink);
26+
Title: post.TitleHumanReadable,
27+
ComicImageLink: comicLink,
28+
ReleaseDate: post.ReleaseDate);
2829

2930
var outputDir = $"{runtimeConfig.OutputDirectory}/Posts/{post.TitleLink}";
3031
OutputUtils.WriteOutFileAsJson(metadata, outputDir, PostMetadata.FileName);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
@using ProgrammerAl.Site.Components.AnimatedComponents
2+
<div class="flex flex-col gap-2 border p-4 m-8">
3+
<p class="font-semibold text-lg">If you have time, please provide anonymous feedback:</p>
4+
<textarea @bind=@Comments
5+
placeholder="Place feedback here..."
6+
rows="4"
7+
maxlength="2000"
8+
class="block p-2.5 w-full text-sm text-gray-900
9+
bg-gray-50 rounded-lg
10+
border border-2 border-gray-300
11+
focus:ring-blue-500 focus:border-blue-500
12+
dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500
13+
dark:focus:border-blue-500" />
14+
15+
@if (SubmitState == SubmitStateType.NotSubmitted)
16+
{
17+
<button @onclick=@PostCommentsAsync
18+
class="p-2 w-2/5 place-self-center
19+
border border-2 border-green-800 rounded
20+
font-bold text-xl">
21+
Submit Feedback
22+
</button>
23+
}
24+
else if (SubmitState == SubmitStateType.Submitting)
25+
{
26+
<div class="place-self-center">
27+
<GreenSpinnerComponent TailwindCssHeightClass="h-12" TailwindCssWidthClass="w-12" />
28+
</div>
29+
}
30+
else if (SubmitState == SubmitStateType.SubmitSuccessful)
31+
{
32+
<p class="font-semibold text-lg">Thank you for your feedback! Buy yourself a sticker from your marketplace of choice!</p>
33+
}
34+
else if (SubmitState == SubmitStateType.SubmitFailed)
35+
{
36+
<p class="font-semibold text-lg text-red-800">@ErrorResultMessage</p>
37+
}
38+
</div>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Microsoft.AspNetCore.Components;
2+
3+
using ProgrammerAl.Site.HttpClients.FeedbackApi;
4+
5+
namespace ProgrammerAl.Site.Components;
6+
public partial class PostFeedbackComponent : ComponentBase
7+
{
8+
[Inject, NotNull]
9+
private IFeedbackApiHttpClient? FeedbackHttpClient { get; set; }
10+
11+
[Parameter, EditorRequired]
12+
public string PostName { get; set; } = "";
13+
14+
private string Comments { get; set; } = "";
15+
private SubmitStateType SubmitState { get; set; } = SubmitStateType.NotSubmitted;
16+
private string ErrorResultMessage { get; set; } = "";
17+
18+
private async Task PostCommentsAsync()
19+
{
20+
if (string.IsNullOrWhiteSpace(PostName))
21+
{
22+
return;
23+
}
24+
25+
SubmitState = SubmitStateType.Submitting;
26+
await InvokeAsync(StateHasChanged);
27+
28+
var result = await FeedbackHttpClient.StoreCommentsAsync(postName: PostName, comments: Comments);
29+
if (result.IsValid)
30+
{
31+
SubmitState = SubmitStateType.SubmitSuccessful;
32+
}
33+
else
34+
{
35+
ErrorResultMessage = $"Error submitting feedback: {result.ResponseBody}";
36+
SubmitState = SubmitStateType.SubmitFailed;
37+
}
38+
39+
await InvokeAsync(StateHasChanged);
40+
}
41+
42+
public enum SubmitStateType
43+
{
44+
NotSubmitted,
45+
Submitting,
46+
SubmitSuccessful,
47+
SubmitFailed
48+
}
49+
}

src/ProgrammerAl.Site/ProgrammerAl.Site/Config/ApiConfig.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ public class ApiConfig
99
[Required(AllowEmptyStrings = false), NotNull]
1010
public string? StorageApiBaseEndpoint { get; set; }
1111

12+
[Required(AllowEmptyStrings = false), NotNull]
13+
public string? FeedbackApiBaseEndpoint { get; set; }
14+
1215
[Required, NotNull]
1316
public TimeSpan? HttpTimeout { get; set; }
1417
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Net.Http;
2+
3+
namespace ProgrammerAl.Site.HttpClients.FeedbackApi;
4+
5+
public interface IFeedbackApiHttpClient
6+
{
7+
ValueTask<HttpResponseResult> StoreCommentsAsync(string postName, string comments);
8+
}
9+
10+
public class FeedbackApiHttpClient : HttpClientBase, IFeedbackApiHttpClient
11+
{
12+
public FeedbackApiHttpClient(HttpClient client)
13+
: base(client)
14+
{
15+
}
16+
17+
public async ValueTask<HttpResponseResult> StoreCommentsAsync(string postName, string comments)
18+
{
19+
var request = new HttpRequestMessage(HttpMethod.Post, "store-comments");
20+
var requestDto = new StoreCommentsRequest(postName, comments);
21+
return await RunAsync(request, requestDto);
22+
}
23+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+

2+
namespace ProgrammerAl.Site.HttpClients.FeedbackApi;
3+
public record StoreCommentsRequest(string PostName, string Comments);
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.Net.Http;
3+
using System.Text.Json;
4+
5+
using PurpleSpikeProductions.ArcadeServices.Public.WebClient.HttpClients.Responses;
6+
7+
namespace ProgrammerAl.Site.HttpClients;
8+
9+
public abstract class HttpClientBase
10+
{
11+
private readonly HttpClient _client;
12+
13+
protected HttpClientBase(HttpClient client)
14+
{
15+
_client = client;
16+
}
17+
18+
protected async ValueTask<HttpResponseResult<TResponse>> RunAsync<TRequest, TResponseDto, TResponse>(HttpRequestMessage requestMessage, TRequest requestBody)
19+
where TResponse : class
20+
where TResponseDto : HttpResponseDtoBase<TResponse>
21+
{
22+
var requestJson = JsonSerializer.Serialize(requestBody);
23+
requestMessage.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
24+
25+
return await RunAsync<TResponseDto, TResponse>(requestMessage);
26+
}
27+
28+
protected async ValueTask<HttpResponseResult<TResponse>> RunAsync<TResponseDto, TResponse>(HttpRequestMessage requestMessage)
29+
where TResponse : class
30+
where TResponseDto : HttpResponseDtoBase<TResponse>
31+
{
32+
var response = await _client.SendAsync(requestMessage);
33+
34+
var bodyContent = await response.Content.ReadAsStringAsync();
35+
36+
if (!response.IsSuccessStatusCode)
37+
{
38+
return new HttpResponseResult<TResponse>(errorMessage: bodyContent, (int)response.StatusCode);
39+
}
40+
41+
var deserializedDto = JsonSerializer.Deserialize<TResponseDto>(bodyContent);
42+
43+
if (deserializedDto is null)
44+
{
45+
return new HttpResponseResult<TResponse>(errorMessage: bodyContent, (int)response.StatusCode);
46+
}
47+
48+
if (!deserializedDto.TryGenerateValidResponseObject(out TResponse? responseRecord))
49+
{
50+
return new HttpResponseResult<TResponse>(errorMessage: bodyContent, (int)response.StatusCode);
51+
}
52+
53+
return new HttpResponseResult<TResponse>(responseObject: responseRecord, (int)response.StatusCode);
54+
}
55+
56+
protected async ValueTask<HttpResponseResult> RunAsync<TRequest>(HttpRequestMessage requestMessage, TRequest requestBody)
57+
{
58+
var requestJson = JsonSerializer.Serialize(requestBody);
59+
requestMessage.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
60+
61+
var response = await _client.SendAsync(requestMessage);
62+
var bodyContent = await response.Content.ReadAsStringAsync();
63+
64+
return new HttpResponseResult(IsValid: response.IsSuccessStatusCode, ResponseBody: bodyContent, StatusCode: (int)response.StatusCode);
65+
}
66+
67+
protected async ValueTask<HttpResponseResult> RunAsync(HttpRequestMessage requestMessage)
68+
{
69+
var response = await _client.SendAsync(requestMessage);
70+
var bodyContent = await response.Content.ReadAsStringAsync();
71+
72+
return new HttpResponseResult(IsValid: response.IsSuccessStatusCode, ResponseBody: bodyContent, StatusCode: (int)response.StatusCode);
73+
}
74+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+

2+
using System.Diagnostics.CodeAnalysis;
3+
4+
namespace PurpleSpikeProductions.ArcadeServices.Public.WebClient.HttpClients.Responses;
5+
6+
public abstract class HttpResponseDtoBase<T> where T : class
7+
{
8+
public abstract bool TryGenerateValidResponseObject([NotNullWhen(true)] out T? outResponse);
9+
}

0 commit comments

Comments
 (0)