-
Notifications
You must be signed in to change notification settings - Fork 2
/
LambdaFunctions.cs
161 lines (125 loc) · 5.78 KB
/
LambdaFunctions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using Amazon.Lambda.Core;
using Amazon.Lambda.S3Events;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Util;
using Scriban;
using System.Text;
using System.Web;
using System.Text.Json;
using Amazon.Comprehend;
using Amazon.Comprehend.Model;
using Amazon.Lambda.APIGatewayEvents;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace ServerlessAwsWebsite;
public class LambdaFunctions
{
// AWS clients can just be instantiated with no arguments like this...
private readonly IAmazonS3 _s3Client = new AmazonS3Client();
// Read the environment variables into this object...
private readonly (
string HtmlBucketName,
string SongBucketName,
string PublicWebsiteUrl) envVars =
(
HtmlBucketName: Environment.GetEnvironmentVariable("HTML_BUCKET_NAME")!,
SongBucketName: Environment.GetEnvironmentVariable("SONG_BUCKET_NAME")!,
PublicWebsiteUrl: Environment.GetEnvironmentVariable("PUBLIC_WEBSITE_URL")!
);
/// <summary>
/// Reads the body of the lyrics file from this S3 key and saves to the state object
/// </summary>
public async Task<StepFunctionsState> ReadLyricsFromS3(StepFunctionsState state, ILambdaContext context)
{
context.Logger.LogInformation("Executing function...");
context.Logger.LogInformation(JsonSerializer.Serialize(state));
state.TriggeredS3Key = state.Detail?.Object?.Key ?? throw new ArgumentException("Missing S3 object key in state");
var objectKey = state.TriggeredS3Key.Replace("+", " ");
var songTitle = objectKey.Replace(".txt", "", StringComparison.InvariantCultureIgnoreCase);
// The event that triggered this lambda only contains the key, so go off and get the entire object...
var s3GetResponse = await _s3Client.GetObjectAsync(new GetObjectRequest
{
BucketName = envVars.SongBucketName,
Key = objectKey
});
// ...and read it as a string (these will be text files)
using var reader = new StreamReader(s3GetResponse.ResponseStream, Encoding.UTF8);
var objectContentString = reader.ReadToEnd();
// Save the title and contents to the state object
state.SongTitle = songTitle;
state.SongLyrics = objectContentString;
return state;
}
/// <summary>
/// Uses AWS Comprehend to detect the language this song was written in
/// </summary>
public async Task<StepFunctionsState> DetectSongLanguage(StepFunctionsState state, ILambdaContext context)
{
var comprehendClient = new AmazonComprehendClient();
var detectDominantLanguageResponse = await comprehendClient.DetectDominantLanguageAsync(new DetectDominantLanguageRequest
{
Text = state.SongLyrics
});
foreach (var dl in detectDominantLanguageResponse.Languages)
{
context.Logger.LogInformation($"Language Code: {dl.LanguageCode}, Score: {dl.Score}");
}
var dominantLanguageKey = detectDominantLanguageResponse.Languages.Select(x => x.LanguageCode).First();
state.LanguageKey = dominantLanguageKey;
return state;
}
/// <summary>
/// Renders using the English language html template
/// </summary>
public async Task<StepFunctionsState> RenderInEnglish(StepFunctionsState state, ILambdaContext context)
{
await RenderHtml("song.en.html", new SongHtmlViewModel(state.SongLyrics!, state.SongTitle!), context.Logger.LogInformation);
return state;
}
/// <summary>
/// Renders using the French language html template
/// </summary>
public async Task<StepFunctionsState> RenderInFrench(StepFunctionsState state, ILambdaContext context)
{
await RenderHtml("song.fr.html", new SongHtmlViewModel(state.SongLyrics!, state.SongTitle!), context.Logger.LogInformation);
return state;
}
public async Task<APIGatewayProxyResponse> SaveSongFromApi(APIGatewayProxyRequest @event, ILambdaContext context)
{
var lyrics = @event.Body;
var songName = HttpUtility.UrlDecode(@event.PathParameters["songname"]);
context.Logger.LogInformation("Song name from url is: " + songName);
// Validate request...
if (string.IsNullOrWhiteSpace(lyrics))
return new APIGatewayProxyResponse { StatusCode = 400, Body = "Song lyrics missing form HTTP body" };
// Save the body text to the songs bucket
await _s3Client.PutObjectAsync(new PutObjectRequest
{
BucketName = envVars.SongBucketName,
Key = songName,
ContentBody = lyrics
});
// Return an HTTP response
return new APIGatewayProxyResponse { StatusCode = 200 };
}
public record SongHtmlViewModel(string Lyrics, string Title);
public async Task RenderHtml(string templateFile, SongHtmlViewModel model, Action<string> log)
{
var template = Template.Parse(await File.ReadAllTextAsync(templateFile));
// Render our (static) html page for this song
var songPageHtml = template.Render(model);
// Save the rendered html page to the publicly facing html bucket
var htmlObjectKey = "Song/" + model.Title;
await _s3Client.PutObjectAsync(new PutObjectRequest
{
BucketName = envVars.HtmlBucketName,
Key = htmlObjectKey,
ContentBody = songPageHtml,
ContentType = "text/html"
});
log($"Rendered and saved html page for song {model.Title}");
log($"This page will be available at:");
log(Path.Join(envVars.PublicWebsiteUrl, HttpUtility.UrlPathEncode(htmlObjectKey)));
}
};