13 Commits

Author SHA1 Message Date
a4d52349c7 added multithreaded api calling on about page
All checks were successful
Portfolio CI/CD / build (push) Successful in 1m5s
Portfolio CI/CD / test (push) Successful in 1m24s
Portfolio CI/CD / publish (push) Successful in 1m16s
2026-02-22 12:25:54 +01:00
aac2f5180b added multithreaded api calling on homepage
All checks were successful
Portfolio CI/CD / build (push) Successful in 1m8s
Portfolio CI/CD / test (push) Successful in 1m51s
Portfolio CI/CD / publish (push) Has been skipped
2026-02-22 12:22:20 +01:00
53bd0e107e removed gitlab ci
All checks were successful
Portfolio CI/CD / build (push) Successful in 57s
Portfolio CI/CD / test (push) Successful in 1m18s
Portfolio CI/CD / publish (push) Has been skipped
2026-02-20 23:14:33 +01:00
c85a6ec46f restored files from lfs 2026-02-20 23:12:57 +01:00
6b6a1bce7e Removed lfs
All checks were successful
Portfolio CI/CD / build (push) Successful in 1m16s
Portfolio CI/CD / test (push) Successful in 1m20s
Portfolio CI/CD / publish (push) Successful in 2m56s
2026-02-20 22:58:47 +01:00
2634daf800 Fixed pipeline
Some checks failed
Portfolio CI/CD / build (push) Successful in 1m8s
Portfolio CI/CD / test (push) Successful in 1m23s
Portfolio CI/CD / publish (push) Failing after 41s
2026-02-20 22:47:54 +01:00
eeb6e5de6a Added ordering for timeline entries
Some checks failed
Portfolio CI/CD / build (push) Failing after 17s
Portfolio CI/CD / test (push) Has been skipped
Portfolio CI/CD / publish (push) Has been skipped
2026-02-20 22:43:54 +01:00
2a9cb2b2fb Changed project ordering on frontend 2025-12-15 15:48:27 +01:00
ef9a825dc2 Fixed docker file 2025-12-14 20:05:50 +01:00
8b4792cb5c Updated docker files 2025-12-14 19:59:03 +01:00
8a16658dfe Partially updated to .net 10 2025-12-14 19:51:36 +01:00
0fa955f794 Updated project repo to use correct ordering 2025-12-13 11:01:43 +01:00
a2f378708f updated ci to new standart 2025-12-13 09:58:50 +01:00
24 changed files with 161 additions and 94 deletions

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
*.png filter=lfs diff=lfs merge=lfs -text

