Repository: matfij/particle-simulator
Pull request #36: Add API Gateway
Author: login: matfij
Created: 2025-05-02T10:15:49Z | Merged: 2025-05-02T13:41:29Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced an API endpoint for file uploads, requiring an API key and supporting throttling limits.
	- File upload requests now return a pre-signed S3 URL and file key for secure, time-limited uploads.

- **Improvements**
	- Enhanced data models for upload requests and responses, providing clearer structure and validation.
	- File uploads are now tracked with unique IDs and metadata, improving organization and traceability.
	- Improved backend structure and permissions for upload handling and storage.

- **Bug Fixes**
	- Removed unused functions and redundant code in the backend.

- **Chores**
	- Updated backend dependencies to include the AWS S3 SDK and API Gateway packages.
	- Cleaned up project references by removing obsolete upload lambda project.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/backend/src/Backend.sln b/backend/src/Backend.sln
index 864e233..1874551 100644
--- a/backend/src/Backend.sln
+++ b/backend/src/Backend.sln
@@ -7,8 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend", "Backend\Backend.
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UploadLambda", "UploadLambda\UploadLambda.csproj", "{1C5B279A-AA15-4F3B-983C-460DB54B921B}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileUploadLambda", "FileUploadLambda\FileUploadLambda.csproj", "{E014B514-9202-4D43-8AD2-E807CE079E7A}"
-EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -43,18 +41,6 @@ Global
 		{1C5B279A-AA15-4F3B-983C-460DB54B921B}.Release|x64.Build.0 = Release|Any CPU
 		{1C5B279A-AA15-4F3B-983C-460DB54B921B}.Release|x86.ActiveCfg = Release|Any CPU
 		{1C5B279A-AA15-4F3B-983C-460DB54B921B}.Release|x86.Build.0 = Release|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Debug|x64.Build.0 = Debug|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Debug|x86.Build.0 = Debug|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Release|Any CPU.Build.0 = Release|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Release|x64.ActiveCfg = Release|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Release|x64.Build.0 = Release|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Release|x86.ActiveCfg = Release|Any CPU
-		{E014B514-9202-4D43-8AD2-E807CE079E7A}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/backend/src/Backend/BackendStack.cs b/backend/src/Backend/BackendStack.cs
index 0e0f0c8..bc63dce 100644
--- a/backend/src/Backend/BackendStack.cs
+++ b/backend/src/Backend/BackendStack.cs
@@ -1,4 +1,5 @@
 using Amazon.CDK;
+using Amazon.CDK.AWS.APIGateway;
 using Amazon.CDK.AWS.DynamoDB;
 using Amazon.CDK.AWS.Lambda;
 using Amazon.CDK.AWS.S3;
@@ -10,16 +11,18 @@ public class BackendStack : Stack
     {
         internal BackendStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
         {
-            var uploadLambdaProps = new FunctionProps
+            #region Upload Lambda
+            var uploadLambda = new Function(this, "upload-lambda", new FunctionProps
             {
                 Runtime = Runtime.DOTNET_8,
                 Handler = "UploadLambda",
                 Code = Code.FromAsset("./src/UploadLambda/bin/Release/net8.0/UploadLambda.zip"),
                 Timeout = Duration.Seconds(30)
-            };
-            var uploadLambda = new Function(this, "upload-lambda", uploadLambdaProps);
+            });
+            #endregion
 
-            var simulationTableProps = new TableProps
+            #region Simulation DynamoDB Table
+            var simulationTable = new Table(this, "simulation-table", new TableProps
             {
                 TableName = "Simulation",
                 PartitionKey = new Attribute
@@ -29,20 +32,12 @@ internal BackendStack(Construct scope, string id, IStackProps props = null) : ba
                 },
                 RemovalPolicy = RemovalPolicy.DESTROY, // RETAIN in production
                 BillingMode = BillingMode.PAY_PER_REQUEST,
-            };
-            var simulationTable = new Table(this, "simulation-table", simulationTableProps);
+            });
 
             simulationTable.GrantReadWriteData(uploadLambda);
+            #endregion
 
-            var fileUploadLambdaProps = new FunctionProps
-            {
-                Runtime = Runtime.DOTNET_8,
-                Handler = "FileUploadLambda",
-                Code = Code.FromAsset("./src/FileUploadLambda/bin/Release/net8.0/FileUploadLambda.zip"),
-                Timeout = Duration.Seconds(30)
-            };
-            var fileUploadLambda = new Function(this, "file-upload-lambda", fileUploadLambdaProps);
-
+            #region Simulation S3 Bucket
             var simulationBucketProps = new BucketProps
             {
                 BucketName="particle-simulation-bucket",
@@ -53,7 +48,38 @@ internal BackendStack(Construct scope, string id, IStackProps props = null) : ba
             };
             var simulationBucket = new Bucket(this, "particle-simulation-bucket", simulationBucketProps);
 
-            simulationBucket.GrantWrite(fileUploadLambda);
+            simulationBucket.GrantWrite(uploadLambda);
+            #endregion
+
+            #region API Gateway
+            var api = new RestApi(this, "simulation-api", new RestApiProps
+            {
+                RestApiName = "Particle Simulator Gateway",
+                ApiKeySourceType = ApiKeySourceType.HEADER,
+            });
+
+            var plan = api.AddUsagePlan("api-usage-plan", new UsagePlanProps
+            {
+                Name = "UsagePlan",
+                Throttle = new ThrottleSettings
+                {
+                    RateLimit = 5,
+                    BurstLimit = 10,
+                }
+            });
+
+            plan.AddApiStage(new UsagePlanPerApiStage
+            {
+                Stage = api.DeploymentStage
+            });
+
+            var uploadLambdaIntegration = new LambdaIntegration(uploadLambda);
+
+            api.Root.AddResource("upload").AddMethod("POST", uploadLambdaIntegration, new MethodOptions
+            {
+                ApiKeyRequired = true,
+            });
+            #endregion
         }
     }
 }
diff --git a/backend/src/UploadLambda/Function.cs b/backend/src/UploadLambda/Function.cs
index 5b40e8d..9e28590 100644
--- a/backend/src/UploadLambda/Function.cs
+++ b/backend/src/UploadLambda/Function.cs
@@ -1,17 +1,83 @@
+using System.Text.Json;
+using Amazon.S3;
+using Amazon.S3.Model;
 using Amazon.DynamoDBv2;
 using Amazon.DynamoDBv2.DataModel;
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.APIGatewayEvents;
 using UploadLambda;
+using System.Net;
+
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
 
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var httpHeaders = new Dictionary<string, string>
+{
+    { "Content-Type", "application/json" }
+};
+
+var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    FileUploadRequest? input;
+    try
+    {
+        input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
+    }
+    catch (Exception e) when (e is JsonException || e is ArgumentNullException)
+    {
+        input = null;
+    }
+    if (input == null)
+    {
+        var error = new ApiError { Message = "Invalid request body" };
+        return new APIGatewayProxyResponse
+        {
+            StatusCode = (int)HttpStatusCode.BadRequest,
+            Body = JsonSerializer.Serialize(error),
+            Headers = httpHeaders,
+        };
+    }
+
+    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-{Guid.NewGuid()}.json";
+
+    var simulation = new Simulation()
+    {
+        Id = Guid.NewGuid().ToString(),
+        Name = input.Name,
+        FileName = fileName,
+        Downloads = 0,
+    };
+
+    await dbContext.SaveAsync(simulation);
+
+    var s3Request = new GetPreSignedUrlRequest
+    {
+        BucketName = bucketName,
+        Key = fileName,
+        Verb = HttpVerb.PUT,
+        Expires = DateTime.UtcNow.AddMinutes(15),
+        ContentType = input.ContentType,
+    };
+
+    var url = s3Client.GetPreSignedURL(s3Request);
+
+    var response = new FileUploadResponse
+    {
+        UploadUrl = url,
+        FileKey = fileName,
+    };
+
+    return new APIGatewayProxyResponse
+    {
+        StatusCode = (int)HttpStatusCode.OK,
+        Body = JsonSerializer.Serialize(response),
+        Headers = httpHeaders,
+    };
 };
 
 await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer())
