Merge branch 'feature/caching' into 'dev'
Resolve "Caching" Closes #13 See merge request leon.hoppe/Portfolio!9
This commit was merged in pull request #25.
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.OutputCaching;
|
||||||
using Portfolio.Shared.Services;
|
using Portfolio.Shared.Services;
|
||||||
|
|
||||||
namespace Portfolio.Api.Controller;
|
namespace Portfolio.Api.Controller;
|
||||||
|
|
||||||
[ApiController, Route("api/about")]
|
[ApiController, Route("api/about"), OutputCache(Tags = [DatabaseContext.CacheKey])]
|
||||||
public class AboutController(IAboutRepository repository) : ControllerBase {
|
public class AboutController(IAboutRepository repository) : ControllerBase {
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.OutputCaching;
|
||||||
using Portfolio.Shared.Services;
|
using Portfolio.Shared.Services;
|
||||||
|
|
||||||
namespace Portfolio.Api.Controller;
|
namespace Portfolio.Api.Controller;
|
||||||
|
|
||||||
[ApiController, Route("api/projects")]
|
[ApiController, Route("api/projects"), OutputCache(Tags = [DatabaseContext.CacheKey])]
|
||||||
public class ProjectController(IProjectRepository repository) : ControllerBase {
|
public class ProjectController(IProjectRepository repository) : ControllerBase {
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.OutputCaching;
|
||||||
using Portfolio.Shared.Services;
|
using Portfolio.Shared.Services;
|
||||||
|
|
||||||
namespace Portfolio.Api.Controller;
|
namespace Portfolio.Api.Controller;
|
||||||
|
|
||||||
[ApiController, Route("api/technologies")]
|
[ApiController, Route("api/technologies"), OutputCache(Tags = [DatabaseContext.CacheKey])]
|
||||||
public class TechnologyController(ITechnologyRepository repository) : ControllerBase {
|
public class TechnologyController(ITechnologyRepository repository) : ControllerBase {
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.OutputCaching;
|
||||||
using Portfolio.Shared.Models;
|
using Portfolio.Shared.Models;
|
||||||
using Portfolio.Shared.Services;
|
using Portfolio.Shared.Services;
|
||||||
|
|
||||||
namespace Portfolio.Api.Controller;
|
namespace Portfolio.Api.Controller;
|
||||||
|
|
||||||
[ApiController, Route("api/timeline")]
|
[ApiController, Route("api/timeline"), OutputCache(Tags = [DatabaseContext.CacheKey])]
|
||||||
public class TimelineController(ITimelineRepository repository) : ControllerBase {
|
public class TimelineController(ITimelineRepository repository) : ControllerBase {
|
||||||
|
|
||||||
[HttpGet("{type}")]
|
[HttpGet("{type}")]
|
||||||
@@ -13,4 +14,10 @@ public class TimelineController(ITimelineRepository repository) : ControllerBase
|
|||||||
return Ok(timeline);
|
return Ok(timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("featured")]
|
||||||
|
public async Task<IActionResult> GetFeaturedTimeline(CancellationToken ct) {
|
||||||
|
var timeline = await repository.GetFeaturedTimeline(ct);
|
||||||
|
return Ok(timeline);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.AspNetCore.OutputCaching;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Portfolio.Shared.Models;
|
using Portfolio.Shared.Models;
|
||||||
|
|
||||||
namespace Portfolio.Api;
|
namespace Portfolio.Api;
|
||||||
|
|
||||||
public class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbContext(options) {
|
public class DatabaseContext(DbContextOptions<DatabaseContext> options, IOutputCacheStore cacheStore) : DbContext(options) {
|
||||||
|
|
||||||
|
public const string CacheKey = "portfolio-data";
|
||||||
|
|
||||||
public DbSet<Project> Projects { get; set; }
|
public DbSet<Project> Projects { get; set; }
|
||||||
|
|
||||||
@@ -20,4 +23,8 @@ public class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbCont
|
|||||||
.UsingEntity("LanguageProject");
|
.UsingEntity("LanguageProject");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new()) {
|
||||||
|
await cacheStore.EvictByTagAsync(CacheKey, cancellationToken);
|
||||||
|
return await base.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0" />
|
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Aspire.StackExchange.Redis.OutputCaching" Version="9.0.0" />
|
||||||
<PackageReference Include="HopFrame.Web" Version="3.0.0" />
|
<PackageReference Include="HopFrame.Web" Version="3.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0"/>
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using HopFrame.Core.Config;
|
using HopFrame.Core.Config;
|
||||||
using HopFrame.Web;
|
using HopFrame.Web;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Portfolio.Api;
|
using Portfolio.Api;
|
||||||
using Portfolio.Api.Services;
|
using Portfolio.Api.Services;
|
||||||
using Portfolio.Shared.Models;
|
using Portfolio.Shared.Models;
|
||||||
@@ -23,6 +22,11 @@ builder.Services.AddScoped<IAboutRepository, AboutRepository>();
|
|||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
|
builder.AddRedisOutputCache("cache");
|
||||||
|
builder.Services.AddOutputCache(options => {
|
||||||
|
options.DefaultExpirationTimeSpan = TimeSpan.FromDays(30);
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.AddHopFrame(options => {
|
builder.Services.AddHopFrame(options => {
|
||||||
options.DisplayUserInfo(false);
|
options.DisplayUserInfo(false);
|
||||||
options.AddDbContext<DatabaseContext>(context => {
|
options.AddDbContext<DatabaseContext>(context => {
|
||||||
@@ -94,6 +98,8 @@ await using (var scope = app.Services.CreateAsyncScope()) {
|
|||||||
|
|
||||||
app.MapDefaultEndpoints();
|
app.MapDefaultEndpoints();
|
||||||
|
|
||||||
|
app.UseOutputCache();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.UseAntiforgery();
|
app.UseAntiforgery();
|
||||||
|
|||||||
@@ -12,4 +12,9 @@ internal sealed class TimelineRepository(DatabaseContext context) : ITimelineRep
|
|||||||
.ToArrayAsync(ct);
|
.ToArrayAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TimelineEntry>> GetFeaturedTimeline(CancellationToken ct) {
|
||||||
|
return await context.Timeline
|
||||||
|
.Where(entry => entry.Featured)
|
||||||
|
.ToArrayAsync(ct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0"/>
|
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0"/>
|
||||||
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.0.0" />
|
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Aspire.Hosting.Redis" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -5,13 +5,21 @@ var postgres = builder.AddPostgres("postgres")
|
|||||||
|
|
||||||
var db = postgres.AddDatabase("data");
|
var db = postgres.AddDatabase("data");
|
||||||
|
|
||||||
|
var cache = builder.AddRedis("cache");
|
||||||
|
|
||||||
var api = builder.AddProject<Projects.Portfolio_Api>("api")
|
var api = builder.AddProject<Projects.Portfolio_Api>("api")
|
||||||
.WithReference(db)
|
.WithReference(db)
|
||||||
.WaitFor(db);
|
.WaitFor(db)
|
||||||
|
.WithReference(cache)
|
||||||
|
.WaitFor(cache);
|
||||||
|
|
||||||
builder.AddProject<Projects.Portfolio_Web>("web")
|
builder.AddProject<Projects.Portfolio_Web>("web")
|
||||||
.WithReference(api)
|
.WithReference(api)
|
||||||
.WaitFor(api)
|
.WaitFor(api)
|
||||||
.WithExternalHttpEndpoints();
|
.WithExternalHttpEndpoints();
|
||||||
|
|
||||||
|
builder.AddContainer("cdn", "nginx")
|
||||||
|
.WithVolume("portfolio-cdn", "/usr/share/nginx/html", true)
|
||||||
|
.WithHttpEndpoint(80, 80);
|
||||||
|
|
||||||
builder.Build().Run();
|
builder.Build().Run();
|
||||||
@@ -6,4 +6,6 @@ public interface ITimelineRepository {
|
|||||||
|
|
||||||
Task<IEnumerable<TimelineEntry>> GetTimeline(TimelineEntryType type, CancellationToken ct);
|
Task<IEnumerable<TimelineEntry>> GetTimeline(TimelineEntryType type, CancellationToken ct);
|
||||||
|
|
||||||
|
Task<IEnumerable<TimelineEntry>> GetFeaturedTimeline(CancellationToken ct);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -104,12 +104,7 @@
|
|||||||
var technologies = await TechnologyRepository.GetTechnologies(TokenSource.Token);
|
var technologies = await TechnologyRepository.GetTechnologies(TokenSource.Token);
|
||||||
_technologies = technologies.Where(t => t.Featured);
|
_technologies = technologies.Where(t => t.Featured);
|
||||||
|
|
||||||
var carrierTimeline = await TimelineRepository.GetTimeline(TimelineEntryType.Carrier, TokenSource.Token);
|
_timeline = await TimelineRepository.GetFeaturedTimeline(TokenSource.Token);
|
||||||
var experienceTimeline = await TimelineRepository.GetTimeline(TimelineEntryType.Experience, TokenSource.Token);
|
|
||||||
_timeline = experienceTimeline
|
|
||||||
.Aggregate(carrierTimeline, (current, entry) => current.Append(entry))
|
|
||||||
.Where(t => t.Featured)
|
|
||||||
.OrderBy(t => t.Date);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,13 @@ internal sealed class TimelineRepository(IHttpClientFactory factory) : ITimeline
|
|||||||
var data = await result.Content.ReadFromJsonAsync<IEnumerable<TimelineEntry>>(ct);
|
var data = await result.Content.ReadFromJsonAsync<IEnumerable<TimelineEntry>>(ct);
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TimelineEntry>> GetFeaturedTimeline(CancellationToken ct) {
|
||||||
|
var client = factory.CreateClient("api");
|
||||||
|
var result = await client.GetAsync("api/timeline/featured", ct);
|
||||||
|
if (!result.IsSuccessStatusCode) return [];
|
||||||
|
|
||||||
|
var data = await result.Content.ReadFromJsonAsync<IEnumerable<TimelineEntry>>(ct);
|
||||||
|
return data ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user