65
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,65 @@
name: Portfolio CI/CD
on:
push:
branches: [ "*" ]
tags: [ "*" ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.x'
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: dotnet restore
- name: Build Projects
run: dotnet build --configuration Release --no-restore
test:
runs-on: ubuntu-latest
needs: build
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.x'
- name: Checkout
uses: actions/checkout@v4
- name: Test
run: dotnet test --verbosity normal
publish:
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.leon-hoppe.de \
-u leon.hoppe --password-stdin
- name: Build backend image
run: docker build -t git.leon-hoppe.de/leon.hoppe/portfolio/backend:latest -f src/Portfolio.Api/Dockerfile .
- name: Build frontend image
run: docker build -t git.leon-hoppe.de/leon.hoppe/portfolio/frontend:latest -f src/Portfolio.Web/Dockerfile .
- name: Push backend image
run: docker push git.leon-hoppe.de/leon.hoppe/portfolio/backend:latest
- name: Push frontend image
run: docker push git.leon-hoppe.de/leon.hoppe/portfolio/frontend:latest

View File

@@ -1,44 +0,0 @@
stages:
- build
- test
- publish
variables:
DOCKER_IMAGE: registry.leon-hoppe.de/leon.hoppe/portfolio
build:
stage: build
image: mcr.microsoft.com/dotnet/sdk:9.0
script:
- dotnet restore
- dotnet build --configuration Release --no-restore
artifacts:
paths:
- "**/bin/Release"
expire_in: 10 minutes
test:
stage: test
image: mcr.microsoft.com/dotnet/sdk:9.0
script:
- dotnet test --verbosity normal
dependencies:
- build
publish:
stage: publish
image: docker:latest
services:
- name: docker:dind
alias: docker
script:
- export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//')
- docker login -u leon.hoppe -p ${CI_REGISTRY_PASSWORD} registry.leon-hoppe.de
- docker build -t $DOCKER_IMAGE/api:$VERSION -t $DOCKER_IMAGE/api:latest -f src/Portfolio.Api/Dockerfile .
- docker build -t $DOCKER_IMAGE/web:$VERSION -t $DOCKER_IMAGE/web:latest -f src/Portfolio.Web/Dockerfile .
- docker push $DOCKER_IMAGE/api:$VERSION
- docker push $DOCKER_IMAGE/web:$VERSION
- docker push $DOCKER_IMAGE/api:latest
- docker push $DOCKER_IMAGE/web:latest
only:
- tags

7
global.json Normal file
View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "10.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}

View File

@@ -12,5 +12,11 @@ public class ProjectController(IProjectRepository repository) : ControllerBase {
var projects = await repository.GetProjects(ct); var projects = await repository.GetProjects(ct);
return Ok(projects); return Ok(projects);
} }
[HttpGet("featured")]
public async Task<IActionResult> GetFeaturedProjects(CancellationToken ct) {
var projects = await repository.GetFeaturedProjects(ct);
return Ok(projects);
}
} }

View File

@@ -12,5 +12,11 @@ public class TechnologyController(ITechnologyRepository repository) : Controller
var technologies = await repository.GetTechnologies(ct); var technologies = await repository.GetTechnologies(ct);
return Ok(technologies); return Ok(technologies);
} }
[HttpGet("featured")]
public async Task<IActionResult> GetFeaturedTechnologies(CancellationToken ct) {
var technologies = await repository.GetFeaturedTechnologies(ct);
return Ok(technologies);
}
} }

View File

@@ -8,10 +8,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0" /> <PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="13.0.2" />
<PackageReference Include="Aspire.StackExchange.Redis.OutputCaching" Version="9.0.0" /> <PackageReference Include="Aspire.StackExchange.Redis.OutputCaching" Version="13.0.2" />
<PackageReference Include="HopFrame.Web" Version="3.1.0" /> <PackageReference Include="HopFrame.Web" Version="3.3.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0"/> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,4 +1,3 @@
using HopFrame.Core.Config;
using HopFrame.Web; using HopFrame.Web;
using Portfolio.Api; using Portfolio.Api;
using Portfolio.Api.Services; using Portfolio.Api.Services;
@@ -31,25 +30,8 @@ builder.Services.AddHopFrame(options => {
options.DisplayUserInfo(false); options.DisplayUserInfo(false);
options.AddDbContext<DatabaseContext>(context => { options.AddDbContext<DatabaseContext>(context => {
context.Table<Project>(table => { context.Table<Project>(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) table.Property(p => p.Languages)
.ForceRelation(isEnumerable: true)
.FormatEach<Technology>((l, _) => l.Name) .FormatEach<Technology>((l, _) => l.Name)
.List(false); .List(false);
@@ -78,6 +60,8 @@ builder.Services.AddHopFrame(options => {
.IsTextArea(true); .IsTextArea(true);
}); });
}); });
options.AddExporters();
}); });
var app = builder.Build(); var app = builder.Build();

View File

@@ -9,6 +9,15 @@ internal sealed class ProjectRepository(DatabaseContext context) : IProjectRepos
public async Task<IEnumerable<Project>> GetProjects(CancellationToken ct) { public async Task<IEnumerable<Project>> GetProjects(CancellationToken ct) {
return await context.Projects return await context.Projects
.Include(p => p.Languages) .Include(p => p.Languages)
.OrderByDescending(p => p.OrderIndex)
.ToArrayAsync(ct);
}
public async Task<IEnumerable<Project>> GetFeaturedProjects(CancellationToken ct) {
return await context.Projects
.Include(p => p.Languages)
.Where(p => p.Featured)
.OrderByDescending(p => p.OrderIndex)
.ToArrayAsync(ct); .ToArrayAsync(ct);
} }