diff --git a/backend/src/UploadLambda/Models.cs b/backend/src/UploadLambda/Models.cs
new file mode 100644
index 0000000..7ca47c1
--- /dev/null
+++ b/backend/src/UploadLambda/Models.cs
@@ -0,0 +1,30 @@
+﻿using Amazon.DynamoDBv2.DataModel;
+
+namespace UploadLambda;
+
+public class FileUploadRequest
+{
+    public required string Name { get; init; }
+    public required string ContentType { get; init; }
+}
+
+public class FileUploadResponse
+{
+    public required string UploadUrl { get; init; }
+    public required string FileKey { get; init; }
+}
+
+public class ApiError
+{
+    public required string Message { get; init; }
+}
+
+[DynamoDBTable("Simulation")]
+public class Simulation
+{
+    [DynamoDBHashKey]
+    public required string Id { get; init; }
+    public required string Name { get; init; }
+    public required string FileName { get; init; }
+    public int Downloads { get; set; }
+}
diff --git a/backend/src/UploadLambda/Simulation.cs b/backend/src/UploadLambda/Simulation.cs
deleted file mode 100644
index 78f57b1..0000000
--- a/backend/src/UploadLambda/Simulation.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-﻿using Amazon.DynamoDBv2.DataModel;
-
-namespace UploadLambda;
-
-[DynamoDBTable("Simulation")]
-public class Simulation
-{
-    [DynamoDBHashKey]
-    public string Id { get; set; } = Guid.NewGuid().ToString();
-    public required string Name { get; set; }
-    public required string FileName { get; set; }
-    public int Downloads { get; set; }
-}
diff --git a/backend/src/UploadLambda/UploadLambda.csproj b/backend/src/UploadLambda/UploadLambda.csproj
index e044b7d..c9d8c51 100644
--- a/backend/src/UploadLambda/UploadLambda.csproj
+++ b/backend/src/UploadLambda/UploadLambda.csproj
@@ -12,9 +12,12 @@
     <PublishReadyToRun>true</PublishReadyToRun>
   </PropertyGroup>
   <ItemGroup>
+    <PackageReference Include="Amazon.CDK.AWS.APIGateway" Version="1.204.0" />
+    <PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.7.1" />
     <PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="1.12.0" />
     <PackageReference Include="Amazon.Lambda.Core" Version="2.5.0" />
     <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.4" />
     <PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.406.25" />
+    <PackageReference Include="AWSSDK.S3" Version="3.7.406.4" />
   </ItemGroup>
 </Project>
\ No newline at end of file


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 9**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2e8ba2cbbc80bcc1e0c28f13abe201ce6ec77b91 and 2752e09583c675f5ca80e9bdc7e6baa5a79e1bd5.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `backend/src/Backend/BackendStack.cs` (4 hunks)
* `backend/src/UploadLambda/Function.cs` (1 hunks)
* `backend/src/UploadLambda/Models.cs` (1 hunks)
* `backend/src/UploadLambda/Simulation.cs` (0 hunks)
* `backend/src/UploadLambda/UploadLambda.csproj` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* backend/src/UploadLambda/Simulation.cs

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>backend/src/UploadLambda/Function.cs (1)</summary>

```
Learnt from: matfij
PR: matfij/particle-simulator#34
File: backend/src/FileUploadLambda/aws-lambda-tools-defaults.json:1-9
Timestamp: 2025-04-28T19:38:26.290Z
Learning: The FileUploadLambda function in the particle-simulator project is responsible for generating pre-signed URLs for S3 bucket uploads, not for streaming files. It should have an appropriate timeout setting (30-60 seconds) rather than the initial 600 seconds.
```

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>backend/src/UploadLambda/Function.cs (1)</summary><blockquote>

<details>
<summary>backend/src/UploadLambda/Models.cs (2)</summary>

* `FileUploadRequest` (5-9)
* `FileUploadResponse` (11-15)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Build and analyze

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>backend/src/Backend/BackendStack.cs (1)</summary>

`54-82`: **Good implementation of API Gateway with security measures.**

The API Gateway implementation correctly requires an API key and implements reasonable throttling limits (5 requests per second with a burst limit of 10). This helps prevent abuse and protects the service.

</details>
<details>
<summary>backend/src/UploadLambda/Models.cs (1)</summary>

`5-25`: **Good use of required modifiers and immutable properties.**

The models are well-structured with proper use of the `required` modifier to ensure proper object initialization and `init` properties for immutability where appropriate. The DynamoDB annotations are also correctly applied.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-02T10:20:26Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>♻️ Duplicate comments (5)</summary><blockquote>

<details>
<summary>backend/src/UploadLambda/UploadLambda.csproj (1)</summary><blockquote>

`21-21`: _🧹 Nitpick (assertive)_

**Version mismatch with other AWS SDK packages**  

`AWSSDK.S3` is pinned to `3.7.406.4` whereas `AWSSDK.DynamoDBv2` uses `3.7.406.25`. Keeping AWS SDK packages on the same minor/patch build simplifies dependency management and avoids edge-case incompatibilities.

</blockquote></details>
<details>
<summary>backend/src/UploadLambda/Function.cs (3)</summary><blockquote>

`13-15`: _🛠️ Refactor suggestion_

**Hard-coded bucket name reduces portability**  

`bucketName` is hard-coded. Use an environment variable (e.g., `SIMULATION_BUCKET_NAME`) or Lambda parameter to allow different buckets per environment without code changes.

---

`39-39`: _🧹 Nitpick (assertive)_

**Filename sanitisation still overly permissive**  

Only spaces are replaced; characters like `/`, `\`, `#`, or non-ASCII glyphs can still sneak into the S3 key and cause issues. Consider a regex that keeps `[a-z0-9-_]` and replaces everything else.

---

`49-60`: _🛠️ Refactor suggestion_

**No error handling around DynamoDB/S3 operations**  

`SaveAsync` and `GetPreSignedURL` may fail (e.g., throttling, IAM denial). Wrapping these calls in `try/catch` and returning a 5XX with a logged error will improve resiliency and observability.

</blockquote></details>
<details>
<summary>backend/src/UploadLambda/Models.cs (1)</summary><blockquote>

`5-9`: _🧹 Nitpick (assertive)_

**`ContentType` lacks validation**  

Accepting any string can allow invalid MIME types, leading to incorrect `Content-Type` on the pre-signed URL. Add a simple format check or use data-annotations.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2752e09583c675f5ca80e9bdc7e6baa5a79e1bd5 and 70a87f257e4a21e1604a75486497d285c2b5ebbd.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `backend/src/Backend.sln` (0 hunks)
* `backend/src/UploadLambda/Function.cs` (1 hunks)
* `backend/src/UploadLambda/Models.cs` (1 hunks)
* `backend/src/UploadLambda/UploadLambda.csproj` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* backend/src/Backend.sln

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>backend/src/UploadLambda/Function.cs (1)</summary>

```
Learnt from: matfij
PR: matfij/particle-simulator#34
File: backend/src/FileUploadLambda/aws-lambda-tools-defaults.json:1-9
Timestamp: 2025-04-28T19:38:26.290Z
Learning: The FileUploadLambda function in the particle-simulator project is responsible for generating pre-signed URLs for S3 bucket uploads, not for streaming files. It should have an appropriate timeout setting (30-60 seconds) rather than the initial 600 seconds.
```

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>backend/src/UploadLambda/Function.cs (1)</summary><blockquote>

<details>
<summary>backend/src/UploadLambda/Models.cs (3)</summary>

