Skip to content

Commit 231ff53

Browse files
committed
Merge branch 'master' into users/devi/jobs-backend
2 parents f5a811a + 0477934 commit 231ff53

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+870
-307
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,5 @@ dependency-reduced-pom.xml
197197
README.html
198198
*.iml
199199
.idea
200-
.exercism
200+
.exercism
201+
/src/.vs

src/Backend/Backend.csproj

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk.Web">
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
44
<TargetFramework>net8.0</TargetFramework>
@@ -13,14 +13,18 @@
1313
<PackageReference Include="Azure.Core" Version="1.49.0" />
1414
<PackageReference Include="Azure.Identity" Version="1.17.0" />
1515
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />
16-
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.53.1" />
16+
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.54.0" />
1717
<PackageReference Include="Microsoft.Extensions.Logging.ApplicationInsights" Version="2.23.0" />
1818
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
19-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
19+
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
2020
</ItemGroup>
2121

2222
<ItemGroup>
2323
<ProjectReference Include="..\Common\Common.csproj" />
2424
</ItemGroup>
2525

26+
<ItemGroup>
27+
<Folder Include="Docs\" />
28+
</ItemGroup>
29+
2630
</Project>

src/Backend/Backend.csproj.user

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<ActiveDebugProfile>https</ActiveDebugProfile>
5+
</PropertyGroup>
6+
</Project>

