From 6ba7275e9299951badb785bd4a558ad99ce5dc46 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 20 Jan 2025 18:52:04 +0100 Subject: [PATCH] Added api endpoints and configured hopframe --- .../Controller/AboutController.cs | 15 ++++++ .../Controller/ProjectController.cs | 15 ++++++ .../Controller/TechnologyController.cs | 15 ++++++ .../Controller/TimelineController.cs | 16 ++++++ src/Portfolio.Api/DatabaseContext.cs | 2 +- src/Portfolio.Api/Program.cs | 50 ++++++++++++++++++- src/Portfolio.Api/Services/AboutRepository.cs | 14 ++++++ .../Services/ProjectRepository.cs | 15 ++++++ .../Services/TechnologyRepository.cs | 13 +++++ .../Services/TimelineRepository.cs | 15 ++++++ src/Portfolio.Shared/Models/About.cs | 2 +- src/Portfolio.Shared/Models/TimelineEntry.cs | 2 +- .../Services/IAboutRepository.cs | 9 ++++ .../Services/IProjectRepository.cs | 9 ++++ .../Services/ITechnologyRepository.cs | 9 ++++ .../Services/ITimelineRepository.cs | 9 ++++ 16 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 src/Portfolio.Api/Controller/AboutController.cs create mode 100644 src/Portfolio.Api/Controller/ProjectController.cs create mode 100644 src/Portfolio.Api/Controller/TechnologyController.cs create mode 100644 src/Portfolio.Api/Controller/TimelineController.cs create mode 100644 src/Portfolio.Api/Services/AboutRepository.cs create mode 100644 src/Portfolio.Api/Services/ProjectRepository.cs create mode 100644 src/Portfolio.Api/Services/TechnologyRepository.cs create mode 100644 src/Portfolio.Api/Services/TimelineRepository.cs create mode 100644 src/Portfolio.Shared/Services/IAboutRepository.cs create mode 100644 src/Portfolio.Shared/Services/IProjectRepository.cs create mode 100644 src/Portfolio.Shared/Services/ITechnologyRepository.cs create mode 100644 src/Portfolio.Shared/Services/ITimelineRepository.cs diff --git a/src/Portfolio.Api/Controller/AboutController.cs b/src/Portfolio.Api/Controller/AboutController.cs new file mode 100644 index 0000000..8a8f2b4 --- /dev/null +++ b/src/Portfolio.Api/Controller/AboutController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; +using Portfolio.Shared.Services; + +namespace Portfolio.Api.Controller; + +[ApiController, Route("api/about")] +public class AboutController(IAboutRepository repository) : ControllerBase { + + [HttpGet] + public async Task GetAbout(CancellationToken ct) { + var about = await repository.GetAbout(ct); + return Ok(about); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Api/Controller/ProjectController.cs b/src/Portfolio.Api/Controller/ProjectController.cs new file mode 100644 index 0000000..139228e --- /dev/null +++ b/src/Portfolio.Api/Controller/ProjectController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; +using Portfolio.Shared.Services; + +namespace Portfolio.Api.Controller; + +[ApiController, Route("api/projects")] +public class ProjectController(IProjectRepository repository) : ControllerBase { + + [HttpGet] + public async Task GetProjects(CancellationToken ct) { + var projects = await repository.GetProjects(ct); + return Ok(projects); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Api/Controller/TechnologyController.cs b/src/Portfolio.Api/Controller/TechnologyController.cs new file mode 100644 index 0000000..40de333 --- /dev/null +++ b/src/Portfolio.Api/Controller/TechnologyController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; +using Portfolio.Shared.Services; + +namespace Portfolio.Api.Controller; + +[ApiController, Route("api/technologies")] +public class TechnologyController(ITechnologyRepository repository) : ControllerBase { + + [HttpGet] + public async Task GetTechnologies(CancellationToken ct) { + var technologies = await repository.GetTechnologies(ct); + return Ok(technologies); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Api/Controller/TimelineController.cs b/src/Portfolio.Api/Controller/TimelineController.cs new file mode 100644 index 0000000..4a405ce --- /dev/null +++ b/src/Portfolio.Api/Controller/TimelineController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; +using Portfolio.Shared.Models; +using Portfolio.Shared.Services; + +namespace Portfolio.Api.Controller; + +[ApiController, Route("api/timeline")] +public class TimelineController(ITimelineRepository repository) : ControllerBase { + + [HttpGet("{type}")] + public async Task GetTimeline(TimelineEntryType type, CancellationToken ct) { + var timeline = await repository.GetTimeline(type, ct); + return Ok(timeline); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Api/DatabaseContext.cs b/src/Portfolio.Api/DatabaseContext.cs index 2d46736..aaa7b5c 100644 --- a/src/Portfolio.Api/DatabaseContext.cs +++ b/src/Portfolio.Api/DatabaseContext.cs @@ -19,7 +19,7 @@ public class DatabaseContext(DbContextOptions options) : DbCont modelBuilder.Entity() .HasMany(p => p.Languages) .WithMany() - .UsingEntity("ProjectLanguage"); + .UsingEntity("LanguageProject"); } } \ No newline at end of file diff --git a/src/Portfolio.Api/Program.cs b/src/Portfolio.Api/Program.cs index 39a8398..e093629 100644 --- a/src/Portfolio.Api/Program.cs +++ b/src/Portfolio.Api/Program.cs @@ -1,5 +1,9 @@ +using HopFrame.Core.Config; using HopFrame.Web; using Portfolio.Api; +using Portfolio.Api.Services; +using Portfolio.Shared.Models; +using Portfolio.Shared.Services; var builder = WebApplication.CreateBuilder(args); @@ -8,11 +12,53 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); builder.AddServiceDefaults(); +AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); builder.AddNpgsqlDbContext("data"); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddControllers(); + builder.Services.AddHopFrame(options => { options.DisplayUserInfo(false); - options.AddDbContext(); + options.AddDbContext(context => { + context.Table(table => { + var langConfig = table.InnerConfig.Properties + .Single(prop => prop.Name == nameof(Project.Languages)); + + langConfig + .GetType()! + .GetProperty(nameof(PropertyConfig.IsRelation))! + .SetValue(langConfig, true); + + langConfig + .GetType()! + .GetProperty(nameof(PropertyConfig.IsEnumerable))! + .SetValue(langConfig, true); + + langConfig + .GetType()! + .GetProperty(nameof(PropertyConfig.IsRequired))! + .SetValue(langConfig, true); + + table.Property(p => p.Languages) + .FormatEach((l, _) => l.Label) + .List(false); + + table.Property(p => p.Cover) + .List(false); + + table.Property(p => p.Description) + .List(false) + .IsTextArea(true); + + table.Property(p => p.SourceCode) + .List(false); + }); + }); }); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); @@ -32,6 +78,8 @@ await using (var scope = app.Services.CreateAsyncScope()) { app.UseHttpsRedirection(); app.MapDefaultEndpoints(); +app.MapControllers(); + app.UseAntiforgery(); app.MapStaticAssets(); app.MapRazorComponents() diff --git a/src/Portfolio.Api/Services/AboutRepository.cs b/src/Portfolio.Api/Services/AboutRepository.cs new file mode 100644 index 0000000..c61a9ef --- /dev/null +++ b/src/Portfolio.Api/Services/AboutRepository.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Portfolio.Shared.Models; +using Portfolio.Shared.Services; + +namespace Portfolio.Api.Services; + +internal sealed class AboutRepository(DatabaseContext context) : IAboutRepository { + + public async Task GetAbout(CancellationToken ct) { + return await context.About + .SingleAsync(ct); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Api/Services/ProjectRepository.cs b/src/Portfolio.Api/Services/ProjectRepository.cs new file mode 100644 index 0000000..836afd9 --- /dev/null +++ b/src/Portfolio.Api/Services/ProjectRepository.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; +using Portfolio.Shared.Models; +using Portfolio.Shared.Services; + +namespace Portfolio.Api.Services; + +internal sealed class ProjectRepository(DatabaseContext context) : IProjectRepository { + + public async Task> GetProjects(CancellationToken ct) { + return await context.Projects + .Include(p => p.Languages) + .ToArrayAsync(ct); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Api/Services/TechnologyRepository.cs b/src/Portfolio.Api/Services/TechnologyRepository.cs new file mode 100644 index 0000000..9f599ef --- /dev/null +++ b/src/Portfolio.Api/Services/TechnologyRepository.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; +using Portfolio.Shared.Models; +using Portfolio.Shared.Services; + +namespace Portfolio.Api.Services; + +internal sealed class TechnologyRepository(DatabaseContext context) : ITechnologyRepository { + + public async Task> GetTechnologies(CancellationToken ct) { + return await context.Technologies.ToArrayAsync(ct); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Api/Services/TimelineRepository.cs b/src/Portfolio.Api/Services/TimelineRepository.cs new file mode 100644 index 0000000..2a8428c --- /dev/null +++ b/src/Portfolio.Api/Services/TimelineRepository.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; +using Portfolio.Shared.Models; +using Portfolio.Shared.Services; + +namespace Portfolio.Api.Services; + +internal sealed class TimelineRepository(DatabaseContext context) : ITimelineRepository { + + public async Task> GetTimeline(TimelineEntryType type, CancellationToken ct) { + return await context.Timeline + .Where(entry => entry.Type == type) + .ToArrayAsync(ct); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Shared/Models/About.cs b/src/Portfolio.Shared/Models/About.cs index b17cc31..baaabe8 100644 --- a/src/Portfolio.Shared/Models/About.cs +++ b/src/Portfolio.Shared/Models/About.cs @@ -2,7 +2,7 @@ namespace Portfolio.Shared.Models; -public class About { +public sealed class About { [Key] public int Id { get; private set; } = 0; diff --git a/src/Portfolio.Shared/Models/TimelineEntry.cs b/src/Portfolio.Shared/Models/TimelineEntry.cs index f6cb8fa..4c25ca1 100644 --- a/src/Portfolio.Shared/Models/TimelineEntry.cs +++ b/src/Portfolio.Shared/Models/TimelineEntry.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Portfolio.Shared.Models; -public class TimelineEntry { +public sealed class TimelineEntry { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; init; } diff --git a/src/Portfolio.Shared/Services/IAboutRepository.cs b/src/Portfolio.Shared/Services/IAboutRepository.cs new file mode 100644 index 0000000..8202a30 --- /dev/null +++ b/src/Portfolio.Shared/Services/IAboutRepository.cs @@ -0,0 +1,9 @@ +using Portfolio.Shared.Models; + +namespace Portfolio.Shared.Services; + +public interface IAboutRepository { + + Task GetAbout(CancellationToken ct); + +} \ No newline at end of file diff --git a/src/Portfolio.Shared/Services/IProjectRepository.cs b/src/Portfolio.Shared/Services/IProjectRepository.cs new file mode 100644 index 0000000..11b17d1 --- /dev/null +++ b/src/Portfolio.Shared/Services/IProjectRepository.cs @@ -0,0 +1,9 @@ +using Portfolio.Shared.Models; + +namespace Portfolio.Shared.Services; + +public interface IProjectRepository { + + Task> GetProjects(CancellationToken ct); + +} \ No newline at end of file diff --git a/src/Portfolio.Shared/Services/ITechnologyRepository.cs b/src/Portfolio.Shared/Services/ITechnologyRepository.cs new file mode 100644 index 0000000..fb34e75 --- /dev/null +++ b/src/Portfolio.Shared/Services/ITechnologyRepository.cs @@ -0,0 +1,9 @@ +using Portfolio.Shared.Models; + +namespace Portfolio.Shared.Services; + +public interface ITechnologyRepository { + + Task> GetTechnologies(CancellationToken ct); + +} \ No newline at end of file diff --git a/src/Portfolio.Shared/Services/ITimelineRepository.cs b/src/Portfolio.Shared/Services/ITimelineRepository.cs new file mode 100644 index 0000000..561f075 --- /dev/null +++ b/src/Portfolio.Shared/Services/ITimelineRepository.cs @@ -0,0 +1,9 @@ +using Portfolio.Shared.Models; + +namespace Portfolio.Shared.Services; + +public interface ITimelineRepository { + + Task> GetTimeline(TimelineEntryType type, CancellationToken ct); + +} \ No newline at end of file