* `FileUploadRequest` (5-9)
* `ApiError` (17-20)
* `FileUploadResponse` (11-15)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Build and analyze

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-02T13:23:16Z
user: login: matfij
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-02T13:36:16Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-02T13:36:38Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>backend/src/UploadLambda/Function.cs (4)</summary><blockquote>

`13-13`: _🛠️ Refactor suggestion_

**Move bucket name to environment variable.**

Hardcoding the bucket name directly in the code makes it harder to change and maintain across environments. Consider using an environment variable instead.



```diff
-var bucketName = "particle-simulation-bucket";
+var bucketName = Environment.GetEnvironmentVariable("SIMULATION_BUCKET_NAME") ?? "particle-simulation-bucket";
```

---

`19-22`: _🛠️ Refactor suggestion_

**Add CORS headers to API responses.**

The current headers only include Content-Type. For browser clients accessing this API, you'll need CORS headers to prevent cross-origin issues.



```diff
var httpHeaders = new Dictionary<string, string>
{
    { "Content-Type", "application/json" },
+   { "Access-Control-Allow-Origin", "*" },
+   { "Access-Control-Allow-Methods", "POST, OPTIONS" },
+   { "Access-Control-Allow-Headers", "Content-Type,Authorization" }
};
```

---

`46-46`: _🛠️ Refactor suggestion_

**Improve filename sanitization for better compatibility.**

The current filename sanitization only replaces spaces with hyphens, which doesn't handle other special characters that could cause issues in URLs or file systems.



```diff
-var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-{Guid.NewGuid()}.json";
+var fileName = $"{System.Text.RegularExpressions.Regex.Replace(input.Name.ToLower(), @"[^a-z0-9-]", "-")}-{Guid.NewGuid()}.json";
```

---

`58-67`: _🛠️ Refactor suggestion_

**Add error handling for S3 pre-signed URL generation.**

The S3 operations lack error handling. Similar to database operations, S3 requests can fail for various reasons.


```diff
-var s3Request = new GetPreSignedUrlRequest
-{
-    BucketName = bucketName,
-    Key = fileName,
-    Verb = HttpVerb.PUT,
-    Expires = DateTime.UtcNow.AddMinutes(15),
-    ContentType = input.ContentType,
-};
-
-var url = s3Client.GetPreSignedURL(s3Request);
+var url = string.Empty;
+try
+{
+    var s3Request = new GetPreSignedUrlRequest
+    {
+        BucketName = bucketName,
+        Key = fileName,
+        Verb = HttpVerb.PUT,
+        Expires = DateTime.UtcNow.AddMinutes(15),
+        ContentType = input.ContentType,
+    };
+
+    url = s3Client.GetPreSignedURL(s3Request);
+}
+catch (Exception ex)
+{
+    context.Logger.LogError($"Error generating pre-signed URL: {ex.Message}");
+    return new APIGatewayProxyResponse
+    {
+        StatusCode = (int)HttpStatusCode.InternalServerError,
+        Body = JsonSerializer.Serialize(new ApiError { Message = "Failed to generate upload URL. Please try again later." }),
+        Headers = httpHeaders,
+    };
+}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 70a87f257e4a21e1604a75486497d285c2b5ebbd and 134f2e25cdd7bca3a600bac9d7002e10906c8bdc.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `backend/src/UploadLambda/Function.cs` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>backend/src/UploadLambda/Function.cs (1)</summary><blockquote>

<details>
<summary>backend/src/UploadLambda/Models.cs (3)</summary>

* `FileUploadRequest` (5-9)
* `ApiError` (17-20)
* `FileUploadResponse` (11-15)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Build and analyze

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>backend/src/UploadLambda/Function.cs (2)</summary>

`26-44`: **Good job properly handling deserialization exceptions.**

The error handling for JSON deserialization has been improved to catch both JsonException and ArgumentNullException, returning a proper 400 response with an error message.

---

`69-80`: **Good implementation of API Gateway response structure.**

The response is properly structured with the appropriate status code, serialized body, and headers. This is the correct way to return data from an API Gateway Lambda integration.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-02T13:39:21Z

Review comments:
diff_hunk: @@ -16,5 +16,6 @@
     <PackageReference Include="Amazon.Lambda.Core" Version="2.5.0" />
     <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.4" />
     <PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.406.25" />
+    <PackageReference Include="AWSSDK.S3" Version="3.7.406.4" />
path: backend/src/UploadLambda/UploadLambda.csproj
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider aligning AWS SDK package versions.**

The added S3 SDK (version 3.7.406.4) differs slightly from the DynamoDBv2 SDK (version 3.7.406.25). While this won't cause issues, aligning versions across AWS packages can simplify maintenance and prevent potential compatibility issues.

```diff
-    <PackageReference Include="AWSSDK.S3" Version="3.7.406.4" />
+    <PackageReference Include="AWSSDK.S3" Version="3.7.406.25" />
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    <PackageReference Include="AWSSDK.S3" Version="3.7.406.25" />
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/UploadLambda.csproj at line 19, the AWS SDK package version for AWSSDK.S3 is 3.7.406.4, which differs from the DynamoDBv2 SDK version 3.7.406.25. To align versions, update the AWSSDK.S3 package reference to version 3.7.406.25 to match the DynamoDBv2 SDK version, ensuring consistent AWS SDK package versions across the project.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T10:20:25Z
author_association: NONE
diff_hunk: @@ -3,15 +3,46 @@
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.S3;
+using Amazon.S3.Model;
 using UploadLambda;
 
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
path: backend/src/UploadLambda/Function.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Move bucket name to environment variable.**

Hardcoding the bucket name directly in the code makes it harder to change and maintain across environments. Consider using an environment variable instead.

```diff
-var bucketName = "particle-simulation-bucket";
+var bucketName = Environment.GetEnvironmentVariable("SIMULATION_BUCKET_NAME") ?? "particle-simulation-bucket";
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
var bucketName = Environment.GetEnvironmentVariable("SIMULATION_BUCKET_NAME")
                 ?? "particle-simulation-bucket";
var s3Client = new AmazonS3Client();
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Function.cs around lines 10 to 11, the bucket name is hardcoded as a string literal, which reduces flexibility and maintainability. Replace the hardcoded bucket name with a call to retrieve it from an environment variable, such as using Environment.GetEnvironmentVariable("BUCKET_NAME"). Ensure the environment variable is properly configured in the deployment environment.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T10:20:25Z
author_association: NONE
diff_hunk: @@ -3,15 +3,46 @@
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.S3;
+using Amazon.S3.Model;
 using UploadLambda;
 
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
+
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var handler = async (FileUploadRequest input, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-${Guid.NewGuid()}.json";
path: backend/src/UploadLambda/Function.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improve filename sanitization.**

The current filename sanitization only replaces spaces with hyphens, which doesn't handle other special characters that could cause issues in URLs or file systems.

```diff
-    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-${Guid.NewGuid()}.json";
+    var fileName = $"{System.Text.RegularExpressions.Regex.Replace(input.Name.ToLower(), @"[^a-z0-9-]", "-")}-{Guid.NewGuid()}.json";
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    var fileName = $"{System.Text.RegularExpressions.Regex.Replace(input.Name.ToLower(), @"[^a-z0-9-]", "-")}-{Guid.NewGuid()}.json";
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Function.cs at line 18, the filename sanitization only replaces spaces with hyphens, which is insufficient for handling other special characters. Update the sanitization logic to remove or replace all characters that are not URL- or filesystem-safe, such as using a regular expression to allow only alphanumeric characters, hyphens, and underscores, and replace or remove all others before appending the GUID and file extension.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T10:20:25Z
author_association: NONE
diff_hunk: @@ -3,15 +3,46 @@
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.S3;
+using Amazon.S3.Model;
 using UploadLambda;
 
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
+
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var handler = async (FileUploadRequest input, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-${Guid.NewGuid()}.json";
+
+    var simulation = new Simulation()
+    {
+        Id = Guid.NewGuid().ToString(),
+        Name = input.Name,
+        FileName = fileName,
+        Downloads = 0,
+    };
+
+    await dbContext.SaveAsync(simulation);
+
+    var request = new GetPreSignedUrlRequest
+    {
+        BucketName = bucketName,
+        Key = fileName,
+        Verb = HttpVerb.PUT,
+        Expires = DateTime.UtcNow.AddMinutes(15),
+        ContentType = input.ContentType,
+    };
+
+    var url = s3Client.GetPreSignedURL(request);
+
+    return new FileUploadResponse
+    {
+        UploadUrl = url,
+        FileKey = fileName,
+    };
 };
