Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4d52349c7 | |||
| aac2f5180b | |||
| 53bd0e107e | |||
| c85a6ec46f | |||
| 6b6a1bce7e | |||
| 2634daf800 | |||
| eeb6e5de6a | |||
| 2a9cb2b2fb | |||
| ef9a825dc2 | |||
| 8b4792cb5c | |||
| 8a16658dfe |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
65
.gitea/workflows/ci.yml
Normal file
65
.gitea/workflows/ci.yml
Normal 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
|
||||
@@ -1,42 +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
|
||||
tags:
|
||||
- docker
|
||||
script:
|
||||
- export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//')
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin 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
7
global.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.0",
|
||||
"rollForward": "latestMajor",
|
||||
"allowPrerelease": true
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,10 @@ public class ProjectController(IProjectRepository repository) : ControllerBase {
|
||||
return Ok(projects);
|
||||
}
|
||||
|
||||
[HttpGet("featured")]
|
||||
public async Task<IActionResult> GetFeaturedProjects(CancellationToken ct) {
|
||||
var projects = await repository.GetFeaturedProjects(ct);
|
||||
return Ok(projects);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,4 +13,10 @@ public class TechnologyController(ITechnologyRepository repository) : Controller
|
||||
return Ok(technologies);
|
||||
}
|
||||
|
||||
[HttpGet("featured")]
|
||||
public async Task<IActionResult> GetFeaturedTechnologies(CancellationToken ct) {
|
||||
var technologies = await repository.GetFeaturedTechnologies(ct);
|
||||
return Ok(technologies);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,10 +8,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0"/>
|
||||
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="13.0.2" />
|
||||
<PackageReference Include="Aspire.StackExchange.Redis.OutputCaching" Version="13.0.2" />
|
||||
<PackageReference Include="HopFrame.Web" Version="3.3.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using HopFrame.Core.Config;
|
||||
using HopFrame.Web;
|
||||
using Portfolio.Api;
|
||||
using Portfolio.Api.Services;
|
||||
@@ -31,25 +30,8 @@ builder.Services.AddHopFrame(options => {
|
||||
options.DisplayUserInfo(false);
|
||||
options.AddDbContext<DatabaseContext>(context => {
|
||||
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)
|
||||
.ForceRelation(isEnumerable: true)
|
||||
.FormatEach<Technology>((l, _) => l.Name)
|
||||
.List(false);
|
||||
|
||||
@@ -78,6 +60,8 @@ builder.Services.AddHopFrame(options => {
|
||||
.IsTextArea(true);
|
||||
});
|
||||
});
|
||||
|
||||
options.AddExporters();
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -13,4 +13,12 @@ internal sealed class ProjectRepository(DatabaseContext context) : IProjectRepos
|
||||
.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,4 +10,10 @@ internal sealed class TechnologyRepository(DatabaseContext context) : ITechnolog
|
||||
return await context.Technologies.ToArrayAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Technology>> GetFeaturedTechnologies(CancellationToken ct) {
|
||||
return await context.Technologies
|
||||
.Where(t => t.Featured)
|
||||
.ToArrayAsync(ct);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAspireHost>true</IsAspireHost>
|
||||
@@ -12,9 +12,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0"/>
|
||||
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.0.0" />
|
||||
<PackageReference Include="Aspire.Hosting.Redis" Version="9.0.0" />
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.0.2" />
|
||||
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="13.0.2" />
|
||||
<PackageReference Include="Aspire.Hosting.Redis" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.0.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.1.0" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.14.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -6,4 +6,6 @@ public interface IProjectRepository {
|
||||
|
||||
Task<IEnumerable<Project>> GetProjects(CancellationToken ct);
|
||||
|
||||
Task<IEnumerable<Project>> GetFeaturedProjects(CancellationToken ct);
|
||||
|
||||
}
|
||||
@@ -6,4 +6,6 @@ public interface ITechnologyRepository {
|
||||
|
||||
Task<IEnumerable<Technology>> GetTechnologies(CancellationToken ct);
|
||||
|
||||
Task<IEnumerable<Technology>> GetFeaturedTechnologies(CancellationToken ct);
|
||||
|
||||
}
|
||||
@@ -53,9 +53,15 @@
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
_about = await AboutRepository.GetAbout(TokenSource.Token);
|
||||
_experience = await TimelineRepository.GetTimeline(TimelineEntryType.Experience, TokenSource.Token);
|
||||
_carrier = await TimelineRepository.GetTimeline(TimelineEntryType.Carrier, TokenSource.Token);
|
||||
var aboutTask = AboutRepository.GetAbout(TokenSource.Token);
|
||||
var experienceTask = TimelineRepository.GetTimeline(TimelineEntryType.Experience, 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -82,7 +82,6 @@
|
||||
if (displayElement.innerText !== jobs.display)
|
||||
displayElement.innerText = jobs.display;
|
||||
}, 50)
|
||||
|
||||
</script>
|
||||
|
||||
@inherits CancellableComponent
|
||||
@@ -98,13 +97,15 @@
|
||||
private IEnumerable<TimelineEntry> _timeline = [];
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
var projects = await ProjectRepository.GetProjects(TokenSource.Token);
|
||||
_projects = projects.Where(p => p.Featured);
|
||||
var projectsTask = ProjectRepository.GetFeaturedProjects(TokenSource.Token);
|
||||
var technologiesTask = TechnologyRepository.GetFeaturedTechnologies(TokenSource.Token);
|
||||
var timelineTask = TimelineRepository.GetFeaturedTimeline(TokenSource.Token);
|
||||
|
||||
var technologies = await TechnologyRepository.GetTechnologies(TokenSource.Token);
|
||||
_technologies = technologies.Where(t => t.Featured);
|
||||
await Task.WhenAll(projectsTask, technologiesTask, timelineTask);
|
||||
|
||||
_timeline = await TimelineRepository.GetFeaturedTimeline(TokenSource.Token);
|
||||
_projects = projectsTask.Result;
|
||||
_technologies = technologiesTask.Result;
|
||||
_timeline = timelineTask.Result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
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
|
||||
WORKDIR /src
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
|
||||
@@ -10,6 +10,15 @@ internal sealed class ProjectRepository(IHttpClientFactory factory) : IProjectRe
|
||||
if (!response.IsSuccessStatusCode) return [];
|
||||
|
||||
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>();
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,13 @@ internal sealed class TechnologyRepository(IHttpClientFactory factory) : ITechno
|
||||
var data = await response.Content.ReadFromJsonAsync<IEnumerable<Technology>>(ct);
|
||||
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 ?? [];
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ internal sealed class TimelineRepository(IHttpClientFactory factory) : ITimeline
|
||||
if (!result.IsSuccessStatusCode) return [];
|
||||
|
||||
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) {
|
||||
@@ -19,6 +19,6 @@ internal sealed class TimelineRepository(IHttpClientFactory factory) : ITimeline
|
||||
if (!result.IsSuccessStatusCode) return [];
|
||||
|
||||
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 |
Reference in New Issue
Block a user