Update project to .NET 10.0 and upgrade dependencies in Dockerfile and project files

This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-03-09 22:27:48 +02:00
parent 1739a2b120
commit 44e75afc4f
7 changed files with 82 additions and 51 deletions
+3 -4
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
@@ -9,14 +9,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" /> <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.3" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
<PackageReference Include="Serilog" Version="4.1.0" /> <PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" /> <PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/> <PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.4"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+20 -26
View File
@@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite; using Microsoft.AspNetCore.Rewrite;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi;
using Serilog; using Serilog;
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
@@ -79,19 +79,13 @@ builder.Services.AddSwaggerGen(c =>
In = ParameterLocation.Header, In = ParameterLocation.Header,
Type = SecuritySchemeType.Http, Type = SecuritySchemeType.Http,
Description = "Put **_ONLY_** your JWT Bearer token on textbox below!", Description = "Put **_ONLY_** your JWT Bearer token on textbox below!",
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
}; };
c.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme); c.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, jwtSecurityScheme);
c.AddSecurityRequirement(new OpenApiSecurityRequirement c.AddSecurityRequirement(_ => new OpenApiSecurityRequirement
{ {
{ jwtSecurityScheme, Array.Empty<string>() } { new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme, null), new List<string>() }
}); });
}); });
builder.Services.Configure<ResourcesConfig>(builder.Configuration.GetSection(nameof(ResourcesConfig))); builder.Services.Configure<ResourcesConfig>(builder.Configuration.GetSection(nameof(ResourcesConfig)));
@@ -142,75 +136,75 @@ app.MapPost("/login",
var user = await userService.ValidateUser(request, ct: cancellationToken); var user = await userService.ValidateUser(request, ct: cancellationToken);
return Results.Ok(new { Token = authService.CreateToken(user)}); return Results.Ok(new { Token = authService.CreateToken(user)});
}) })
.WithOpenApi(op => new(op){ Summary = "Login"});; .WithSummary("Login");
app.MapPost("/users", app.MapPost("/users",
async (RegisterUserRequest registerUserRequest, IUserService userService, CancellationToken cancellationToken) async (RegisterUserRequest registerUserRequest, IUserService userService, CancellationToken cancellationToken)
=> await userService.RegisterUser(registerUserRequest, cancellationToken)) => await userService.RegisterUser(registerUserRequest, cancellationToken))
.RequireAuthorization(apiAdminPolicy) .RequireAuthorization(apiAdminPolicy)
.WithOpenApi(op => new(op){ Summary = "Creates a new user"}); .WithSummary("Creates a new user");
app.MapGet("/users/current", app.MapGet("/users/current",
async (IAuthService authService) => await authService.GetCurrentUser()) async (IAuthService authService) => await authService.GetCurrentUser())
.RequireAuthorization() .RequireAuthorization()
.WithOpenApi(op => new(op){ Summary = "Get Current User"}); .WithSummary("Get Current User");
app.MapGet("/users", app.MapGet("/users",
async (string? searchEmail, RoleEnum? searchRole, IUserService userService, CancellationToken ct) async (string? searchEmail, RoleEnum? searchRole, IUserService userService, CancellationToken ct)
=> await userService.GetUsers(searchEmail, searchRole, ct)) => await userService.GetUsers(searchEmail, searchRole, ct))
.RequireAuthorization(apiAdminPolicy) .RequireAuthorization(apiAdminPolicy)
.WithOpenApi(op => new(op){ Summary = "List users by criteria"}); .WithSummary("List users by criteria");
app.MapPut("/users/hardware/set", app.MapPut("/users/hardware/set",
async ([FromBody]SetHWRequest request, IUserService userService, ICache cache, CancellationToken ct) => async ([FromBody]SetHWRequest request, IUserService userService, ICache cache, CancellationToken ct) =>
await userService.UpdateHardware(request.Email, request.Hardware, ct: ct)) await userService.UpdateHardware(request.Email, request.Hardware, ct: ct))
.RequireAuthorization(apiAdminPolicy) .RequireAuthorization(apiAdminPolicy)
.WithOpenApi(op => new OpenApiOperation(op){ Summary = "Sets user's hardware"}); .WithSummary("Sets user's hardware");
app.MapPut("/users/queue-offsets/set", app.MapPut("/users/queue-offsets/set",
async ([FromBody]SetUserQueueOffsetsRequest request, IUserService userService, CancellationToken ct) async ([FromBody]SetUserQueueOffsetsRequest request, IUserService userService, CancellationToken ct)
=> await userService.UpdateQueueOffsets(request.Email, request.Offsets, ct)) => await userService.UpdateQueueOffsets(request.Email, request.Offsets, ct))
.RequireAuthorization() .RequireAuthorization()
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Sets user's queue offsets" }); .WithSummary("Sets user's queue offsets");
app.MapPut("/users/{email}/set-role/{role}", async (string email, RoleEnum role, IUserService userService, CancellationToken ct) app.MapPut("/users/{email}/set-role/{role}", async (string email, RoleEnum role, IUserService userService, CancellationToken ct)
=> await userService.ChangeRole(email, role, ct)) => await userService.ChangeRole(email, role, ct))
.RequireAuthorization(apiAdminPolicy) .RequireAuthorization(apiAdminPolicy)
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Set user's role" }); .WithSummary("Set user's role");
app.MapPut("/users/{email}/enable", async (string email, IUserService userService, CancellationToken ct) app.MapPut("/users/{email}/enable", async (string email, IUserService userService, CancellationToken ct)
=> await userService.SetEnableStatus(email, true, ct)) => await userService.SetEnableStatus(email, true, ct))
.RequireAuthorization(apiAdminPolicy) .RequireAuthorization(apiAdminPolicy)
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Disable user" }); .WithSummary("Enable user");
app.MapPut("/users/{email}/disable", async (string email, IUserService userService, CancellationToken ct) app.MapPut("/users/{email}/disable", async (string email, IUserService userService, CancellationToken ct)
=> await userService.SetEnableStatus(email, false, ct)) => await userService.SetEnableStatus(email, false, ct))
.RequireAuthorization(apiAdminPolicy) .RequireAuthorization(apiAdminPolicy)
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Disable user" }); .WithSummary("Disable user");
app.MapDelete("/users/{email}", async (string email, IUserService userService, CancellationToken ct) app.MapDelete("/users/{email}", async (string email, IUserService userService, CancellationToken ct)
=> await userService.RemoveUser(email, ct)) => await userService.RemoveUser(email, ct))
.RequireAuthorization(apiAdminPolicy) .RequireAuthorization(apiAdminPolicy)
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Remove user" }); .WithSummary("Remove user");
app.MapPost("/resources/{dataFolder?}", app.MapPost("/resources/{dataFolder?}",
async ([FromRoute]string? dataFolder, IFormFile data, IResourcesService resourceService, CancellationToken ct) async ([FromRoute]string? dataFolder, IFormFile data, IResourcesService resourceService, CancellationToken ct)
=> await resourceService.SaveResource(dataFolder, data, ct)) => await resourceService.SaveResource(dataFolder, data, ct))
.Accepts<IFormFile>("multipart/form-data") .Accepts<IFormFile>("multipart/form-data")
.RequireAuthorization() .RequireAuthorization()
//.WithOpenApi(op => new(op){ Summary = "Upload resource"}); //For some reason doesn't work when this is specified. .WithSummary("Upload resource")
.DisableAntiforgery(); .DisableAntiforgery();
app.MapGet("/resources/list/{dataFolder?}", app.MapGet("/resources/list/{dataFolder?}",
async ([FromRoute]string? dataFolder, string? search, IResourcesService resourcesService, CancellationToken ct) async ([FromRoute]string? dataFolder, string? search, IResourcesService resourcesService, CancellationToken ct)
=> await resourcesService.ListResources(dataFolder, search, ct)) => await resourcesService.ListResources(dataFolder, search, ct))
.RequireAuthorization() .RequireAuthorization()
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Lists resources in folder" }); .WithSummary("Lists resources in folder");
app.MapPost("/resources/clear/{dataFolder?}", app.MapPost("/resources/clear/{dataFolder?}",
([FromRoute]string? dataFolder, IResourcesService resourcesService) => resourcesService.ClearFolder(dataFolder)) ([FromRoute]string? dataFolder, IResourcesService resourcesService) => resourcesService.ClearFolder(dataFolder))
.RequireAuthorization(apiAdminPolicy) .RequireAuthorization(apiAdminPolicy)
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Clear folder" }); .WithSummary("Clear folder");
app.MapPost("/resources/get/{dataFolder?}", //Need to have POST method for secure password app.MapPost("/resources/get/{dataFolder?}", //Need to have POST method for secure password
async ([FromBody]GetResourceRequest request, [FromRoute]string? dataFolder, IAuthService authService, async ([FromBody]GetResourceRequest request, [FromRoute]string? dataFolder, IAuthService authService,
@@ -227,7 +221,7 @@ app.MapPost("/resources/get/{dataFolder?}", //Need to have POST method for secur
return Results.File(stream, "application/octet-stream", request.FileName); return Results.File(stream, "application/octet-stream", request.FileName);
}).RequireAuthorization() }).RequireAuthorization()
.WithOpenApi(op => new OpenApiOperation(op){ Summary = "Gets encrypted by users Password and HardwareHash resources. POST method for secure password"}); .WithSummary("Gets encrypted by users Password and HardwareHash resources. POST method for secure password");
app.MapGet("/resources/get-installer", app.MapGet("/resources/get-installer",
async (IAuthService authService, IResourcesService resourcesService, CancellationToken ct) => async (IAuthService authService, IResourcesService resourcesService, CancellationToken ct) =>
@@ -240,7 +234,7 @@ app.MapGet("/resources/get-installer",
throw new FileNotFoundException("Installer file was not found!"); throw new FileNotFoundException("Installer file was not found!");
return Results.File(stream, "application/octet-stream", name); return Results.File(stream, "application/octet-stream", name);
}).RequireAuthorization() }).RequireAuthorization()
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Gets latest installer" }); .WithSummary("Gets latest installer");
app.MapGet("/resources/get-installer/stage", app.MapGet("/resources/get-installer/stage",
async (IAuthService authService, IResourcesService resourcesService, CancellationToken ct) => async (IAuthService authService, IResourcesService resourcesService, CancellationToken ct) =>
@@ -253,7 +247,7 @@ app.MapGet("/resources/get-installer/stage",
throw new FileNotFoundException("Installer file was not found!"); throw new FileNotFoundException("Installer file was not found!");
return Results.File(stream, "application/octet-stream", name); return Results.File(stream, "application/octet-stream", name);
}).RequireAuthorization() }).RequireAuthorization()
.WithOpenApi(op => new OpenApiOperation(op) { Summary = "Gets latest installer" }); .WithSummary("Gets latest installer");
app.MapPost("/resources/check", app.MapPost("/resources/check",
+4 -7
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@@ -9,15 +9,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentValidation" Version="11.10.0" /> <PackageReference Include="FluentValidation" Version="11.10.0" />
<PackageReference Include="linq2db" Version="5.4.1" /> <PackageReference Include="linq2db" Version="5.4.1" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Npgsql" Version="8.0.5" /> <PackageReference Include="Npgsql" Version="10.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.Extensions.Options"> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.8\Microsoft.Extensions.Options.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>
+3 -10
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@@ -11,18 +11,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.AspNetCore.Http.Abstractions"> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.8\Microsoft.AspNetCore.Http.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.Http.Features" />
<Reference Include="Microsoft.Extensions.Options">
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.8\Microsoft.Extensions.Options.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="LazyCache.AspNetCore" Version="2.4.0" /> <PackageReference Include="LazyCache.AspNetCore" Version="2.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
</ItemGroup> </ItemGroup>
+2 -2
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
+2 -2
View File
@@ -1,9 +1,9 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app WORKDIR /app
EXPOSE 8080 EXPOSE 8080
# Build whole app # Build whole app
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /app WORKDIR /app
COPY . . COPY . .
+48
View File
@@ -0,0 +1,48 @@
#!/bin/sh
apt install -y docker.io apache2-utils nginx
# install cloudflared
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
dpkg -i cloudflared.deb
rm cloudflared.deb
docker run -d -p 5000:5000 --name registry --restart always registry:latest
cd /etc/nginx
mkdir -p auth
cd auth
htpasswd -c .htpasswd zxsanny
chmod 640 .htpasswd
chown root:www-data .htpasswd
cd /etc/nginx/sites-available
tee docker-registry << 'END'
server {
listen 80;
server_name _;
client_max_body_size 900M;
location / {
auth_basic "Registry";
auth_basic_user_file /etc/nginx/auth/.htpasswd;
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
END
ln -sf /etc/nginx/sites-available/docker-registry /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl restart nginx
# start tunnel — prints a *.trycloudflare.com URL
cloudflared tunnel --url http://localhost:80
# then from another machine:
# docker login <printed-trycloudflare-url>
# enter username: zxsanny and the password set above