View File

@@ -9,5 +9,11 @@ internal sealed class TechnologyRepository(DatabaseContext context) : ITechnolog
public async Task<IEnumerable<Technology>> GetTechnologies(CancellationToken ct) { public async Task<IEnumerable<Technology>> GetTechnologies(CancellationToken ct) {
return await context.Technologies.ToArrayAsync(ct); return await context.Technologies.ToArrayAsync(ct);
} }
public async Task<IEnumerable<Technology>> GetFeaturedTechnologies(CancellationToken ct) {
return await context.Technologies
.Where(t => t.Featured)
.ToArrayAsync(ct);
}
} }

View File

@@ -4,7 +4,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost> <IsAspireHost>true</IsAspireHost>
@@ -12,9 +12,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0"/> <PackageReference Include="Aspire.Hosting.AppHost" Version="13.0.2" />
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.0.0" /> <PackageReference Include="Aspire.Hosting.PostgreSQL" Version="13.0.2" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.0.0" /> <PackageReference Include="Aspire.Hosting.Redis" Version="13.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -10,13 +10,13 @@
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"/> <FrameworkReference Include="Microsoft.AspNetCore.App"/>
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0"/> <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.1.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.0.0"/> <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.1.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0"/> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0"/> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0"/> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0"/> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0"/> <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.14.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -6,4 +6,6 @@ public interface IProjectRepository {
Task<IEnumerable<Project>> GetProjects(CancellationToken ct); Task<IEnumerable<Project>> GetProjects(CancellationToken ct);
Task<IEnumerable<Project>> GetFeaturedProjects(CancellationToken ct);
} }

View File

@@ -6,4 +6,6 @@ public interface ITechnologyRepository {
Task<IEnumerable<Technology>> GetTechnologies(CancellationToken ct); Task<IEnumerable<Technology>> GetTechnologies(CancellationToken ct);
Task<IEnumerable<Technology>> GetFeaturedTechnologies(CancellationToken ct);
} }

View File

@@ -53,9 +53,15 @@
}; };
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
_about = await AboutRepository.GetAbout(TokenSource.Token); var aboutTask = AboutRepository.GetAbout(TokenSource.Token);
_experience = await TimelineRepository.GetTimeline(TimelineEntryType.Experience, TokenSource.Token); var experienceTask = TimelineRepository.GetTimeline(TimelineEntryType.Experience, TokenSource.Token);
_carrier = await TimelineRepository.GetTimeline(TimelineEntryType.Carrier, TokenSource.Token); var carrierTask = TimelineRepository.GetTimeline(TimelineEntryType.Carrier, TokenSource.Token);
await Task.WhenAll(aboutTask, experienceTask, carrierTask);
_about = aboutTask.Result;
_experience = experienceTask.Result;
_carrier = carrierTask.Result;
} }
} }

View File

@@ -82,7 +82,6 @@
if (displayElement.innerText !== jobs.display) if (displayElement.innerText !== jobs.display)
displayElement.innerText = jobs.display; displayElement.innerText = jobs.display;
}, 50) }, 50)
</script> </script>
@inherits CancellableComponent @inherits CancellableComponent
@@ -98,13 +97,15 @@
private IEnumerable<TimelineEntry> _timeline = []; private IEnumerable<TimelineEntry> _timeline = [];
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
var projects = await ProjectRepository.GetProjects(TokenSource.Token); var projectsTask = ProjectRepository.GetFeaturedProjects(TokenSource.Token);
_projects = projects.Where(p => p.Featured); var technologiesTask = TechnologyRepository.GetFeaturedTechnologies(TokenSource.Token);
var timelineTask = TimelineRepository.GetFeaturedTimeline(TokenSource.Token);
var technologies = await TechnologyRepository.GetTechnologies(TokenSource.Token); await Task.WhenAll(projectsTask, technologiesTask, timelineTask);
_technologies = technologies.Where(t => t.Featured);
_timeline = await TimelineRepository.GetFeaturedTimeline(TokenSource.Token); _projects = projectsTask.Result;
_technologies = technologiesTask.Result;
_timeline = timelineTask.Result;
} }
} }