path: backend/src/UploadLambda/Function.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix string interpolation error in filename generation and add error handling.**

There are several issues that need to be addressed in the handler function:

1. The string interpolation syntax is incorrect in the filename generation.
2. The filename sanitization is minimal and doesn't handle various special characters.
3. There's no error handling for potential DynamoDB or S3 operation failures.

Apply this fix to correct the string interpolation and add error handling:

```diff
var handler = async (FileUploadRequest input, ILambdaContext context) =>
{
-    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-${Guid.NewGuid()}.json";
+    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-{Guid.NewGuid()}.json";

-    var simulation = new Simulation()
-    {
-        Id = Guid.NewGuid().ToString(),
-        Name = input.Name,
-        FileName = fileName,
-        Downloads = 0,
-    };
+    try
+    {
+        var simulation = new Simulation()
+        {
+            Id = Guid.NewGuid().ToString(),
+            Name = input.Name,
+            FileName = fileName,
+            Downloads = 0,
+        };

-    await dbContext.SaveAsync(simulation);
+        await dbContext.SaveAsync(simulation);

-    var request = new GetPreSignedUrlRequest
-    {
-        BucketName = bucketName,
-        Key = fileName,
-        Verb = HttpVerb.PUT,
-        Expires = DateTime.UtcNow.AddMinutes(15),
-        ContentType = input.ContentType,
-    };
+        var request = new GetPreSignedUrlRequest
+        {
+            BucketName = bucketName,
+            Key = fileName,
+            Verb = HttpVerb.PUT,
+            Expires = DateTime.UtcNow.AddMinutes(15),
+            ContentType = input.ContentType,
+        };

-    var url = s3Client.GetPreSignedURL(request);
+        var url = s3Client.GetPreSignedURL(request);

-    return new FileUploadResponse
-    {
-        UploadUrl = url,
-        FileKey = fileName,
-    };
+        return new FileUploadResponse
+        {
+            UploadUrl = url,
+            FileKey = fileName,
+        };
+    }
+    catch (Exception ex)
+    {
+        context.Logger.LogError($"Error processing upload request: {ex.Message}");
+        throw;
+    }
};
```

Additionally, consider implementing a more robust filename sanitization method to handle all special characters that could cause issues in URLs.

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Function.cs around lines 16 to 46, fix the string interpolation in the filename generation by replacing the incorrect syntax with the correct C# interpolation format using $"...{...}...". Add try-catch blocks around the database save and S3 pre-signed URL generation calls to handle potential exceptions and return appropriate error responses or log errors. Also, improve the filename sanitization by implementing a method that removes or replaces all special characters that could cause issues in URLs, not just spaces.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T10:20:25Z
author_association: NONE
diff_hunk: @@ -53,7 +48,38 @@ internal BackendStack(Construct scope, string id, IStackProps props = null) : ba
             };
             var simulationBucket = new Bucket(this, "particle-simulation-bucket", simulationBucketProps);
 
-            simulationBucket.GrantWrite(fileUploadLambda);
+            simulationBucket.GrantWrite(uploadLambda);
+            #endregion
+
+            #region API Gateway
+            var api = new RestApi(this, "simulation-api", new RestApiProps
+            {
+                RestApiName = "Particle Simulator Gateway",
+                ApiKeySourceType = ApiKeySourceType.HEADER,
+            });
path: backend/src/Backend/BackendStack.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add CORS configuration for browser-based applications.**

If this API will be called from browser applications, you'll need to configure CORS. Otherwise, browsers will block requests due to the same-origin policy.

```diff
var api = new RestApi(this, "simulation-api", new RestApiProps
{
    RestApiName = "Particle Simulator Gateway",
    ApiKeySourceType = ApiKeySourceType.HEADER,
+    DefaultCorsPreflightOptions = new CorsOptions
+    {
+        AllowOrigins = new[] { "*" }, // Restrict to specific origins in production
+        AllowMethods = new[] { "POST", "OPTIONS" },
+        AllowHeaders = new[] { "Content-Type", "X-Api-Key" },
+    }
});
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
var api = new RestApi(this, "simulation-api", new RestApiProps
{
    RestApiName = "Particle Simulator Gateway",
    ApiKeySourceType = ApiKeySourceType.HEADER,
    DefaultCorsPreflightOptions = new CorsOptions
    {
        AllowOrigins = new[] { "*" }, // Restrict to specific origins in production
        AllowMethods = new[] { "POST", "OPTIONS" },
        AllowHeaders = new[] { "Content-Type", "X-Api-Key" },
    }
});
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/Backend/BackendStack.cs around lines 55 to 59, the RestApi is missing CORS configuration, which is necessary for browser-based applications to avoid same-origin policy issues. Add a CorsPreflightOptions property to the RestApiProps with appropriate settings such as allowedOrigins, allowedMethods, and allowedHeaders to enable CORS support for the API.
```

</details>

---

_🧹 Nitpick (assertive)_

**Add CloudWatch Logs access logging for API Gateway.**

Enabling access logging for the API Gateway helps with debugging, monitoring, and auditing API usage.

```diff
var api = new RestApi(this, "simulation-api", new RestApiProps
{
    RestApiName = "Particle Simulator Gateway",
    ApiKeySourceType = ApiKeySourceType.HEADER,
+    DeployOptions = new StageOptions
+    {
+        AccessLogDestination = new LogGroupLogDestination(new Amazon.CDK.AWS.Logs.LogGroup(this, "ApiGatewayAccessLogs")),
+        AccessLogFormat = AccessLogFormat.JsonWithStandardFields()
+    }
});
```

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/Backend/BackendStack.cs around lines 55 to 59, the RestApi is created without CloudWatch Logs access logging enabled. To fix this, configure the RestApiProps to include access logging by creating a LogGroup for the API Gateway access logs and setting the AccessLogDestination and AccessLogFormat properties accordingly. This will enable detailed access logs for monitoring and debugging API usage.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T10:20:25Z
author_association: NONE
diff_hunk: @@ -53,7 +48,38 @@ internal BackendStack(Construct scope, string id, IStackProps props = null) : ba
             };
             var simulationBucket = new Bucket(this, "particle-simulation-bucket", simulationBucketProps);
 
-            simulationBucket.GrantWrite(fileUploadLambda);
+            simulationBucket.GrantWrite(uploadLambda);
+            #endregion
+
+            #region API Gateway
+            var api = new RestApi(this, "simulation-api", new RestApiProps
+            {
+                RestApiName = "Particle Simulator Gateway",
+                ApiKeySourceType = ApiKeySourceType.HEADER,
+            });
+
+            var plan = api.AddUsagePlan("api-usage-plan", new UsagePlanProps
+            {
+                Name = "UsagePlan",
+                Throttle = new ThrottleSettings
+                {
+                    RateLimit = 5,
+                    BurstLimit = 10,
+                }
+            });
+
+            plan.AddApiStage(new UsagePlanPerApiStage
+            {
+                Stage = api.DeploymentStage
+            });
+
+            var uploadLambdaIntegration = new LambdaIntegration(uploadLambda);
+
+            api.Root.AddResource("upload").AddMethod("POST", uploadLambdaIntegration, new MethodOptions
+            {
+                ApiKeyRequired = true,
+            });
+            #endregion
path: backend/src/Backend/BackendStack.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Export API URL and API key for client usage.**

