Skip to content

Commit

Permalink
Merge pull request #42 from goodtocode/34-subjects-business-list-pagi…
Browse files Browse the repository at this point in the history
…nation-with-crud

34 subjects business list pagination with crud
  • Loading branch information
goodtocode authored Aug 19, 2023
2 parents 07553fa + ed39ff7 commit 74d2f0e
Show file tree
Hide file tree
Showing 98 changed files with 2,075 additions and 676 deletions.
11 changes: 6 additions & 5 deletions .github/workflows/gtc-rg-subjects-landingzone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ on:
description: 'Running mode'

env:
API_NAME: 'api-subjects-dev-001'
API_IDENTITY: 'identity-subjects-dev-001'
API_NAME: 'api-subjects-dev-001'
APPINSIGHTS_NAME: 'appi-subjects-dev-001'
ARM_PATH: './.azure'
AZURE_RG_ENVIRONMENT: 'Development'
Expand All @@ -32,6 +31,7 @@ env:
PLAN_NAME: 'plan-entities-dev-001'
SHARED_RG_NAME: 'gtc-rg-entities-dev-001'
STORAGE_NAME: 'stsubjectsdev001'
USER_IDENTITY: 'identity-subjects-dev-001'
WEB_NAME: 'web-subjects-dev-001'
WORKSPACE_NAME: 'work-entities-dev-001'

Expand Down Expand Up @@ -92,12 +92,13 @@ jobs:
template: ${{ env.ARM_PATH }}/api-apiapp.json
parameters: name=${{ env.API_NAME }} planName=${{ env.PLAN_NAME }} planResourceGroupName=${{ env.SHARED_RG_NAME }} appiKey=${{ secrets.APPI_KEY }} appiConnection=${{ secrets.APPI_CONNECTION }} rgEnvironment=${{ env.AZURE_RG_ENVIRONMENT }}

- name: Identity ${{ env.API_NAME }}
- name: Identity ${{ env.USER_IDENTITY }}
run: |
az identity create --resource-group ${{ env.AZURE_RG_NAME }} --name ${{ env.API_IDENTITY }}
az identity create --resource-group ${{ env.AZURE_RG_NAME }} --name ${{ env.USER_IDENTITY }}
# The following command requires Security Reader
$objectId = az ad sp list --display-name ${{ env.API_IDENTITY }} --query "[?displayName=='${{ env.API_IDENTITY }}'].id" --output tsv
$objectId = az ad sp list --display-name ${{ env.USER_IDENTITY }} --query "[?displayName=='${{ env.USER_IDENTITY }}'].id" --output tsv
az webapp identity assign --resource-group ${{ env.AZURE_RG_NAME }} --name ${{ env.API_NAME }} --identities $objectId
az webapp identity assign --resource-group ${{ env.AZURE_RG_NAME }} --name ${{ env.WEB_NAME }} --identities $objectId
az keyvault set-policy -n ${{ env.KEYVAULT_NAME }} -g ${{ env.AZURE_RG_NAME }} --object-id $objectId --secret-permissions get
shell: pwsh

Expand Down
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Subjects/Presentation/WebApi/bin/Debug/net7.0/Goodtocode.Subjects.WebApi.dll",
"program": "${workspaceFolder}/src/Subjects/Presentation.Api.WebApi/bin/Debug/net7.0/Goodtocode.Subjects.WebApi.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Subjects/Presentation/WebApi",
"cwd": "${workspaceFolder}/src/Subjects/Presentation.Api.WebApi",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
Expand Down
6 changes: 3 additions & 3 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/Subjects/Presentation/WebApi/WebApi.csproj",
"${workspaceFolder}/src/Subjects/Presentation.Api.WebApi/Presentation.Api.WebApi.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
Expand All @@ -23,7 +23,7 @@
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/Subjects/Presentation/WebApi/WebApi.csproj",
"${workspaceFolder}/src/Subjects/Presentation.Api.WebApi/Presentation.Api.WebApi.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
Expand All @@ -37,7 +37,7 @@
"watch",
"run",
"--project",
"${workspaceFolder}/src/Subjects/Presentation/WebApi/WebApi.csproj"
"${workspaceFolder}/src/Subjects/Presentation.Api.WebApi/Presentation.Api.WebApi.csproj"
],
"problemMatcher": "$msCompile"
}
Expand Down
13 changes: 13 additions & 0 deletions src/Subjects/Common.Persistence/Cache/CacheConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Goodtocode.Common.Persistence.Cache;

