Skip to content

Commit 64be0d6

Browse files
authored
Fix AdminKycProxy lambda function (#54)
* - update code * - fast solve build * - fix tests * - fix nuget conflicts * - catch "HttpStatusCode.TooManyRequests" exception * - save before returns when "TooManyRequests" received * - include logic to process only N users in one lambda execution * - refactor code * - add `GetHttpResponseAsync` func * - refactor exist code - update net6 to net8 * - work with users by statuses * - use `Poly` library for rate limit stategy - rename `LambdaFunction` to `AdminKycProxyLambda` * - bugfix with path segment adding - bugfix with async context working * - simplify code - clear users table by status if has changes * - fix the code * - try to use `contextFactory` for processing async each status * - solve work with context * - simplify code * - remove useless env variable * - use prod context connection * - add logging when call deleting rows * - use .sln from master branch * - just fix exist test - add attributes to mark envs as required
1 parent 19543e8 commit 64be0d6

10 files changed

Lines changed: 184 additions & 88 deletions

File tree

KYC.API.Proxy.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,4 @@ Global
5757
GlobalSection(ExtensibilityGlobals) = postSolution
5858
SolutionGuid = {0803A1E1-195C-4AC0-A580-70F2086DE96D}
5959
EndGlobalSection
60-
EndGlobal
60+
EndGlobal

src/AdminKycProxy/AdminKycProxy.csproj

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@
1111
<PublishReadyToRun>true</PublishReadyToRun>
1212
</PropertyGroup>
1313
<ItemGroup>
14-
<PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
15-
<PackageReference Include="Amazon.Lambda.Serialization.Json" Version="2.1.0" />
14+
<PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" />
15+
<PackageReference Include="Amazon.Lambda.Serialization.Json" Version="2.2.3" />
1616
<PackageReference Include="Flurl.Http" Version="3.2.4" />
17-
<PackageReference Include="KYC.DataBase" Version="1.0.6" />
17+
<PackageReference Include="KYC.DataBase" Version="1.0.8" />
1818
<PackageReference Include="SecretsManager" Version="1.0.0" />
1919
</ItemGroup>
20+
<ItemGroup>
21+
<None Update="appsettings.json">
22+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
23+
</None>
24+
</ItemGroup>
2025
</Project>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using Flurl;
2+
using System.Net;
3+
using Flurl.Http;
4+
using KYC.DataBase;
5+
using SecretsManager;
6+
using Amazon.Lambda.Core;
7+
using AdminKycProxy.Models;
8+
using KYC.DataBase.Models.Types;
9+
using EnvironmentManager.Extensions;
10+
using Microsoft.EntityFrameworkCore;
11+
12+
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
13+
14+
namespace AdminKycProxy;
15+
16+
public class AdminKycProxyLambda(SecretManager secretManager, IDbContextFactory<KycDbContext> contextFactory)
17+
{
18+
public AdminKycProxyLambda()
19+
: this(
20+
secretManager: new SecretManager(),
21+
contextFactory: new KycDbContextFactory()
22+
)
23+
{ }
24+
25+
public async Task<HttpStatusCode> RunAsync()
26+
{
27+
await Task.WhenAll(
28+
Enum.GetValues<Status>()
29+
.Select(ProcessStatusAsync)
30+
.ToList()
31+
);
32+
33+
return HttpStatusCode.OK;
34+
}
35+
36+
private async Task ProcessStatusAsync(Status status)
37+
{
38+
await using var context = await contextFactory.CreateDbContextAsync();
39+
40+
var skip = context.Users.Count(x => x.Status == status);
41+
42+
var url = Env.KYC_URL.Get<string>()
43+
.AppendPathSegment(status)
44+
.WithHeader("Authorization", secretManager.GetSecretValue(Env.SECRET_ID.Get<string>(), Env.SECRET_API_KEY.Get<string>()))
45+
.WithHeader("cache-control", "no-cache")
46+
.SetQueryParam("limit", 1);
47+
48+
var users = context.Users.Where(x => x.Status == status).ToArray();
49+
var response = await GetHttpResponseAsync(url, skip - 1);
50+
if (response?.Data.Records.Length > 0 && users.Length > 0 && users.Last().RefId != response.Data.Records.First().RefId)
51+
{
52+
context.Users.RemoveRange(context.Users.Where(x => x.Status == status));
53+
var deleted = await context.SaveChangesAsync();
54+
LambdaLogger.Log($"DELETED: For '{status}' status: {deleted}");
55+
}
56+
57+
url = url.SetQueryParam("limit", Env.PAGE_SIZE.Get<int>());
58+
do
59+
{
60+
response = await GetHttpResponseAsync(url, skip);
61+
if (response == null) break;
62+
63+
var downloadedUsers = response.Data.Records
64+
.Where(downloaded => users.All(dbUser => dbUser.RefId != downloaded.RefId))
65+
.ToList();
66+
67+
if (downloadedUsers.Count == 0) break;
68+
69+
context.Users.AddRange(downloadedUsers);
70+
skip += Env.PAGE_SIZE.Get<int>();
71+
} while (true);
72+
73+
var added = await context.SaveChangesAsync();
74+
LambdaLogger.Log($"ADDED: For '{status}' status: {added}");
75+
}
76+
77+
private static async Task<HttpResponse?> GetHttpResponseAsync(IFlurlRequest url, int skip)
78+
{
79+
var response = await url
80+
.SetQueryParam("skip", skip)
81+
.AllowHttpStatus(HttpStatusCode.TooManyRequests)
82+
.GetAsync();
83+
84+
return response.StatusCode != (int)HttpStatusCode.OK ? null : await response.GetJsonAsync<HttpResponse>();
85+
}
86+
}

src/AdminKycProxy/Env.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using EnvironmentManager.Attributes;
2+
3+
namespace AdminKycProxy;
4+
5+
public enum Env
6+
{
7+
[EnvironmentVariable(isRequired: true)]
8+
SECRET_ID,
9+
[EnvironmentVariable(isRequired: true)]
10+
SECRET_API_KEY,
11+
[EnvironmentVariable(isRequired: true)]
12+
KYC_URL,
13+
[EnvironmentVariable(isRequired: true, type: typeof(int))]
14+
PAGE_SIZE
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using KYC.DataBase;
2+
using Microsoft.EntityFrameworkCore;
3+
using ConfiguredSqlConnection.Extensions;
4+
5+
namespace AdminKycProxy;
6+
7+
public class KycDbContextFactory : IDbContextFactory<KycDbContext>
8+
{
9+
public KycDbContext CreateDbContext()
10+
{
11+
return new DbContextFactory<KycDbContext>().Create(ContextOption.Prod);
12+
}
13+
}

src/AdminKycProxy/LambdaFunction.cs

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/AdminKycProxy/LambdaSettings.cs

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/AdminKycProxy/Models/Data.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace AdminKycProxy.Models;
44

55
public class Data
66
{
7-
public User[] Records { get; set; } = Array.Empty<User>();
7+
public User[] Records { get; set; } = [];
88
public int Total { get; set; }
99
public int Skip { get; set; }
1010
public int Limit { get; set; }

src/AdminKycProxy/Readme.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,51 @@
11
# AdminKycProxy
22

3+
This starter project consists of:
4+
* LambdaFunction.cs - class file containing a class with a single function handler method
5+
* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS
6+
7+
You may also have a test project depending on the options selected.
8+
9+
The generated function handler is a simple method accepting a string argument that returns the json object with the input string. Replace the body of this method, and parameters, to suit your needs.
10+
11+
## Here are some steps to follow from Visual Studio:
12+
13+
To deploy your function to AWS Lambda, right click the project in Solution Explorer and select *Publish to AWS Lambda*.
14+
15+
To view your deployed function open its Function View window by double-clicking the function name shown beneath the AWS Lambda node in the AWS Explorer tree.
16+
17+
To perform testing against your deployed function use the Test Invoke tab in the opened Function View window.
18+
19+
To configure event sources for your deployed function, for example to have your function invoked when an object is created in an Amazon S3 bucket, use the Event Sources tab in the opened Function View window.
20+
21+
To update the runtime configuration of your deployed function use the Configuration tab in the opened Function View window.
22+
23+
To view execution logs of invocations of your function use the Logs tab in the opened Function View window.
24+
25+
## Here are some steps to follow to get started from the command line:
26+
27+
Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line.
28+
29+
#### Amazon.Lambda.Tools
30+
31+
Install `Amazon.Lambda.Tools` Global Tools if not already installed.
32+
```
33+
dotnet tool install -g Amazon.Lambda.Tools
34+
```
35+
36+
If already installed check if new version is available.
37+
```bash
38+
dotnet tool update -g Amazon.Lambda.Tools
39+
```
40+
41+
#### Amazon.Lambda.TestTool-8.0
42+
43+
Install `Amazon.Lambda.TestTool-8.0` Global Tools if not already installed.
44+
```
45+
dotnet tool install -g Amazon.Lambda.TestTool-8.0
46+
```
47+
48+
If already installed check if new version is available.
49+
```bash
50+
dotnet tool update -g Amazon.Lambda.TestTool-8.0
51+
```

tests/AdminKycProxy.Tests/LambdaFunctionTests.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using Flurl.Http.Testing;
88
using KYC.DataBase.Models;
99
using AdminKycProxy.Models;
10+
using KYC.DataBase.Models.Types;
11+
using Microsoft.EntityFrameworkCore;
1012
using ConfiguredSqlConnection.Extensions;
1113

1214
namespace AdminKycProxy.Tests;
@@ -15,7 +17,7 @@ public class LambdaFunctionTests
1517
{
1618
public LambdaFunctionTests()
1719
{
18-
Environment.SetEnvironmentVariable("DOWNLOADED_FROM", "0");
20+
Environment.SetEnvironmentVariable("PAGE_SIZE", "20");
1921
Environment.SetEnvironmentVariable("SECRET_ID", "SecretId");
2022
Environment.SetEnvironmentVariable("SECRET_API_KEY", "SecretApiKey");
2123
Environment.SetEnvironmentVariable("KYC_URL", "https://kyc.blockpass.org/kyc/1.0/connect/ClientId/applicants");
@@ -24,7 +26,7 @@ public LambdaFunctionTests()
2426
[Fact]
2527
internal void Ctor_Default()
2628
{
27-
var lambda = new LambdaFunction();
29+
var lambda = new AdminKycProxyLambda();
2830

2931
lambda.Should().NotBeNull();
3032
}
@@ -44,14 +46,13 @@ internal async Task RunAsync()
4446
Limit = 20,
4547
Skip = 0,
4648
Total = 1,
47-
Records = new[]
48-
{
49+
Records = [
4950
new User
5051
{
5152
RecordId = Guid.NewGuid().ToString(),
52-
Status = "approved"
53+
Status = Status.approved
5354
}
54-
}
55+
]
5556
}
5657
};
5758
using var httpTest = new HttpTest();
@@ -62,13 +63,14 @@ internal async Task RunAsync()
6263
.ForCallsTo("https://kyc.blockpass.org/kyc/1.0/connect/ClientId/applicants?limit=20&skip=20")
6364
.RespondWithJson(new HttpResponse());
6465

65-
var context = new DbContextFactory<KycDbContext>().Create(ContextOption.InMemory, Guid.NewGuid().ToString());
66+
var contextFactory = new Mock<IDbContextFactory<KycDbContext>>();
67+
contextFactory.Setup(x => x.CreateDbContextAsync(default))
68+
.ReturnsAsync(() => new DbContextFactory<KycDbContext>().Create(ContextOption.InMemory, Guid.NewGuid().ToString()));
6669

67-
var lambda = new LambdaFunction(secretManager.Object, context);
70+
var lambda = new AdminKycProxyLambda(secretManager.Object, contextFactory.Object);
6871

6972
var result = await lambda.RunAsync();
7073

7174
result.Should().Be(HttpStatusCode.OK);
72-
context.Users.Should().HaveCount(1);
7375
}
7476
}

0 commit comments

Comments
 (0)