The stack doesn't export the API URL or API key, which clients will need to interact with the API. Consider adding these exports.

```diff
#region API Gateway
var api = new RestApi(this, "simulation-api", new RestApiProps
{
    RestApiName = "Particle Simulator Gateway",
    ApiKeySourceType = ApiKeySourceType.HEADER,
});

+var apiKey = api.AddApiKey("ApiKey");

var plan = api.AddUsagePlan("api-usage-plan", new UsagePlanProps
{
    Name = "UsagePlan",
    Throttle = new ThrottleSettings
    {
        RateLimit = 5,
        BurstLimit = 10,
    }
});

plan.AddApiStage(new UsagePlanPerApiStage
{
    Stage = api.DeploymentStage
});

+plan.AddApiKey(apiKey);

var uploadLambdaIntegration = new LambdaIntegration(uploadLambda);

api.Root.AddResource("upload").AddMethod("POST", uploadLambdaIntegration, new MethodOptions
{
    ApiKeyRequired = true,
});

+// Export the API URL and API key reference for clients
+new CfnOutput(this, "ApiUrl", new CfnOutputProps
+{
+    Value = api.Url,
+    Description = "API Gateway URL",
+    ExportName = "ParticleSimulatorApiUrl"
+});
+
+new CfnOutput(this, "ApiKeyId", new CfnOutputProps
+{
+    Value = apiKey.KeyId,
+    Description = "API Key ID (retrieve value from AWS Console or CLI)",
+    ExportName = "ParticleSimulatorApiKeyId"
+});
#endregion
```

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/Backend/BackendStack.cs around lines 54 to 82, the API URL and API key are not exported from the stack, which are necessary for clients to access the API. Add CloudFormation outputs for the API Gateway URL and the API key generated by the usage plan. Use the api.Url property for the URL export and create an API key resource if not already present, then export its key value. This will make these values accessible to clients after deployment.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T10:20:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+﻿using Amazon.DynamoDBv2.DataModel;
+
+namespace UploadLambda;
+
+public class FileUploadRequest
+{
+    public required string Name { get; init; }
+    public required string ContentType { get; init; }
+}
path: backend/src/UploadLambda/Models.cs
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Add validation for ContentType property.**

The `ContentType` property should be validated to ensure it's a valid MIME type. This will prevent issues when setting the ContentType on the pre-signed URL.

```diff
public class FileUploadRequest
{
    public required string Name { get; init; }
-    public required string ContentType { get; init; }
+    
+    private string _contentType = string.Empty;
+    public required string ContentType 
+    { 
+        get => _contentType;
+        init
+        {
+            if (string.IsNullOrWhiteSpace(value) || !value.Contains('/'))
+            {
+                throw new ArgumentException("ContentType must be a valid MIME type (e.g., application/json)");
+            }
+            _contentType = value;
+        }
+    }
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
public class FileUploadRequest
{
    public required string Name { get; init; }

    private string _contentType = string.Empty;
    public required string ContentType
    {
        get => _contentType;
        init
        {
            if (string.IsNullOrWhiteSpace(value) || !value.Contains('/'))
            {
                throw new ArgumentException("ContentType must be a valid MIME type (e.g., application/json)");
            }
            _contentType = value;
        }
    }
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Models.cs around lines 5 to 9, the FileUploadRequest class lacks validation for the ContentType property to ensure it is a valid MIME type. Add validation logic, such as a method or attribute, to verify that ContentType conforms to standard MIME type formats before it is used. This can be done by implementing a validation method that checks the format or using data annotations with a custom validation attribute to enforce MIME type correctness.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T10:20:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+﻿using Amazon.DynamoDBv2.DataModel;
+
+namespace UploadLambda;
+
+public class FileUploadRequest
+{
+    public required string Name { get; init; }
+    public required string ContentType { get; init; }
+}
+
+public class FileUploadResponse
+{
+    public required string UploadUrl { get; init; }
+    public required string FileKey { get; init; }
+}
+
+[DynamoDBTable("Simulation")]
+public class Simulation
+{
+    [DynamoDBHashKey]
+    public required string Id { get; init; }
+    public required string Name { get; init; }
+    public required string FileName { get; init; }
+    public int Downloads { get; set; }
+}
path: backend/src/UploadLambda/Models.cs
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Add XML documentation comments.**

Adding XML documentation comments to your models improves code readability and enables IntelliSense documentation for users of these classes.

```diff
+/// <summary>
+/// Represents a request to upload a file to the simulation system.
+/// </summary>
public class FileUploadRequest
{
+    /// <summary>
+    /// Gets the name of the file to be uploaded.
+    /// </summary>
    public required string Name { get; init; }
+    
+    /// <summary>
+    /// Gets the MIME content type of the file to be uploaded.
+    /// </summary>
    public required string ContentType { get; init; }
}

+/// <summary>
+/// Represents a response containing the pre-signed URL for file upload.
+/// </summary>
public class FileUploadResponse
{
+    /// <summary>
+    /// Gets the pre-signed URL for uploading the file.
+    /// </summary>
    public required string UploadUrl { get; init; }
+    
+    /// <summary>
+    /// Gets the generated file key in the S3 bucket.
+    /// </summary>
    public required string FileKey { get; init; }
}

+/// <summary>
+/// Represents a simulation record stored in DynamoDB.
+/// </summary>
[DynamoDBTable("Simulation")]
public class Simulation
{
+    /// <summary>
+    /// Gets the unique identifier for the simulation.
+    /// </summary>
    [DynamoDBHashKey]
    public required string Id { get; init; }
+    
+    /// <summary>
+    /// Gets the name of the simulation.
+    /// </summary>
    public required string Name { get; init; }
+    
+    /// <summary>
+    /// Gets the filename of the simulation file in S3.
+    /// </summary>
    public required string FileName { get; init; }
+    
+    /// <summary>
+    /// Gets or sets the number of times the simulation has been downloaded.
+    /// </summary>
    public int Downloads { get; set; }
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
/// <summary>
/// Represents a request to upload a file to the simulation system.
/// </summary>
public class FileUploadRequest
{
    /// <summary>
    /// Gets the name of the file to be uploaded.
    /// </summary>
    public required string Name { get; init; }

    /// <summary>
    /// Gets the MIME content type of the file to be uploaded.
    /// </summary>
    public required string ContentType { get; init; }
}

/// <summary>
/// Represents a response containing the pre-signed URL for file upload.
/// </summary>
public class FileUploadResponse
{
    /// <summary>
    /// Gets the pre-signed URL for uploading the file.
    /// </summary>
    public required string UploadUrl { get; init; }

    /// <summary>
    /// Gets the generated file key in the S3 bucket.
    /// </summary>
    public required string FileKey { get; init; }
}

/// <summary>
/// Represents a simulation record stored in DynamoDB.
/// </summary>
[DynamoDBTable("Simulation")]
public class Simulation
{
    /// <summary>
    /// Gets the unique identifier for the simulation.
    /// </summary>
    [DynamoDBHashKey]
    public required string Id { get; init; }

    /// <summary>
    /// Gets the name of the simulation.
    /// </summary>
    public required string Name { get; init; }

    /// <summary>
    /// Gets the filename of the simulation file in S3.
    /// </summary>
    public required string FileName { get; init; }

