Skip to content

Commit 81bdc00

Browse files
authored
feat: .NET 11 (#339)
Migrates the backend from .NET 10 to .NET 11 (SDK 11.0.100-rc.1), and drops the OneOf package in favour of the language's own discriminated unions. - Targets net11.0 with LangVersion=preview and <Features>runtime-async=on</Features>. - Replaces OneOf<T0..Tn> with C# `union` types declared in Common/Results/Unions.cs, plus the case markers in Common/Results/CommonResultCases.cs. All ~30 call sites move from .Match/.TryPickT0/.IsT0/.AsT0 to pattern matching. Non-exhaustive switches over a union carry `default: throw new UnreachableException()`, since a switch statement is not exhaustiveness-checked. - Bumps ASP.NET Core, EF Core, Npgsql and DataProtection to 11.0.0-rc.1, and OpenShock.Internal.* to 1.2.0. Microsoft.AspNetCore.OpenApi goes to 11.x, which needs Microsoft.OpenApi 3.x and so trips NU1608 against Asp.Versioning.OpenApi's <3.0.0 constraint; verified working (the generated document is unchanged bar 9 schemas gaining a correct "type": "object"), so NU1608 is demoted to a warning rather than suppressed. - Pins StackExchange.Redis 3.2.15: 3.0.25 reflects over a MulticastDelegate field that no longer exists on .NET 11 and aborts the process. - Moves the docker images to rc.1 and copies global.json into the build stage, so the SDK pin is enforced there instead of silently falling back to whatever the base image ships. - Converts the configuration service's TryGet*Async to ConfigGetResult<T>, and the Cron email template parser from throwing to returning an error case. 68 files, +710/-503. 311 tests pass.
1 parent 59faa8b commit 81bdc00

68 files changed

Lines changed: 710 additions & 503 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.dockerignore‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ API/SmtpTemplates/dist/
99
**/appsettings.Development.json
1010
Dockerfile*
1111
**/*.md
12-
dev/
12+
Dev/

‎.github/workflows/ci-build.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ name: ci-build
2323

2424
env:
2525
REGISTRY: ghcr.io
26-
DOTNET_VERSION: 10.0.x
26+
DOTNET_VERSION: 11.0.x
2727

2828
jobs:
2929

‎.github/workflows/ci-tag.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
name: ci-tag
88

99
env:
10-
DOTNET_VERSION: 10.0.x
10+
DOTNET_VERSION: 11.0.x
1111
REGISTRY: ghcr.io
1212
IMAGE_NAME: ${{ github.repository_owner }}/api
1313

‎.github/workflows/codeql.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99
- cron: '0 6 * * 1'
1010

1111
env:
12-
DOTNET_VERSION: 10.x.x
12+
DOTNET_VERSION: 11.0.x
1313

1414
jobs:
1515
analyze:

‎API/Controller/Account/Authenticated/ChangeEmail.cs‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
using System.Net.Mime;
33
using Microsoft.AspNetCore.Mvc;
44
using OpenShock.API.Models.Requests;
5+
using OpenShock.API.Services.Account;
56
using OpenShock.Common.Errors;
67
using OpenShock.Common.Problems;
78
using OpenShock.Common.Utils;
9+
using Results = OpenShock.Common.Results;
810

911
using OpenShock.Internal.Common.Utils;
1012

@@ -42,13 +44,16 @@ public async Task<IActionResult> ChangeEmail([FromBody] ChangeEmailRequest body)
4244

4345
var result = await _accountService.CreateEmailChangeFlowAsync(CurrentUser.Id, body.Email, actorId: CurrentUser.Id);
4446

45-
return result.Match<IActionResult>(
46-
success => Ok(),
47-
alreadyInUse => Problem(AccountError.EmailChangeAlreadyInUse),
48-
unchanged => Problem(AccountError.EmailChangeUnchanged),
49-
tooMany => Problem(AccountError.EmailChangeTooMany),
50-
notActivated => throw new UnreachableException("Authenticated user is not activated"),
51-
deactivated => throw new UnreachableException("Authenticated user is deactivated"),
52-
notFound => throw new UnreachableException("Authenticated user not found in database"));
47+
return result switch
48+
{
49+
Results.Success => Ok(),
50+
EmailAlreadyInUse => Problem(AccountError.EmailChangeAlreadyInUse),
51+
EmailUnchanged => Problem(AccountError.EmailChangeUnchanged),
52+
TooManyEmailChanges => Problem(AccountError.EmailChangeTooMany),
53+
AccountNotActivated => throw new UnreachableException("Authenticated user is not activated"),
54+
AccountDeactivated => throw new UnreachableException("Authenticated user is deactivated"),
55+
Results.NotFound => throw new UnreachableException("Authenticated user not found in database"),
56+
_ => throw new UnreachableException()
57+
};
5358
}
5459
}

‎API/Controller/Account/Authenticated/ChangePassword.cs‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
using System.Net.Mime;
33
using Microsoft.AspNetCore.Mvc;
44
using OpenShock.API.Models.Requests;
5+
using OpenShock.API.Services.Account;
56
using OpenShock.Common.Errors;
67
using OpenShock.Common.Problems;
78
using OpenShock.Common.Utils;
9+
using Results = OpenShock.Common.Results;
810

911
using OpenShock.Internal.Common.Utils;
1012

@@ -42,10 +44,13 @@ public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest
4244

4345
var result = await _accountService.ChangePasswordAsync(CurrentUser.Id, body.NewPassword, actorId: CurrentUser.Id);
4446

45-
return result.Match<IActionResult>(
46-
success => Ok(),
47-
notActivated => throw new UnreachableException("Authenticated user is not activated"),
48-
deactivated => throw new UnreachableException("Authenticated user is deactivated"),
49-
notFound => throw new UnreachableException("Authenticated user not found in database"));
47+
return result switch
48+
{
49+
Results.Success => Ok(),
50+
AccountNotActivated => throw new UnreachableException("Authenticated user is not activated"),
51+
AccountDeactivated => throw new UnreachableException("Authenticated user is deactivated"),
52+
Results.NotFound => throw new UnreachableException("Authenticated user not found in database"),
53+
_ => throw new UnreachableException()
54+
};
5055
}
5156
}

‎API/Controller/Account/Authenticated/ChangeUsername.cs‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
using System.Net.Mime;
1+
using System.Diagnostics;
2+
using System.Net.Mime;
23
using Microsoft.AspNetCore.Mvc;
34
using OpenShock.API.Models.Requests;
5+
using OpenShock.API.Services.Account;
46
using OpenShock.Common.Errors;
57
using OpenShock.Common.OpenShockDb;
68
using OpenShock.Common.Problems;
9+
using OpenShock.Common.Validation;
10+
using Results = OpenShock.Common.Results;
711

812
using OpenShock.Internal.Common.Problems;
913

@@ -28,12 +32,15 @@ public async Task<IActionResult> ChangeUsername([FromBody] ChangeUsernameRequest
2832
var result = await _accountService.ChangeUsernameAsync(CurrentUser.Id, body.Username, actorId: CurrentUser.Id,
2933
ignoreLimit: CurrentUser.Roles.Any(r => r is RoleType.Staff or RoleType.Admin or RoleType.System));
3034

31-
return result.Match<IActionResult>(
32-
success => Ok(),
33-
usernametaken => Problem(AccountError.UsernameTaken),
34-
usernameerror => Problem(AccountError.UsernameInvalid(usernameerror)),
35-
recentlychanged => Problem(AccountError.UsernameRecentlyChanged),
36-
accountdeactivated => Problem(AccountError.AccountDeactivated),
37-
notfound => throw new Exception("Unexpected result, apparently our current user does not exist..."));
35+
return result switch
36+
{
37+
Results.Success => Ok(),
38+
UsernameTaken => Problem(AccountError.UsernameTaken),
39+
UsernameError usernameError => Problem(AccountError.UsernameInvalid(usernameError)),
40+
RecentlyChanged => Problem(AccountError.UsernameRecentlyChanged),
41+
AccountDeactivated => Problem(AccountError.AccountDeactivated),
42+
Results.NotFound => throw new Exception("Unexpected result, apparently our current user does not exist..."),
43+
_ => throw new UnreachableException()
44+
};
3845
}
3946
}

‎API/Controller/Account/Authenticated/Deactivate.cs‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
using Microsoft.AspNetCore.Mvc;
2+
using System.Diagnostics;
23
using System.Net.Mime;
4+
using OpenShock.API.Services.Account;
35
using OpenShock.Common.Errors;
46
using OpenShock.Common.Problems;
7+
using AccountSvc = OpenShock.API.Services.Account;
8+
using Results = OpenShock.Common.Results;
59

610
using OpenShock.Internal.Common.Problems;
711

@@ -19,12 +23,14 @@ public sealed partial class AuthenticatedAccountController
1923
public async Task<IActionResult> Deactivate()
2024
{
2125
var deactivationResult = await _accountService.DeactivateAccountAsync(CurrentUser.Id, CurrentUser.Id, deleteLater: true);
22-
return deactivationResult.Match<IActionResult>(
23-
success => NoContent(),
24-
cannotDeactivatePrivledged => Problem(AccountActivationError.CannotDeactivateOrDeletePrivledgedAccount),
25-
alreadyDeactivated => Problem(AccountActivationError.AlreadyDeactivated),
26-
unauthorized => Problem(AccountActivationError.Unauthorized),
27-
notFound => throw new Exception("This is not supposed to happen, wtf?")
28-
);
26+
return deactivationResult switch
27+
{
28+
Results.Success => NoContent(),
29+
CannotDeactivatePrivilegedAccount => Problem(AccountActivationError.CannotDeactivateOrDeletePrivledgedAccount),
30+
AccountDeactivationAlreadyInProgress => Problem(AccountActivationError.AlreadyDeactivated),
31+
AccountSvc.Unauthorized => Problem(AccountActivationError.Unauthorized),
32+
Results.NotFound => throw new Exception("This is not supposed to happen, wtf?"),
33+
_ => throw new UnreachableException()
34+
};
2935
}
3036
}

‎API/Controller/Account/CheckUsername.cs‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.Diagnostics;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Net.Mime;
34
using Microsoft.AspNetCore.Mvc;
45
using OpenShock.API.Models.Requests;
6+
using OpenShock.API.Services.Account;
7+
using OpenShock.Common.Results;
58
using OpenShock.Common.Validation;
69

710
namespace OpenShock.API.Controller.Account;
@@ -20,11 +23,13 @@ public async Task<UsernameCheckResponse> CheckUsername([FromBody] ChangeUsername
2023
{
2124
var result = await _accountService.CheckUsernameAvailabilityAsync(body.Username, cancellationToken);
2225

23-
return result.Match(
24-
success => new UsernameCheckResponse(UsernameAvailability.Available),
25-
taken => new UsernameCheckResponse(UsernameAvailability.Taken),
26-
invalid => new UsernameCheckResponse(UsernameAvailability.Invalid, invalid)
27-
);
26+
return result switch
27+
{
28+
Success => new UsernameCheckResponse(UsernameAvailability.Available),
29+
UsernameTaken => new UsernameCheckResponse(UsernameAvailability.Taken),
30+
UsernameError invalid => new UsernameCheckResponse(UsernameAvailability.Invalid, invalid),
31+
_ => throw new UnreachableException()
32+
};
2833
}
2934
}
3035

‎API/Controller/Account/LoginV2.cs‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
using Microsoft.AspNetCore.Mvc;
22
using OpenShock.API.Models.Requests;
3+
using System.Diagnostics;
34
using System.Net.Mime;
45
using Asp.Versioning;
56
using Microsoft.AspNetCore.RateLimiting;
7+
using OpenShock.API.Services.Account;
68
using OpenShock.Common.Errors;
9+
using OpenShock.Common.OpenShockDb;
710
using OpenShock.Common.Problems;
811
using OpenShock.API.Models.Response;
912
using OpenShock.API.Services.Turnstile;
13+
using Results = OpenShock.Common.Results;
1014

1115
using OpenShock.Internal.Common.Problems;
1216

@@ -38,14 +42,16 @@ public async Task<IActionResult> LoginV2(
3842
if (turnstileError is not null) return turnstileError;
3943

4044
var getAccountResult = await _accountService.GetAccountByCredentialsAsync(body.UsernameOrEmail, body.Password, cancellationToken);
41-
if (!getAccountResult.TryPickT0(out var account, out var errors))
45+
if (getAccountResult is not User account)
4246
{
43-
return errors.Match(
44-
notFound => Problem(LoginError.InvalidCredentials),
45-
deactivated => Problem(AccountError.AccountDeactivated),
46-
notActivated => Problem(AccountError.AccountNotActivated),
47-
oauthOnly => Problem(AccountError.AccountOAuthOnly)
48-
);
47+
return getAccountResult switch
48+
{
49+
Results.NotFound => Problem(LoginError.InvalidCredentials),
50+
AccountDeactivated => Problem(AccountError.AccountDeactivated),
51+
AccountNotActivated => Problem(AccountError.AccountNotActivated),
52+
AccountIsOAuthOnly => Problem(AccountError.AccountOAuthOnly),
53+
_ => throw new UnreachableException()
54+
};
4955
}
5056

5157
await CreateSession(account.Id, cookieDomain);

0 commit comments

Comments
 (0)