src/Backend/Controllers/ProblemsController.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
namespace Backend.Controllers
22
{
3+
using Backend.Filters;
34
using Backend.Models.Public;
45
using Backend.Operations;
6+
using Common.Models;
57
using Microsoft.AspNetCore.Mvc;
6-
using ProblemPublicModel = Common.Models.Problem;
78

89
[ApiController]
910
[Route("api")]
1011
public class ProblemsController : ControllerBase
1112
{
12-
private AppContext appContext;
1313
private readonly ILogger<ProblemsController> logger;
1414
private readonly IConfiguration configuration;
15-
public ProblemsController(AppContext appContext, ILogger<ProblemsController> logger, IConfiguration configuration)
15+
private readonly DataProvider dataProvider;
16+
public ProblemsController(ILogger<ProblemsController> logger,
17+
DataProvider dataProvider,
18+
IConfiguration configuration)
1619
{
17-
this.appContext = appContext;
1820
this.logger = logger;
21+
this.dataProvider = dataProvider;
1922
this.configuration = configuration;
2023
}
2124

@@ -28,23 +31,23 @@ public ActionResult<string> GetHome()
2831

2932
[HttpGet]
3033
[Route("problems")]
31-
public async Task<ActionResult<IEnumerable<ProblemPublicModel>>> GetProblems(
34+
public async Task<ActionResult<IEnumerable<Problem>>> GetProblems(
3235
[FromQuery(Name = QueryParam.Skip)] int skip = 0,
3336
[FromQuery(Name = QueryParam.Limit)] int limit = 50,
3437
[FromQuery(Name = QueryParam.Company)] List<string>? companies = null,
35-
[FromQuery(Name = QueryParam.Difficulty)] List<Common.Models.Difficulty>? difficulties = null,
38+
[FromQuery(Name = QueryParam.Difficulty)] List<Difficulty>? difficulties = null,
3639
[FromQuery(Name = QueryParam.Tag)] List<string>? tags = null)
3740
{
3841
var filter = new ProblemFilter(skip, limit, companies, difficulties, tags);
39-
var filteredProblems = await appContext.dataProvider.GetProblemsAsync(filter);
42+
var filteredProblems = await dataProvider.GetProblemsAsync(filter);
4043
return Ok(filteredProblems);
4144
}
4245

4346
[HttpGet]
4447
[Route("problems/{id}")]
45-
public async Task<ActionResult<ProblemPublicModel>> GetProblems(string id)
48+
public async Task<ActionResult<Problem>> GetProblems(string id)
4649
{
47-
var problem = await appContext.dataProvider.GetProblemByIdAsync(id);
50+
var problem = await dataProvider.GetProblemByIdAsync(id);
4851
if (problem != null)
4952
{
5053
return Ok(problem);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Next Plans
2+
1. **Create functions for async updates for Jobs backend**
3+
2. **Integrate Redis for fast retreival**
4+
3. **Notification System upon new Job post arrival**
5+
3.1. **Will require integration to some async framework.**
6+
3.2. **Using Servicebus can be costly might use some other message broker**
7+
4. **Custom tracing framework using Prometheus and Grafana (learning purpose)**
8+
5. **Make the system scalable enough to register a new scraping source easily**

src/Backend/Filters/IFilter.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Common.Models;
2+
3+
namespace Backend.Filters
4+
{
5+
public interface IFilter
6+
{
7+
public List<Problem> ApplyFilterAsync(List<Problem> problems);
8+
}
9+
}
Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,41 @@
1-
namespace Backend.Operations
1+
using Common.Models;
2+
3+
namespace Backend.Filters
24
{
35
public class ProblemFilter : IFilter
46
{
57
private int skip = 0;
68
private int limit = 50;
79
private List<string> companies;
8-
private List<Common.Models.Difficulty> difficulties;
10+
private List<Difficulty> difficulties;
911
private List<string> tags;
1012

11-
public ProblemFilter(int skip, int limit, List<string>? companies, List<Common.Models.Difficulty>? difficulties, List<string>? tags)
13+
public ProblemFilter(int skip, int limit, List<string>? companies, List<Difficulty>? difficulties, List<string>? tags)
1214
{
1315
this.skip = skip;
1416
this.limit = Math.Min(limit, 50);
1517
this.companies = companies ?? new List<string>();
16-
this.difficulties = difficulties ?? new List<Common.Models.Difficulty>();
18+
this.difficulties = difficulties ?? new List<Difficulty>();
1719
this.tags = tags ?? new List<string>();
1820
}
1921

20-
public List<Common.Models.Problem> ApplyFilterAsync(List<Common.Models.Problem> problems)
22+
public List<Problem> ApplyFilterAsync(List<Problem> problems)
2123
{
22-
List<Common.Models.Problem> filteredProblems = problems;
24+
List<Problem> filteredProblems = problems;
2325

2426
// TODO: Add tags filtering logic with company
25-
if (companies != null && companies.Count > 0)
27+
if (companies != null && companies.Count > 0 || tags != null && tags.Count > 0)
2628
{
2729
filteredProblems = filteredProblems.Where(
2830
p => p.companies != null &&
29-
p.companies.Keys.Any(
30-
c => this.companies.Contains(c, StringComparer.OrdinalIgnoreCase))).ToList();
31+
p.companies.Any(kv =>
32+
(companies == null || companies.Count== 0 || companies.Contains(kv.Key, StringComparer.OrdinalIgnoreCase)) &&
33+
kv.Value.Any(t => tags == null || tags.Count == 0 || tags.Contains(t, StringComparer.OrdinalIgnoreCase)))).ToList();
3134
}
3235

3336
if (difficulties != null && difficulties.Count > 0)
3437
{
35-
filteredProblems = filteredProblems.Where(p => this.difficulties.Contains(p.difficulty)).ToList();
38+
filteredProblems = filteredProblems.Where(p => difficulties.Contains(p.difficulty)).ToList();
3639
}
3740

3841
filteredProblems = filteredProblems.Skip(skip).Take(limit).ToList();

src/Backend/Models/Internal/Problem.cs

Whitespace-only changes.

src/Backend/Models/Public/Problem.cs

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 15 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
namespace Backend.Operations
22
{
3+
using Backend.Filters;
4+
using Common.Cache;
5+
using Common.Constants;
36
using Common.Models;
4-
using Microsoft.Azure.Cosmos;
57

68
public class DataProvider
79
{
8-
private const int RefreshIntervalInHours = 3;
9-
private CosmosClient cosmosClient;
10-
private readonly IConfiguration configuration;
11-
ILogger<DataProvider> logger;
12-
private DateTime lastLoadedTime = DateTime.MinValue;
13-
private Task backgroundRefreshTask;
14-
private Dictionary<string, Problem> problemsCache = new Dictionary<string, Problem>(StringComparer.OrdinalIgnoreCase);
15-
16-
public DataProvider(CosmosClient client, IConfiguration configuration, ILogger<DataProvider> logger)
10+
private ICache _problemCache;
11+
private ILogger<DataProvider> _logger;
12+
public DataProvider([FromKeyedServices(CacheConstants.ProblemCacheKey)] ICache problemCache, ILogger<DataProvider> logger)
1713
{
18-
this.cosmosClient = client;
19-
this.configuration = configuration;
20-
this.logger = logger;
21-
this.backgroundRefreshTask = Task.Run(() => this.StartBackgroundRefreshAsync(CancellationToken.None));
14+
_problemCache = problemCache;
15+
_logger = logger;
2216
}
2317

2418
public async Task<List<Problem>> GetProblemsAsync(IFilter? filter = null)
@@ -43,68 +37,17 @@ public async Task<List<Problem>> GetProblemsAsync(IFilter? filter = null)
4337

4438
private async Task<Dictionary<string, Problem>> GetAllProblemsAsync()
4539
{
46-
if (problemsCache.Count == 0)
40+
if (_problemCache.Contains(CacheConstants.ProblemCacheKey))
4741
{
48-
await LoadLatestDataAsync();
49-
if (problemsCache.Count == 0)
50-
{
51-
this.logger.LogWarning("No problems found in the cache after loading data.");
52-
}
42+
_logger.LogInformation("Problem cache hit. Retrieving data from cache.");
5343
}
54-
return problemsCache;
55-
}
56-
57-
private async Task LoadLatestDataAsync()
58-
{
59-
int maxRetries = 3;
60-
for(int i = 0; i < maxRetries; i++)
44+
else
6145
{
62-
try
63-
{
64-
var dbId = configuration.GetValue<string>("ApplicationSettings:CosmosDbDatabaseId");
65-
var containerId = configuration.GetValue<string>("ApplicationSettings:CosmosDbContainerId");
66-
var db = cosmosClient.GetDatabase(dbId);
67-
var container = db.GetContainer(containerId);
68-
69-
var query = "SELECT * FROM c";
70-
var queryDefinition = new QueryDefinition(query);
71-
var queryResultSetIterator = container.GetItemQueryIterator<ProblemSchema>(queryDefinition);
72-
73-
List<Problem> results = new List<Problem>();
74-
while (queryResultSetIterator.HasMoreResults)
75-
{
76-
var response = await queryResultSetIterator.ReadNextAsync();
77-
results.AddRange(response.Select(item => new Problem(item)));
78-
}
79-
80-
lastLoadedTime = DateTime.UtcNow;
81-
problemsCache = results.ToDictionary(p => p.id, StringComparer.OrdinalIgnoreCase);
82-
this.logger.LogInformation($"Loaded {problemsCache.Count} problems from Cosmos DB at {lastLoadedTime}");
83-
break;
84-
}
85-
catch (Exception ex)
86-
{
87-
this.logger.LogError($"Error loading data from Cosmos DB. {ex}");
88-
await Task.Delay(TimeSpan.FromSeconds(2 * (i + 1)));
89-
}
90-
}
91-
}
92-
93-
public async Task StartBackgroundRefreshAsync(CancellationToken cancellationToken)
94-
{
95-
while (!cancellationToken.IsCancellationRequested)
96-
{
97-
try
98-
{
99-
await LoadLatestDataAsync();
100-
}
101-
catch (Exception ex)
102-
{
103-
this.logger.LogError($"Error during background data refresh: {ex}");
104-
}
105-
106-
await Task.Delay(TimeSpan.FromHours(RefreshIntervalInHours), cancellationToken);
46+
_logger.LogInformation("Problem cache miss. Loading data into cache.");
47+
await _problemCache.Populate();
10748
}
49+
50+
return _problemCache.Get<Dictionary<string, Problem>>(CacheConstants.ProblemCacheKey) ?? new Dictionary<string, Problem>(StringComparer.OrdinalIgnoreCase);
10851
}
10952
}
11053
}

0 commit comments

Comments
 (0)