    /// <summary>
    /// Gets or sets the number of times the simulation has been downloaded.
    /// </summary>
    public int Downloads { get; set; }
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Models.cs around lines 5 to 25, the model classes lack XML documentation comments. Add XML summary comments above each class and their properties to describe their purpose and usage. This will improve code readability and provide IntelliSense support for developers using these models.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T10:20:26Z
author_association: NONE
diff_hunk: @@ -12,9 +12,12 @@
     <PublishReadyToRun>true</PublishReadyToRun>
   </PropertyGroup>
   <ItemGroup>
+    <PackageReference Include="Amazon.CDK.AWS.APIGateway" Version="1.204.0" />
+    <PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.7.1" />
path: backend/src/UploadLambda/UploadLambda.csproj
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Questionable dependency – CDK library referenced inside Lambda project**  

`Amazon.CDK.AWS.APIGateway` is an *infrastructure* library; it is generally referenced by the CDK stack project, not the Lambda runtime project. Shipping it with the Lambda bundle increases cold-start size by several MB without providing runtime value.  
Consider removing this reference from the Lambda `.csproj` and keeping it only in the CDK/stack project.

---



</details>

**Remove infrastructure-only dependency from Lambda project**  

The `Amazon.CDK.AWS.APIGateway` package is an infrastructure (CDK) library and provides no runtime value inside your Lambda—only IAM/CloudFormation constructs. Including it in the Lambda bundle inflates cold-start duration by several MB. Remove it from the Lambda project and keep it only in your CDK/stack project.

- File: `backend/src/UploadLambda/UploadLambda.csproj`  
  ```diff
  - <PackageReference Include="Amazon.CDK.AWS.APIGateway" Version="1.204.0" />
  ```
  *Leave* the `Amazon.Lambda.APIGatewayEvents` reference intact, as it’s required for API Gateway event models at runtime.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    <PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.7.1" />
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/UploadLambda.csproj at lines 15-16, remove the PackageReference to "Amazon.CDK.AWS.APIGateway" as it is an infrastructure-only CDK library not needed at runtime in the Lambda project. Keep the "Amazon.Lambda.APIGatewayEvents" reference intact since it is required for runtime API Gateway event handling. This will reduce the Lambda bundle size and improve cold-start performance.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T13:23:15Z
author_association: NONE
diff_hunk: @@ -1,17 +1,76 @@
+using System.Text.Json;
+using Amazon.S3;
+using Amazon.S3.Model;
 using Amazon.DynamoDBv2;
 using Amazon.DynamoDBv2.DataModel;
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.APIGatewayEvents;
 using UploadLambda;
+using System.Net;
+
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
 
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var httpHeaders = new Dictionary<string, string>
+{
+    { "Content-Type", "application/json" }
+};
+
+var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    var input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
+
+    if (input == null)
+    {
+        var error = new ApiError { Message = "Invalid request body" };
+        return new APIGatewayProxyResponse
+        {
+            StatusCode = (int)HttpStatusCode.BadRequest,
+            Body = JsonSerializer.Serialize(error),
+            Headers = httpHeaders,
+        };
+    }
path: backend/src/UploadLambda/Function.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Unhandled deserialization exceptions lead to 500 responses**  

`JsonSerializer.Deserialize` will throw `JsonException` when the JSON is malformed or required properties are missing. The current code catches only the `null` result, not the exception, so the Lambda returns an unhandled 500.  
Wrap deserialization in a try/catch and return a `400` with a proper `ApiError` on failure.

```diff
-    var input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
-    if (input == null)
+    FileUploadRequest? input;
+    try
+    {
+        input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
+    }
+    catch (JsonException)
+    {
+        input = null;
+    }
+
+    if (input is null)
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
{
    FileUploadRequest? input;
    try
    {
        input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
    }
    catch (JsonException)
    {
        input = null;
    }

    if (input is null)
    {
        var error = new ApiError { Message = "Invalid request body" };
        return new APIGatewayProxyResponse
        {
            StatusCode = (int)HttpStatusCode.BadRequest,
            Body = JsonSerializer.Serialize(error),
            Headers = httpHeaders,
        };
    }
    // … rest of handler …
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Function.cs around lines 24 to 37, the code currently calls JsonSerializer.Deserialize without handling exceptions, which causes unhandled 500 errors on malformed JSON. To fix this, wrap the deserialization call in a try/catch block that catches JsonException, and in the catch block return a 400 response with an ApiError indicating invalid request body. This ensures malformed input results in a proper client error response instead of a server error.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 134f2e2
created_at: 2025-05-02T13:23:15Z
author_association: NONE
diff_hunk: @@ -1,17 +1,76 @@
+using System.Text.Json;
+using Amazon.S3;
+using Amazon.S3.Model;
 using Amazon.DynamoDBv2;
 using Amazon.DynamoDBv2.DataModel;
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.APIGatewayEvents;
 using UploadLambda;
+using System.Net;
+
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
 
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var httpHeaders = new Dictionary<string, string>
+{
+    { "Content-Type", "application/json" }
+};
+
+var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    var input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
+
+    if (input == null)
+    {
+        var error = new ApiError { Message = "Invalid request body" };
+        return new APIGatewayProxyResponse
+        {
+            StatusCode = (int)HttpStatusCode.BadRequest,
+            Body = JsonSerializer.Serialize(error),
+            Headers = httpHeaders,
+        };
+    }
+
+    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-{Guid.NewGuid()}.json";
+
+    var simulation = new Simulation()
+    {
+        Id = Guid.NewGuid().ToString(),
+        Name = input.Name,
+        FileName = fileName,
+        Downloads = 0,
+    };
+
+    await dbContext.SaveAsync(simulation);
+
+    var s3Request = new GetPreSignedUrlRequest
+    {
+        BucketName = bucketName,
+        Key = fileName,
+        Verb = HttpVerb.PUT,
+        Expires = DateTime.UtcNow.AddMinutes(15),
+        ContentType = input.ContentType,
+    };
+
+    var url = s3Client.GetPreSignedURL(s3Request);
+
+    var response = new FileUploadResponse
+    {
+        UploadUrl = url,
+        FileKey = fileName,
+    };
+
+    return new APIGatewayProxyResponse
+    {
+        StatusCode = (int)HttpStatusCode.OK,
+        Body = JsonSerializer.Serialize(response),
+        Headers = httpHeaders,
+    };
path: backend/src/UploadLambda/Function.cs
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Missing CORS headers**  

If this Lambda is invoked through an API Gateway intended for browser clients, add `Access-Control-Allow-Origin` (and possibly `-Methods`, `-Headers`) to the response headers to avoid CORS failures.

---

🏁 Script executed:

```shell
#!/bin/bash
set -e
# Locate the definition and population of httpHeaders in Function.cs
rg -n "httpHeaders" -A5 -B5 backend/src/UploadLambda/Function.cs
```

Length of output: 1139

---



</details>

**Add CORS headers to API responses**  
This Lambda currently only sets `Content-Type`. If it’s invoked via API Gateway for browser clients, you must include CORS headers (e.g. `Access‐Control‐Allow‐Origin`, and as needed `-Methods`, `-Headers`) to prevent CORS errors. You can either:  
- Augment the `httpHeaders` dictionary in `backend/src/UploadLambda/Function.cs` (lines 19–23)  
- Or configure CORS on the API Gateway integration  

Key spots to update:  
- httpHeaders definition (lines 19–23)  
- Response builders at lines 31–36 (BadRequest) and 68–73 (OK)

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Function.cs around lines 19 to 23 and 68 to 73, the httpHeaders dictionary and API response are missing CORS headers. To fix this, add the 'Access-Control-Allow-Origin' header with a value like '*' to the httpHeaders dictionary where it is defined, and ensure this dictionary is included in all API Gateway responses, including the OK response at lines 68 to 73 and the BadRequest response at lines 31 to 36. This will prevent CORS errors when the Lambda is invoked from browser clients.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T13:23:15Z
author_association: NONE
diff_hunk: @@ -0,0 +1,30 @@
+﻿using Amazon.DynamoDBv2.DataModel;
+
+namespace UploadLambda;
+
+public class FileUploadRequest
+{
+    public required string Name { get; init; }
+    public required string ContentType { get; init; }
+}
+
+public class FileUploadResponse
+{
+    public required string UploadUrl { get; init; }
+    public required string FileKey { get; init; }
+}
+
+public class ApiError
+{
+    public required string Message { get; init; }
+}
+
+[DynamoDBTable("Simulation")]
+public class Simulation
+{
+    [DynamoDBHashKey]
+    public required string Id { get; init; }
+    public required string Name { get; init; }
+    public required string FileName { get; init; }
+    public int Downloads { get; set; }
+}
path: backend/src/UploadLambda/Models.cs
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Simulation model: clarify primary-key and add secondary indexes if needed**  

Only `Id` is marked as the hash key. If frequent queries by `Name` or `FileName` are expected, plan a GSI now to avoid future migrations.

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Models.cs around lines 22 to 30, the Simulation class only marks Id as the hash key, but if queries by Name or FileName are expected, add Global Secondary Indexes (GSIs) for these fields now. Define GSIs for Name and FileName by adding appropriate DynamoDB attributes and annotations to enable efficient querying without future schema migrations.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T13:23:15Z
author_association: NONE
diff_hunk: @@ -1,17 +1,76 @@
+using System.Text.Json;
+using Amazon.S3;
+using Amazon.S3.Model;
 using Amazon.DynamoDBv2;
 using Amazon.DynamoDBv2.DataModel;
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.APIGatewayEvents;
 using UploadLambda;
+using System.Net;
+
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
 
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var httpHeaders = new Dictionary<string, string>
+{
+    { "Content-Type", "application/json" }
+};
+
+var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    var input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
+
+    if (input == null)
+    {
+        var error = new ApiError { Message = "Invalid request body" };
+        return new APIGatewayProxyResponse
+        {
+            StatusCode = (int)HttpStatusCode.BadRequest,
+            Body = JsonSerializer.Serialize(error),
+            Headers = httpHeaders,
+        };
+    }
path: backend/src/UploadLambda/Function.cs
user: login: matfij
body: Good catch, updated.
created_at: 2025-05-02T13:36:16Z
author_association: OWNER
diff_hunk: @@ -1,17 +1,76 @@
+using System.Text.Json;
+using Amazon.S3;
+using Amazon.S3.Model;
 using Amazon.DynamoDBv2;
 using Amazon.DynamoDBv2.DataModel;
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.APIGatewayEvents;
 using UploadLambda;
+using System.Net;
+
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
 
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var httpHeaders = new Dictionary<string, string>
+{
+    { "Content-Type", "application/json" }
+};
+
+var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    var input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
+
+    if (input == null)
+    {
+        var error = new ApiError { Message = "Invalid request body" };
+        return new APIGatewayProxyResponse
+        {
+            StatusCode = (int)HttpStatusCode.BadRequest,
+            Body = JsonSerializer.Serialize(error),
+            Headers = httpHeaders,
+        };
+    }
path: backend/src/UploadLambda/Function.cs
user: login: coderabbitai[bot]
body: @matfij Great! Thank you for implementing the error handling. This change will make the API more robust by properly handling malformed JSON input with clear 400 responses instead of unexpected 500 errors.

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20500%20errors%20from%20bad%20JSON!%20Wrap%20JsonSerializer.Deserialize%20in%20try%2Fcatch%20to%20catch%20JsonException%20and%20return%20a%20clear%20400%20error%20instead.%20Cleaner%2C%20safer%20API%20responses!%20%F0%9F%9A%80%20%40coderabbitai&url=https%3A%2F%2Fgithub.com%2Fmatfij%2Fparticle-simulator%2Fpull%2F36%23discussion_r2071606519)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@matfij Great! Thank you for implementing the error handling. This change will make the API more robust by properly handling malformed JSON input with clear 400 responses instead of unexpected 500 errors.

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20500%20errors%20from%20bad%20JSON!%20Wrap%20JsonSerializer.Deserialize%20in%20try%2Fcatch%20to%20catch%20JsonException%20and%20return%20a%20clear%20400%20error%20instead.%20Cleaner%2C%20safer%20API%20responses!%20%F0%9F%9A%80%20%40coderabbitai&url=https%3A%2F%2Fgithub.com%2Fmatfij%2Fparticle-simulator%2Fpull%2F36%23discussion_r2071606519)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T13:36:38Z
author_association: NONE
diff_hunk: @@ -1,17 +1,83 @@
+using System.Text.Json;
+using Amazon.S3;
+using Amazon.S3.Model;
 using Amazon.DynamoDBv2;
 using Amazon.DynamoDBv2.DataModel;
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.APIGatewayEvents;
 using UploadLambda;
+using System.Net;
+
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
 
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var httpHeaders = new Dictionary<string, string>
+{
+    { "Content-Type", "application/json" }
+};
+
+var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    FileUploadRequest? input;
+    try
+    {
+        input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
+    }
+    catch (Exception e) when (e is JsonException || e is ArgumentNullException)
+    {
+        input = null;
+    }
+    if (input == null)
+    {
+        var error = new ApiError { Message = "Invalid request body" };
+        return new APIGatewayProxyResponse
+        {
+            StatusCode = (int)HttpStatusCode.BadRequest,
+            Body = JsonSerializer.Serialize(error),
+            Headers = httpHeaders,
+        };
+    }
+
+    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-{Guid.NewGuid()}.json";
+
+    var simulation = new Simulation()
+    {
+        Id = Guid.NewGuid().ToString(),
+        Name = input.Name,
+        FileName = fileName,
+        Downloads = 0,
+    };
+
+    await dbContext.SaveAsync(simulation);
+
path: backend/src/UploadLambda/Function.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling for DynamoDB operations.**

The call to SaveAsync lacks error handling. Database operations can fail due to network issues, permission problems, or service unavailability.


```diff
-await dbContext.SaveAsync(simulation);
+try
+{
+    await dbContext.SaveAsync(simulation);
+}
+catch (Exception ex)
+{
+    context.Logger.LogError($"Error saving to DynamoDB: {ex.Message}");
+    return new APIGatewayProxyResponse
+    {
+        StatusCode = (int)HttpStatusCode.InternalServerError,
+        Body = JsonSerializer.Serialize(new ApiError { Message = "Failed to process your request. Please try again later." }),
+        Headers = httpHeaders,
+    };
+}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    var simulation = new Simulation()
    {
        Id = Guid.NewGuid().ToString(),
        Name = input.Name,
        FileName = fileName,
        Downloads = 0,
    };