/// <summary>
/// "CacheConfiguration": {
/// "AbsoluteExpirationInHours": 1,
/// "SlidingExpirationInMinutes": 30
/// }
/// </summary>
public class CacheConfiguration
{
public int AbsoluteExpirationInHours { get; set; }
public int SlidingExpirationInMinutes { get; set; }
}
7 changes: 7 additions & 0 deletions src/Subjects/Common.Persistence/Cache/CacheTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Goodtocode.Common.Persistence.Cache;

public enum CacheTypes
{
Redis,
Memory
}
8 changes: 8 additions & 0 deletions src/Subjects/Common.Persistence/Cache/ICacheService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Goodtocode.Common.Persistence.Cache;

public interface ICacheService
{
bool TryGet<T>(string cacheKey, out T value);
T Set<T>(string cacheKey, T value);
void Remove(string cacheKey);
}
39 changes: 39 additions & 0 deletions src/Subjects/Common.Persistence/Cache/MemoryCacheService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;

namespace Goodtocode.Common.Persistence.Cache;

public class MemoryCacheService : ICacheService
{
private readonly IMemoryCache _cache;
private readonly CacheConfiguration _config;
private readonly MemoryCacheEntryOptions _options;
public MemoryCacheService(IMemoryCache memoryCache, IOptions<CacheConfiguration> cacheConfig)
{
_cache = memoryCache;
_config = cacheConfig.Value;
if (_config != null)
{
_options = new MemoryCacheEntryOptions
{
AbsoluteExpiration = DateTime.Now.AddHours(_config.AbsoluteExpirationInHours),
Priority = CacheItemPriority.High,
SlidingExpiration = TimeSpan.FromMinutes(_config.SlidingExpirationInMinutes)
};
}
}
public bool TryGet<T>(string cacheKey, out T value)
{
_cache.TryGetValue(cacheKey, out value);
if (value == null) return false;
else return true;
}
public T Set<T>(string cacheKey, T value)
{
return _cache.Set(cacheKey, value, _options);
}
public void Remove(string cacheKey)
{
_cache.Remove(cacheKey);
}
}
43 changes: 43 additions & 0 deletions src/Subjects/Common.Persistence/Cache/RedisCacheService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using System.Text.Json;

namespace Goodtocode.Common.Persistence.Cache;

public class RedisCacheService : ICacheService
{
private readonly IDistributedCache _cache;
private readonly CacheConfiguration _config;
private readonly DistributedCacheEntryOptions _options;

public RedisCacheService(IDistributedCache cache, IOptions<CacheConfiguration> cacheConfig)
{
_cache = cache;
_config = cacheConfig.Value;
if (_config != null)
{
_options = new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTime.Now.AddHours(_config.AbsoluteExpirationInHours),
SlidingExpiration = TimeSpan.FromMinutes(_config.SlidingExpirationInMinutes)
};
}
}

public bool TryGet<T>(string cacheKey, out T value)
{
var serialized = _cache.GetString(cacheKey);
value = JsonSerializer.Deserialize<T>(serialized);
if (value == null) return false;
else return true;
}
public T Set<T>(string cacheKey, T value)
{
_cache.SetString(cacheKey, JsonSerializer.Serialize(value), _options);
return value;
}
public void Remove(string cacheKey)
{
_cache.Remove(cacheKey);
}
}
20 changes: 20 additions & 0 deletions src/Subjects/Common.Persistence/Common.Persistence.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Goodtocode.Common.Persistence</RootNamespace>
<AssemblyName>Goodtocode.Common.Persistence</AssemblyName>
<Version>1.0.0</Version>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
</ItemGroup>
</Project>
29 changes: 29 additions & 0 deletions src/Subjects/Common.Persistence/ConfigureServices.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Goodtocode.Common.Persistence.Cache;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Goodtocode.Common.Persistence;

public static class ConfigureServices
{
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services,
IConfiguration configuration)
{
services.Configure<CacheConfiguration>(options => { configuration.GetSection("CacheConfiguration"); });
//For In-Memory Caching
services.AddMemoryCache();
services.AddTransient<MemoryCacheService>();
services.AddTransient<RedisCacheService>();
services.AddTransient<Func<CacheTypes, ICacheService>>(serviceProvider => key =>
{
return key switch
{
CacheTypes.Memory => serviceProvider.GetService<MemoryCacheService>(),
CacheTypes.Redis => serviceProvider.GetService<RedisCacheService>(),
_ => serviceProvider.GetService<MemoryCacheService>()
};
});

return services;
}
}
62 changes: 62 additions & 0 deletions src/Subjects/Common.Persistence/Repository/CachedRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Goodtocode.Common.Persistence.Cache;
using Microsoft.EntityFrameworkCore;