View File

@@ -1,10 +1,10 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
USER $APP_UID USER $APP_UID
WORKDIR /app WORKDIR /app
EXPOSE 8080 EXPOSE 8080
EXPOSE 8081 EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release ARG BUILD_CONFIGURATION=Release
WORKDIR /src WORKDIR /src

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>

View File

@@ -10,6 +10,15 @@ internal sealed class ProjectRepository(IHttpClientFactory factory) : IProjectRe
if (!response.IsSuccessStatusCode) return []; if (!response.IsSuccessStatusCode) return [];
var data = await response.Content.ReadFromJsonAsync<IEnumerable<Project>>(ct); var data = await response.Content.ReadFromJsonAsync<IEnumerable<Project>>(ct);
return data ?? []; return data?.OrderByDescending(p => p.OrderIndex) ?? Enumerable.Empty<Project>();
}
public async Task<IEnumerable<Project>> GetFeaturedProjects(CancellationToken ct) {
var client = factory.CreateClient("api");
var response = await client.GetAsync("api/projects/featured", ct);
if (!response.IsSuccessStatusCode) return [];
var data = await response.Content.ReadFromJsonAsync<IEnumerable<Project>>(ct);
return data?.OrderByDescending(p => p.OrderIndex) ?? Enumerable.Empty<Project>();
} }
} }

View File

@@ -12,4 +12,13 @@ internal sealed class TechnologyRepository(IHttpClientFactory factory) : ITechno
var data = await response.Content.ReadFromJsonAsync<IEnumerable<Technology>>(ct); var data = await response.Content.ReadFromJsonAsync<IEnumerable<Technology>>(ct);
return data ?? []; return data ?? [];
} }
public async Task<IEnumerable<Technology>> GetFeaturedTechnologies(CancellationToken ct) {
var client = factory.CreateClient("api");
var response = await client.GetAsync("api/technologies/featured", ct);
if (!response.IsSuccessStatusCode) return [];
var data = await response.Content.ReadFromJsonAsync<IEnumerable<Technology>>(ct);
return data ?? [];
}
} }

View File

@@ -10,7 +10,7 @@ internal sealed class TimelineRepository(IHttpClientFactory factory) : ITimeline
if (!result.IsSuccessStatusCode) return []; if (!result.IsSuccessStatusCode) return [];
var data = await result.Content.ReadFromJsonAsync<IEnumerable<TimelineEntry>>(ct); var data = await result.Content.ReadFromJsonAsync<IEnumerable<TimelineEntry>>(ct);
return data ?? []; return data?.OrderBy(t => t.Date) ?? Enumerable.Empty<TimelineEntry>();
} }
public async Task<IEnumerable<TimelineEntry>> GetFeaturedTimeline(CancellationToken ct) { public async Task<IEnumerable<TimelineEntry>> GetFeaturedTimeline(CancellationToken ct) {
@@ -19,6 +19,6 @@ internal sealed class TimelineRepository(IHttpClientFactory factory) : ITimeline
if (!result.IsSuccessStatusCode) return []; if (!result.IsSuccessStatusCode) return [];
var data = await result.Content.ReadFromJsonAsync<IEnumerable<TimelineEntry>>(ct); var data = await result.Content.ReadFromJsonAsync<IEnumerable<TimelineEntry>>(ct);
return data ?? []; return data?.OrderBy(t => t.Date) ?? Enumerable.Empty<TimelineEntry>();
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 B

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 1.4 KiB