    try
    {
        await dbContext.SaveAsync(simulation);
    }
    catch (Exception ex)
    {
        context.Logger.LogError($"Error saving to DynamoDB: {ex.Message}");
        return new APIGatewayProxyResponse
        {
            StatusCode = (int)HttpStatusCode.InternalServerError,
            Body = JsonSerializer.Serialize(new ApiError { Message = "Failed to process your request. Please try again later." }),
            Headers = httpHeaders,
        };
    }
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Function.cs around lines 48 to 57, the call to SaveAsync on the dbContext does not have error handling, which can cause unhandled exceptions if the database operation fails. Wrap the SaveAsync call in a try-catch block to catch potential exceptions, log the error details appropriately, and handle the failure gracefully, such as by returning an error response or retrying as needed.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T13:39:20Z
author_association: NONE
diff_hunk: @@ -1,17 +1,83 @@
+using System.Text.Json;
+using Amazon.S3;
+using Amazon.S3.Model;
 using Amazon.DynamoDBv2;
 using Amazon.DynamoDBv2.DataModel;
 using Amazon.Lambda.Core;
 using Amazon.Lambda.RuntimeSupport;
 using Amazon.Lambda.Serialization.SystemTextJson;
+using Amazon.Lambda.APIGatewayEvents;
 using UploadLambda;
+using System.Net;
+
+var bucketName = "particle-simulation-bucket";
+var s3Client = new AmazonS3Client();
 