namespace Goodtocode.Common.Persistence.Repository;

public class CachedRepository<T> : ICachedRepository<T> where T : class
{
private readonly static CacheTypes CacheType = CacheTypes.Memory;
private readonly string cacheKey = $"{typeof(T)}";
private readonly DbContext _dbContext;
private readonly Func<CacheTypes, ICacheService> _cacheService;

public CachedRepository(DbContext dbContext, Func<CacheTypes, ICacheService> cacheService)
{
_dbContext = dbContext;
_cacheService = cacheService;
}

public virtual async Task<T> GetByIdAsync(int id)
{
return await _dbContext.Set<T>().FindAsync(id);
}

public async Task<IReadOnlyList<T>> GetAllAsync()
{
if (!_cacheService(CacheType).TryGet(cacheKey, out IReadOnlyList<T> cachedList))
{
cachedList = await _dbContext.Set<T>().ToListAsync();
_cacheService(CacheType).Set(cacheKey, cachedList);
}
return cachedList;
}

public async Task<T> AddAsync(T entity)
{
await _dbContext.Set<T>().AddAsync(entity);
await _dbContext.SaveChangesAsync();
await RefreshCache();
return entity;
}

public async Task UpdateAsync(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
await RefreshCache();
}

public async Task DeleteAsync(T entity)
{
_dbContext.Set<T>().Remove(entity);
await _dbContext.SaveChangesAsync();
await RefreshCache();
}

public async Task RefreshCache()
{
_cacheService(CacheType).Remove(cacheKey);
var cachedList = await _dbContext.Set<T>().ToListAsync();
_cacheService(CacheType).Set(cacheKey, cachedList);
}
}
10 changes: 10 additions & 0 deletions src/Subjects/Common.Persistence/Repository/ICachedRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Goodtocode.Common.Persistence.Repository;

public interface ICachedRepository<T> where T : class
{
Task<T> GetByIdAsync(int id);
Task<IReadOnlyList<T>> GetAllAsync();
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using RestSharp;
using Microsoft.IdentityModel.Tokens;
using RestSharp;
using System.Text.Json;

namespace Goodtocode.Common.Infrastructure.ApiClient;
Expand Down Expand Up @@ -53,15 +54,10 @@ private async Task<string> GetNewAccessToken()

var restClient = new RestClient(tokenUrl);
var response = await restClient.ExecuteAsync(request, CancellationToken.None);
if (!response.IsSuccessful) return string.Empty;
if (!response.IsSuccessful || string.IsNullOrEmpty(response.Content)) return string.Empty;
var tokenResponse = JsonSerializer.Deserialize<BearerToken>(response.Content);
if (tokenResponse == null) return string.Empty;
ExpirationDateUtc = DateTime.UtcNow.AddSeconds(tokenResponse.expires_in);
return tokenResponse.access_token;
}

private void SetAccessToken(string accessToken, DateTime expirationDate)
{
Token = accessToken;
ExpirationDateUtc = expirationDate;
}
}
13 changes: 13 additions & 0 deletions src/Subjects/Common/Common.ApiClient/BearerToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Goodtocode.Common.Infrastructure.ApiClient;

[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "RFC6749")]
public class BearerToken
{
public string token_type { get; set; } = string.Empty;

public int expires_in { get; set; }

public int ext_expires_in { get; set; }

public string access_token { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>Goodtocode.Common.Infrastructure.ApiClient</RootNamespace>
<AssemblyName>Goodtocode.Common.Infrastructure.ApiClient</AssemblyName>
<RootNamespace>Goodtocode.Common.ApiClient</RootNamespace>
<AssemblyName>Goodtocode.Common.ApiClient</AssemblyName>
<Version>1.0.0</Version>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.8" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.31.0" />
<PackageReference Include="RestSharp" Version="110.2.0" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="FluentValidation" Version="11.5.2" />
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions src/Subjects/Common/Common.Domain/Common.Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Common.Extensions\Common.Extensions.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit 74d2f0e

Please sign in to comment.