 var dbClient = new AmazonDynamoDBClient();
 var dbContext = new DynamoDBContext(dbClient);
 
-var handler = async (Simulation input, ILambdaContext context) =>
+var httpHeaders = new Dictionary<string, string>
+{
+    { "Content-Type", "application/json" }
+};
+
+var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
 {
-    await dbContext.SaveAsync(input);
-    return input.Id;
+    FileUploadRequest? input;
+    try
+    {
+        input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
+    }
+    catch (Exception e) when (e is JsonException || e is ArgumentNullException)
+    {
+        input = null;
+    }
+    if (input == null)
+    {
+        var error = new ApiError { Message = "Invalid request body" };
+        return new APIGatewayProxyResponse
+        {
+            StatusCode = (int)HttpStatusCode.BadRequest,
+            Body = JsonSerializer.Serialize(error),
+            Headers = httpHeaders,
+        };
+    }
+
+    var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-{Guid.NewGuid()}.json";
+
+    var simulation = new Simulation()
+    {
+        Id = Guid.NewGuid().ToString(),
+        Name = input.Name,
+        FileName = fileName,
+        Downloads = 0,
+    };
+
+    await dbContext.SaveAsync(simulation);
+
+    var s3Request = new GetPreSignedUrlRequest
+    {
+        BucketName = bucketName,
+        Key = fileName,
+        Verb = HttpVerb.PUT,
+        Expires = DateTime.UtcNow.AddMinutes(15),
+        ContentType = input.ContentType,
+    };
+
+    var url = s3Client.GetPreSignedURL(s3Request);
+
+    var response = new FileUploadResponse
+    {
+        UploadUrl = url,
+        FileKey = fileName,
+    };
+
+    return new APIGatewayProxyResponse
+    {
+        StatusCode = (int)HttpStatusCode.OK,
+        Body = JsonSerializer.Serialize(response),
+        Headers = httpHeaders,
+    };
 };
path: backend/src/UploadLambda/Function.cs
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider wrapping the entire handler in a global try-catch.**

While you have specific error handling for deserialization, consider adding a global try-catch to ensure that unexpected exceptions don't result in unhandled 500 errors.


```diff
var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
{
+   try
+   {
        FileUploadRequest? input;
        // ... existing code ...
        
        return new APIGatewayProxyResponse
        {
            StatusCode = (int)HttpStatusCode.OK,
            Body = JsonSerializer.Serialize(response),
            Headers = httpHeaders,
        };
+   }
+   catch (Exception ex)
+   {
+       context.Logger.LogError($"Unhandled exception: {ex}");
+       return new APIGatewayProxyResponse
+       {
+           StatusCode = (int)HttpStatusCode.InternalServerError,
+           Body = JsonSerializer.Serialize(new ApiError { Message = "An unexpected error occurred." }),
+           Headers = httpHeaders,
+       };
+   }
};
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
{
    try
    {
        FileUploadRequest? input;
        try
        {
            input = JsonSerializer.Deserialize<FileUploadRequest>(request.Body);
        }
        catch (Exception e) when (e is JsonException || e is ArgumentNullException)
        {
            input = null;
        }

        if (input == null)
        {
            var error = new ApiError { Message = "Invalid request body" };
            return new APIGatewayProxyResponse
            {
                StatusCode = (int)HttpStatusCode.BadRequest,
                Body = JsonSerializer.Serialize(error),
                Headers = httpHeaders,
            };
        }

        var fileName = $"{input.Name.ToLower().Replace(" ", "-")}-{Guid.NewGuid()}.json";

        var simulation = new Simulation()
        {
            Id = Guid.NewGuid().ToString(),
            Name = input.Name,
            FileName = fileName,
            Downloads = 0,
        };

        await dbContext.SaveAsync(simulation);

        var s3Request = new GetPreSignedUrlRequest
        {
            BucketName = bucketName,
            Key = fileName,
            Verb = HttpVerb.PUT,
            Expires = DateTime.UtcNow.AddMinutes(15),
            ContentType = input.ContentType,
        };

        var url = s3Client.GetPreSignedURL(s3Request);

        var response = new FileUploadResponse
        {
            UploadUrl = url,
            FileKey = fileName,
        };

        return new APIGatewayProxyResponse
        {
            StatusCode = (int)HttpStatusCode.OK,
            Body = JsonSerializer.Serialize(response),
            Headers = httpHeaders,
        };
    }
    catch (Exception ex)
    {
        context.Logger.LogError($"Unhandled exception: {ex}");
        return new APIGatewayProxyResponse
        {
            StatusCode = (int)HttpStatusCode.InternalServerError,
            Body = JsonSerializer.Serialize(new ApiError { Message = "An unexpected error occurred." }),
            Headers = httpHeaders,
        };
    }
};
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

```
In backend/src/UploadLambda/Function.cs around lines 24 to 81, the handler currently only catches exceptions related to JSON deserialization but lacks a global try-catch block. Wrap the entire handler logic inside a try-catch block that catches general exceptions to prevent unhandled errors. In the catch block, log the exception and return a generic 500 Internal Server Error response with an appropriate error message to ensure robustness.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T13:39:20Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-02T10:15:56Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce a new API Gateway with an `/upload` POST endpoint that requires an API key and throttles requests. The upload Lambda function is refactored to accept an `APIGatewayProxyRequest`, deserialize it into a `FileUploadRequest`, generate a new simulation record with a unique file key, and return a pre-signed S3 upload URL via a JSON `FileUploadResponse`. Supporting models for request, response, error, and simulation metadata are consolidated into a new models file. The S3 write permission is granted to the correct Lambda, and the AWS S3 SDK and API Gateway packages are added as dependencies. The previously separate `Simulation` class file is removed, and the solution file is updated to remove the old project reference.

## Changes

| File(s)                                                                                  | Change Summary                                                                                                                              |
|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| backend/src/Backend/BackendStack.cs                                                      | Refactored Lambda and DynamoDB resource instantiation; removed unused Lambda; added API Gateway with `/upload` POST endpoint, API key, throttling, and Lambda integration. S3 bucket grants write to correct Lambda. Reorganized with region markers. |
| backend/src/UploadLambda/Function.cs                                                     | Changed handler signature to accept `APIGatewayProxyRequest` and return `APIGatewayProxyResponse`; deserializes request, validates input, creates simulation record, generates pre-signed S3 upload URL, and returns JSON response. Initialized S3 client and bucket name outside handler. |
| backend/src/UploadLambda/Models.cs                                                       | Added classes `FileUploadRequest`, `FileUploadResponse`, `ApiError`, and `Simulation` for structured Lambda input/output, error handling, and DynamoDB storage. |
| backend/src/UploadLambda/Simulation.cs                                                   | Deleted file; simulation data model moved and consolidated into `Models.cs`.                                                                |
| backend/src/UploadLambda/UploadLambda.csproj                                             | Added AWS SDK packages: `Amazon.CDK.AWS.APIGateway`, `Amazon.Lambda.APIGatewayEvents`, and `AWSSDK.S3`.                                        |
| backend/src/Backend.sln                                                                   | Removed project entry and configurations for the old `FileUploadLambda` project.                                                             |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant APIGateway
    participant UploadLambda
    participant DynamoDB
    participant S3

    Client->>APIGateway: POST /upload (FileUploadRequest, API key)
    APIGateway->>UploadLambda: Invoke Lambda with APIGatewayProxyRequest
    UploadLambda->>DynamoDB: Save new Simulation record
    UploadLambda->>S3: Generate pre-signed upload URL for file key
    UploadLambda-->>APIGateway: FileUploadResponse (UploadUrl, FileKey)
    APIGateway-->>Client: FileUploadResponse (UploadUrl, FileKey)
```

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=matfij/particle-simulator&utm_content=36):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-02T13:42:55Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=matfij_particle-simulator&pullRequest=36) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=matfij_particle-simulator&pullRequest=36&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=matfij_particle-simulator&pullRequest=36&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=matfij_particle-simulator&pullRequest=36&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=matfij_particle-simulator&pullRequest=36&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=matfij_particle-simulator&pullRequest=36&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=matfij_particle-simulator&pullRequest=36)


author_association: NONE