From f18ee5c29941146923034195cd7c3c363be4f1ff Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 24 Sep 2025 12:08:52 +0200 Subject: [PATCH 01/29] Create initial draft for .NET 10 changes --- API/Controller/Admin/GetOnlineDevices.cs | 2 +- Common/Hubs/UserHub.cs | 2 +- Common/OpenShockMiddlewareHelper.cs | 7 +++---- .../LCGNodeProvisioner/LCGNodeProvisioner.cs | 2 +- Common/Services/Session/SessionService.cs | 7 ++++--- Common/Utils/TrustedProxiesFetcher.cs | 4 ++-- Directory.Build.props | 4 ++-- Directory.Packages.props | 18 +++++++++--------- global.json | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/API/Controller/Admin/GetOnlineDevices.cs b/API/Controller/Admin/GetOnlineDevices.cs index 6860f3a5..9208c6cc 100644 --- a/API/Controller/Admin/GetOnlineDevices.cs +++ b/API/Controller/Admin/GetOnlineDevices.cs @@ -22,7 +22,7 @@ public async Task GetOnlineDevices() { var devicesOnline = _redis.RedisCollection(false); - var allOnlineDevices = await devicesOnline.ToArrayAsync(); + var allOnlineDevices = await devicesOnline.ToListAsync(); var dbLookup = await _db.Devices .Where(x => allOnlineDevices.Select(y => y.Id).Contains(x.Id)) .Select(x => new diff --git a/Common/Hubs/UserHub.cs b/Common/Hubs/UserHub.cs index 00a24d80..1edfd686 100644 --- a/Common/Hubs/UserHub.cs +++ b/Common/Hubs/UserHub.cs @@ -47,7 +47,7 @@ public override async Task OnConnectedAsync() .Where(x => x.Shockers.Any(y => y.UserShares.Any(z => z.SharedWithUserId == UserId))) .Select(x => x.Id.ToString()).ToArrayAsync(); - var own = devicesOnline.Where(x => x.Owner == UserId).ToArrayAsync(); + var own = devicesOnline.Where(x => x.Owner == UserId).ToListAsync(); var shared = devicesOnline.FindByIdsAsync(sharedDevices); await Task.WhenAll(own, shared); diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 712de703..1c3fd86b 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -1,7 +1,5 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using OpenShock.Common.OpenShockDb; using OpenShock.Common.Options; using OpenShock.Common.Redis; @@ -10,6 +8,7 @@ using Redis.OM.Contracts; using Scalar.AspNetCore; using Serilog; +using IPNetwork = System.Net.IPNetwork; namespace OpenShock.Common; @@ -26,11 +25,11 @@ public static class OpenShockMiddlewareHelper public static async Task UseCommonOpenShockMiddleware(this WebApplication app) { var metricsOptions = app.Services.GetRequiredService(); - var metricsAllowedIpNetworks = metricsOptions.AllowedNetworks.Select(x => IPNetwork.Parse(x)).ToArray(); + var metricsAllowedIpNetworks = metricsOptions.AllowedNetworks.Select(IPNetwork.Parse).ToArray(); foreach (var proxy in await TrustedProxiesFetcher.GetTrustedNetworksAsync()) { - ForwardedSettings.KnownNetworks.Add(proxy); + ForwardedSettings.KnownIPNetworks.Add(proxy); } app.UseForwardedHeaders(ForwardedSettings); diff --git a/Common/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs b/Common/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs index 130228b2..92c61cf5 100644 --- a/Common/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs +++ b/Common/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs @@ -43,7 +43,7 @@ public LCGNodeProvisioner(IRedisConnectionProvider redisConnectionProvider, IWeb var nodes = await _lcgNodes .Where(x => x.Environment == _environmentName) - .ToArrayAsync(); + .ToListAsync(); var node = nodes .OrderBy(x => DistanceLookup.TryGetDistanceBetween(x.Country, countryCode, out float distance) ? distance : Distance.DistanceToAndromedaGalaxyInKm) // Just a large number :3 diff --git a/Common/Services/Session/SessionService.cs b/Common/Services/Session/SessionService.cs index 97b8d039..5b410dc2 100644 --- a/Common/Services/Session/SessionService.cs +++ b/Common/Services/Session/SessionService.cs @@ -45,7 +45,8 @@ await _loginSessions.InsertAsync(new LoginSession public async Task> ListSessionsByUserIdAsync(Guid userId) { - return await _loginSessions.Where(x => x.UserId == userId).ToArrayAsync(); + var list = await _loginSessions.Where(x => x.UserId == userId).ToListAsync(); + return list.AsReadOnly(); } public async Task GetSessionByTokenAsync(string sessionToken) @@ -95,11 +96,11 @@ public async Task DeleteSessionByIdAsync(Guid sessionId) public async Task DeleteSessionsByUserIdAsync(Guid userId) { - var sessions = await _loginSessions.Where(x => x.UserId == userId).ToArrayAsync(); + var sessions = await _loginSessions.Where(x => x.UserId == userId).ToListAsync(); await _loginSessions.DeleteAsync(sessions); - return sessions.Length; + return sessions.Count; } public async Task DeleteSessionAsync(LoginSession loginSession) diff --git a/Common/Utils/TrustedProxiesFetcher.cs b/Common/Utils/TrustedProxiesFetcher.cs index 4078fb02..b89ace4c 100644 --- a/Common/Utils/TrustedProxiesFetcher.cs +++ b/Common/Utils/TrustedProxiesFetcher.cs @@ -1,4 +1,4 @@ -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; +using System.Net; namespace OpenShock.Common.Utils; @@ -23,7 +23,7 @@ public static class TrustedProxiesFetcher "fe80::/10", ]; - private static readonly IPNetwork[] PrivateNetworksParsed = [.. PrivateNetworks.Select(x => IPNetwork.Parse(x))]; + private static readonly IPNetwork[] PrivateNetworksParsed = [.. PrivateNetworks.Select(IPNetwork.Parse)]; private static readonly char[] NewLineSeperators = ['\r', '\n', '\t']; diff --git a/Directory.Build.props b/Directory.Build.props index 2e09c730..a17750c4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -22,12 +22,12 @@ - net9.0 + net10.0 enable - Latest + Preview enable $(Product) $(Product.Replace(" ", "_")) diff --git a/Directory.Packages.props b/Directory.Packages.props index 15d7c287..8814d33d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,16 +14,16 @@ - - - - - - - + + + + + + + - + @@ -41,6 +41,6 @@ - + \ No newline at end of file diff --git a/global.json b/global.json index 485649e1..d32ac4b9 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.0", + "version": "10.0.100-rc.1", "rollForward": "latestMinor", "allowPrerelease": false } From b283abfde69386e27074f0b63fde9cb35d3747f9 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 1 Oct 2025 10:53:55 +0200 Subject: [PATCH 02/29] Update Directory.Packages.props --- Directory.Packages.props | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8814d33d..26fb9ed7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,14 +5,14 @@ - + - + - + @@ -28,19 +28,19 @@ - + - + - - + + - + \ No newline at end of file From 510d44eed12209707a30533f54e0a71daaba8386 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 1 Oct 2025 11:44:45 +0200 Subject: [PATCH 03/29] Use field keyword --- LiveControlGateway/Controllers/HubControllerBase.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/LiveControlGateway/Controllers/HubControllerBase.cs b/LiveControlGateway/Controllers/HubControllerBase.cs index f8a91f0d..6f21f088 100644 --- a/LiveControlGateway/Controllers/HubControllerBase.cs +++ b/LiveControlGateway/Controllers/HubControllerBase.cs @@ -1,4 +1,5 @@ -using FlatSharp; +using System.Diagnostics.CodeAnalysis; +using FlatSharp; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using OneOf; @@ -37,17 +38,16 @@ public abstract class HubControllerBase : FlatbuffersWebsocketBaseCon /// Service provider /// protected readonly IServiceProvider ServiceProvider; - - private HubLifetime? _hubLifetime; - + /// /// Hub lifetime /// /// + [field: AllowNull, MaybeNull] protected HubLifetime HubLifetime { - get => _hubLifetime ?? throw new InvalidOperationException("Hub lifetime is null but was tried to access"); - private set => _hubLifetime = value; + get => field ?? throw new InvalidOperationException("Hub lifetime is null but was tried to access"); + private set; } private readonly LcgOptions _options; From 1320cbbf93b68c12079fbaeb17019fde2a6f48ff Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Fri, 17 Oct 2025 10:31:34 +0200 Subject: [PATCH 04/29] Update Directory.Packages.props --- Directory.Packages.props | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 26fb9ed7..6a731e1c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,35 +12,35 @@ - + - - - - + + + + - + - + - + - - + + - + - + \ No newline at end of file From 2735fa6a090055850588481753602bf427030798 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Fri, 17 Oct 2025 10:35:36 +0200 Subject: [PATCH 05/29] Fix some errors --- API.IntegrationTests/Tests/MetadataTests.cs | 6 +++--- Common.Tests/Geo/Alpha2CountryCodeTests.cs | 1 - Common.Tests/Models/SemVersion.cs | 11 ++--------- Common.Tests/Query/ExpressionBuilderTests.cs | 1 - Common.Tests/Query/QueryStringTokenizerTests.cs | 1 - 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/API.IntegrationTests/Tests/MetadataTests.cs b/API.IntegrationTests/Tests/MetadataTests.cs index 3a13f5f4..e1cfc20a 100644 --- a/API.IntegrationTests/Tests/MetadataTests.cs +++ b/API.IntegrationTests/Tests/MetadataTests.cs @@ -34,7 +34,7 @@ public async Task GetMetadata_ShouldMatchBackendInfoResponseContract() // Validate Version var version = data.GetProperty("version").GetString(); - await Assert.That(version).IsNotNullOrWhitespace(); + await Assert.That(version).IsNotNullOrEmpty(); // Validate Commit var commit = data.GetProperty("commit").GetString(); @@ -62,7 +62,7 @@ await Assert.That(Uri.TryCreate(shortLinkUrlStr, UriKind.Absolute, out _)) if (turnstileSiteKeyProp.ValueKind is not JsonValueKind.Null) { var turnstileSiteKey = turnstileSiteKeyProp.GetString(); - await Assert.That(turnstileSiteKey).IsNotNullOrWhitespace(); + await Assert.That(turnstileSiteKey).IsNotNullOrEmpty(); } // Validate OAuthProviders (string[]) @@ -71,7 +71,7 @@ await Assert.That(Uri.TryCreate(shortLinkUrlStr, UriKind.Absolute, out _)) foreach (var provider in oauthProviders.EnumerateArray()) { var p = provider.GetString(); - await Assert.That(p).IsNotNullOrWhitespace(); + await Assert.That(p).IsNotNullOrEmpty(); } // Validate IsUserAuthenticated (bool) diff --git a/Common.Tests/Geo/Alpha2CountryCodeTests.cs b/Common.Tests/Geo/Alpha2CountryCodeTests.cs index 7dbda73e..c4198875 100644 --- a/Common.Tests/Geo/Alpha2CountryCodeTests.cs +++ b/Common.Tests/Geo/Alpha2CountryCodeTests.cs @@ -1,5 +1,4 @@ using OpenShock.Common.Geo; -using TUnit.Assertions.AssertConditions.Throws; namespace OpenShock.Common.Tests.Geo; diff --git a/Common.Tests/Models/SemVersion.cs b/Common.Tests/Models/SemVersion.cs index 0caac30a..3a68dfae 100644 --- a/Common.Tests/Models/SemVersion.cs +++ b/Common.Tests/Models/SemVersion.cs @@ -1,5 +1,6 @@ using System.Text; using OpenShock.Common.Models; +using OpenShock.Serialization.Types; namespace OpenShock.Common.Tests.Models; @@ -222,7 +223,7 @@ public async Task Equals_Structural_IncludingBuild() var b = new SemVersion(1, 2, 3, "alpha", "b1"); await Assert.That(a).IsEqualTo(b); - await Assert.That(a).IsEqualTo(b); + await Assert.That(a).IsEqualTo(b); await Assert.That(a.GetHashCode()).IsEqualTo(b.GetHashCode()); } @@ -244,14 +245,6 @@ public async Task Equals_DiffersInPrerelease_False() await Assert.That(a).IsNotEqualTo(b); } - [Test] - public async Task Equals_Null_False() - { - var a = new SemVersion(0, 0, 0); - await Assert.That(a).IsNotEqualTo(null); - await Assert.That(a).IsNotEqualTo(null); - } - // --------------------------- // FBS roundtrip (requires your Serialization.Types.SemVer) // --------------------------- diff --git a/Common.Tests/Query/ExpressionBuilderTests.cs b/Common.Tests/Query/ExpressionBuilderTests.cs index 5e1fb1d3..3a663dc5 100644 --- a/Common.Tests/Query/ExpressionBuilderTests.cs +++ b/Common.Tests/Query/ExpressionBuilderTests.cs @@ -1,5 +1,4 @@ using OpenShock.Common.Query; -using TUnit.Assertions.AssertConditions.Throws; using Bogus; namespace OpenShock.Common.Tests.Query; diff --git a/Common.Tests/Query/QueryStringTokenizerTests.cs b/Common.Tests/Query/QueryStringTokenizerTests.cs index 478d9d3e..c7ca5a6a 100644 --- a/Common.Tests/Query/QueryStringTokenizerTests.cs +++ b/Common.Tests/Query/QueryStringTokenizerTests.cs @@ -1,5 +1,4 @@ using OpenShock.Common.Query; -using TUnit.Assertions.AssertConditions.Throws; namespace OpenShock.Common.Tests.Query; From 590ea33351b794220f50f4516d1ea3788f7db85f Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Mon, 20 Oct 2025 13:38:50 +0200 Subject: [PATCH 06/29] Update Directory.Packages.props --- Directory.Packages.props | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 6a731e1c..5a72bcd7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,13 +14,13 @@ - + - - + + @@ -38,9 +38,9 @@ - - - - + + + + \ No newline at end of file From a9b75ad33015ea2782e13bd2987b230236c7427a Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 28 Oct 2025 19:39:58 +0100 Subject: [PATCH 07/29] Update global.json --- global.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/global.json b/global.json index d32ac4b9..6e016cfb 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "10.0.100-rc.1", - "rollForward": "latestMinor", - "allowPrerelease": false + "version": "10.0.0", + "rollForward": "latestFeature", + "allowPrerelease": true } } From 6775c2dfc449e7c70d34aa64ab8908abf1a5e27e Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 28 Oct 2025 19:53:15 +0100 Subject: [PATCH 08/29] Revert some changes --- Common.Tests/Models/SemVersion.cs | 1 - Directory.Packages.props | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Common.Tests/Models/SemVersion.cs b/Common.Tests/Models/SemVersion.cs index 3a68dfae..dd1f76c9 100644 --- a/Common.Tests/Models/SemVersion.cs +++ b/Common.Tests/Models/SemVersion.cs @@ -1,6 +1,5 @@ using System.Text; using OpenShock.Common.Models; -using OpenShock.Serialization.Types; namespace OpenShock.Common.Tests.Models; diff --git a/Directory.Packages.props b/Directory.Packages.props index 51aa6e46..55120b03 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -36,7 +36,7 @@ - + From cc112892d0bb1369a6f4366ae54785cf183cc47d Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 28 Oct 2025 19:59:09 +0100 Subject: [PATCH 09/29] Improve SessionService SessionLister --- API/Controller/Sessions/ListSessions.cs | 7 +++---- Common/Services/Session/ISessionService.cs | 2 +- Common/Services/Session/SessionService.cs | 5 ++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/API/Controller/Sessions/ListSessions.cs b/API/Controller/Sessions/ListSessions.cs index a00123ea..2074c226 100644 --- a/API/Controller/Sessions/ListSessions.cs +++ b/API/Controller/Sessions/ListSessions.cs @@ -6,10 +6,9 @@ namespace OpenShock.API.Controller.Sessions; public sealed partial class SessionsController { [HttpGet] - public async Task> ListSessions() + public IAsyncEnumerable ListSessions() { - var sessions = await _sessionService.ListSessionsByUserIdAsync(CurrentUser.Id); - - return sessions.Select(LoginSessionResponse.MapFrom); + return _sessionService.ListSessionsByUserIdAsync(CurrentUser.Id) + .Select(LoginSessionResponse.MapFrom); } } \ No newline at end of file diff --git a/Common/Services/Session/ISessionService.cs b/Common/Services/Session/ISessionService.cs index a7951270..6538356e 100644 --- a/Common/Services/Session/ISessionService.cs +++ b/Common/Services/Session/ISessionService.cs @@ -6,7 +6,7 @@ public interface ISessionService { public Task CreateSessionAsync(Guid userId, string userAgent, string ipAddress); - public Task> ListSessionsByUserIdAsync(Guid userId); + public IAsyncEnumerable ListSessionsByUserIdAsync(Guid userId); public Task GetSessionByTokenAsync(string sessionToken); diff --git a/Common/Services/Session/SessionService.cs b/Common/Services/Session/SessionService.cs index 7dc631f8..f506640f 100644 --- a/Common/Services/Session/SessionService.cs +++ b/Common/Services/Session/SessionService.cs @@ -43,10 +43,9 @@ await _loginSessions.InsertAsync(new LoginSession return new CreateSessionResult(id, token); } - public async Task> ListSessionsByUserIdAsync(Guid userId) + public IAsyncEnumerable ListSessionsByUserIdAsync(Guid userId) { - var list = await _loginSessions.Where(x => x.UserId == userId).ToListAsync(); - return list.AsReadOnly(); + return _loginSessions.Where(x => x.UserId == userId); } public async Task GetSessionByTokenAsync(string sessionToken) From ba965b666314367f9d8627c0e3595851b92a2d65 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Thu, 6 Nov 2025 21:56:21 +0100 Subject: [PATCH 10/29] Update global.json --- global.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/global.json b/global.json index 6e016cfb..35bdbc77 100644 --- a/global.json +++ b/global.json @@ -2,6 +2,6 @@ "sdk": { "version": "10.0.0", "rollForward": "latestFeature", - "allowPrerelease": true + "allowPrerelease": false } -} +} \ No newline at end of file From d1adb30f72f0eb6dc4e3713fbfbbfc1fc5e9a05b Mon Sep 17 00:00:00 2001 From: hhvrc Date: Thu, 6 Nov 2025 22:01:10 +0100 Subject: [PATCH 11/29] Revert some changes --- Directory.Build.props | 2 +- global.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a0582022..4491cfc4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,7 +27,7 @@ enable - Preview + Latest enable $(Product) $(Product.Replace(" ", "_")) diff --git a/global.json b/global.json index 35bdbc77..cb40a970 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { "version": "10.0.0", - "rollForward": "latestFeature", + "rollForward": "latestMinor", "allowPrerelease": false } -} \ No newline at end of file +} From c18cd361823f391ae74b9c760eea997119b7bc12 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Thu, 6 Nov 2025 22:23:16 +0100 Subject: [PATCH 12/29] Update ListSessions.cs --- API/Controller/Sessions/ListSessions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/API/Controller/Sessions/ListSessions.cs b/API/Controller/Sessions/ListSessions.cs index 6fac65f4..ebf9e092 100644 --- a/API/Controller/Sessions/ListSessions.cs +++ b/API/Controller/Sessions/ListSessions.cs @@ -9,9 +9,10 @@ public sealed partial class SessionsController { [HttpGet] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - public IAsyncEnumerable ListSessions() + public IAsyncEnumerable ListSessions([EnumeratorCancellation] CancellationToken cancellationToken) { return _sessionService.ListSessionsByUserIdAsync(CurrentUser.Id) + .WithCancellation(cancellationToken) .Select(LoginSessionResponse.MapFrom); } } \ No newline at end of file From 63c6565199a364d97d8a9b951f5fde41783e6e7f Mon Sep 17 00:00:00 2001 From: hhvrc Date: Thu, 6 Nov 2025 22:27:03 +0100 Subject: [PATCH 13/29] Update ListSessions.cs --- API/Controller/Sessions/ListSessions.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/API/Controller/Sessions/ListSessions.cs b/API/Controller/Sessions/ListSessions.cs index ebf9e092..964b1c26 100644 --- a/API/Controller/Sessions/ListSessions.cs +++ b/API/Controller/Sessions/ListSessions.cs @@ -1,5 +1,4 @@ using System.Net.Mime; -using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Mvc; using OpenShock.API.Models.Response; @@ -9,10 +8,9 @@ public sealed partial class SessionsController { [HttpGet] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - public IAsyncEnumerable ListSessions([EnumeratorCancellation] CancellationToken cancellationToken) + public IAsyncEnumerable ListSessions() { return _sessionService.ListSessionsByUserIdAsync(CurrentUser.Id) - .WithCancellation(cancellationToken) .Select(LoginSessionResponse.MapFrom); } } \ No newline at end of file From 296df410c97488f98299c47b241361623281c9c7 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 11 Nov 2025 20:15:58 +0100 Subject: [PATCH 14/29] Update Directory.Packages.props --- Directory.Packages.props | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index dd46e2d1..9146d1d7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,14 +14,14 @@ - - + + - - + + - + @@ -37,10 +37,10 @@ - + - + \ No newline at end of file From 3b10388e7bcdb69dc6bba11f4cfbb4ab6a60a40c Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 11 Nov 2025 20:40:24 +0100 Subject: [PATCH 15/29] Update Directory.Packages.props --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 9146d1d7..44ace0d3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,11 +16,11 @@ - + - - + + @@ -41,6 +41,6 @@ - + \ No newline at end of file From a550140ac3c723b9af9cfff88c7aae034fcfe8f8 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 11 Nov 2025 20:44:14 +0100 Subject: [PATCH 16/29] Update workflows --- .github/workflows/ci-build.yml | 2 +- .github/workflows/ci-tag.yml | 2 +- .github/workflows/codeql.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 4dd2d192..7dbf7709 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -33,7 +33,7 @@ name: ci-build env: REGISTRY: ghcr.io - DOTNET_VERSION: 9.0.x + DOTNET_VERSION: 10.0.x jobs: diff --git a/.github/workflows/ci-tag.yml b/.github/workflows/ci-tag.yml index 504ccb81..959050ce 100644 --- a/.github/workflows/ci-tag.yml +++ b/.github/workflows/ci-tag.yml @@ -7,7 +7,7 @@ on: name: ci-tag env: - DOTNET_VERSION: 9.0.x + DOTNET_VERSION: 10.0.x REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository_owner }}/api diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8cfacc32..14f1ec6c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -9,7 +9,7 @@ on: - cron: '0 6 * * 1' env: - DOTNET_VERSION: 9.x.x + DOTNET_VERSION: 10.x.x jobs: analyze: From 2395048f5e0cfb830b793be9f1b16b44ec7016de Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 11 Nov 2025 20:46:07 +0100 Subject: [PATCH 17/29] Update docker files --- docker/API.Dockerfile | 2 +- docker/Base.Dockerfile | 2 +- docker/Cron.Dockerfile | 2 +- docker/LiveControlGateway.Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/API.Dockerfile b/docker/API.Dockerfile index 92247044..2e67a470 100644 --- a/docker/API.Dockerfile +++ b/docker/API.Dockerfile @@ -10,7 +10,7 @@ COPY --link API/. API/ RUN dotnet publish --no-restore -c Release API/API.csproj -o /app # final is the final runtime stage for running the app -FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final-api +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final-api WORKDIR /app COPY docker/entrypoint.sh /entrypoint.sh diff --git a/docker/Base.Dockerfile b/docker/Base.Dockerfile index 766d5bfe..0768d286 100644 --- a/docker/Base.Dockerfile +++ b/docker/Base.Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build-common +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build-common WORKDIR /src COPY --link Common/*.csproj Common/ diff --git a/docker/Cron.Dockerfile b/docker/Cron.Dockerfile index 10890695..1836c4b9 100644 --- a/docker/Cron.Dockerfile +++ b/docker/Cron.Dockerfile @@ -10,7 +10,7 @@ COPY --link Cron/. Cron/ RUN dotnet publish --no-restore -c Release Cron/Cron.csproj -o /app # final is the final runtime stage for running the app -FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final-cron +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final-cron WORKDIR /app COPY docker/entrypoint.sh /entrypoint.sh diff --git a/docker/LiveControlGateway.Dockerfile b/docker/LiveControlGateway.Dockerfile index 9f302cba..5d4a8107 100644 --- a/docker/LiveControlGateway.Dockerfile +++ b/docker/LiveControlGateway.Dockerfile @@ -10,7 +10,7 @@ COPY --link LiveControlGateway/. LiveControlGateway/ RUN dotnet publish --no-restore -c Release LiveControlGateway/LiveControlGateway.csproj -o /app # final is the final runtime stage for running the app -FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final-gateway +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final-gateway WORKDIR /app COPY docker/entrypoint.sh /entrypoint.sh From 63df5fd78f390ffe8d78eb03a54a56bb8e5cbd4a Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 11 Nov 2025 20:47:08 +0100 Subject: [PATCH 18/29] Revert "Update Directory.Packages.props" This reverts commit 3b10388e7bcdb69dc6bba11f4cfbb4ab6a60a40c. --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 44ace0d3..9146d1d7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,11 +16,11 @@ - + - - + + @@ -41,6 +41,6 @@ - + \ No newline at end of file From 35b530defe6504e68a37796e911bbe16bd5f413e Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 11 Nov 2025 21:01:30 +0100 Subject: [PATCH 19/29] OpenAPI broke --- .../DataAnnotations/EmailAddressAttribute.cs | 7 +++---- .../Interfaces/IOperationAttribute.cs | 2 +- .../Interfaces/IParameterAttribute.cs | 2 +- Common/DataAnnotations/OpenApiSchemas.cs | 19 ++++++++++--------- Common/DataAnnotations/PasswordAttribute.cs | 7 +++---- Common/DataAnnotations/UsernameAttribute.cs | 7 +++---- Common/Swagger/AttributeFilter.cs | 6 +++--- Common/Swagger/SwaggerGenExtensions.cs | 2 +- Common/Utils/ConfigureSwaggerOptions.cs | 2 +- 9 files changed, 26 insertions(+), 28 deletions(-) diff --git a/Common/DataAnnotations/EmailAddressAttribute.cs b/Common/DataAnnotations/EmailAddressAttribute.cs index cf7a3ec0..030314dc 100644 --- a/Common/DataAnnotations/EmailAddressAttribute.cs +++ b/Common/DataAnnotations/EmailAddressAttribute.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.Net.Mail; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenShock.Common.Constants; using OpenShock.Common.DataAnnotations.Interfaces; @@ -13,7 +12,7 @@ namespace OpenShock.Common.DataAnnotations; /// /// Inherits from . /// -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] public sealed class EmailAddressAttribute : ValidationAttribute, IParameterAttribute { /// @@ -61,7 +60,7 @@ public void Apply(OpenApiSchema schema) { //if (ShouldValidate) schema.Pattern = ???; - schema.Example = new OpenApiString(ExampleValue); + schema.Example = ExampleValue; } /// diff --git a/Common/DataAnnotations/Interfaces/IOperationAttribute.cs b/Common/DataAnnotations/Interfaces/IOperationAttribute.cs index d47bd156..6345cb6f 100644 --- a/Common/DataAnnotations/Interfaces/IOperationAttribute.cs +++ b/Common/DataAnnotations/Interfaces/IOperationAttribute.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; namespace OpenShock.Common.DataAnnotations.Interfaces; diff --git a/Common/DataAnnotations/Interfaces/IParameterAttribute.cs b/Common/DataAnnotations/Interfaces/IParameterAttribute.cs index 295f4d5e..896446fc 100644 --- a/Common/DataAnnotations/Interfaces/IParameterAttribute.cs +++ b/Common/DataAnnotations/Interfaces/IParameterAttribute.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; namespace OpenShock.Common.DataAnnotations.Interfaces; diff --git a/Common/DataAnnotations/OpenApiSchemas.cs b/Common/DataAnnotations/OpenApiSchemas.cs index 69651381..03ac265f 100644 --- a/Common/DataAnnotations/OpenApiSchemas.cs +++ b/Common/DataAnnotations/OpenApiSchemas.cs @@ -1,21 +1,22 @@ -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenShock.Common.Models; namespace OpenShock.Common.DataAnnotations; public static class OpenApiSchemas { - public static OpenApiSchema SemVerSchema => new OpenApiSchema { + public static OpenApiSchema SemVerSchema => new() + { Title = "SemVer", - Type = "string", - Pattern = /* lang=regex */ "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", - Example = new OpenApiString("1.0.0-dev+a16f2") + Type = JsonSchemaType.String, + Pattern = /* lang=regex */ @"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", + Example = "1.0.0-dev+a16f2" }; - public static OpenApiSchema PauseReasonEnumSchema => new OpenApiSchema { + public static OpenApiSchema PauseReasonEnumSchema => new() + { Title = nameof(PauseReason), - Type = "integer", + Type = JsonSchemaType.Integer, Description = """ An integer representing the reason(s) for the shocker being paused, expressed as a bitfield where reasons are OR'd together. @@ -26,6 +27,6 @@ public static class OpenApiSchemas For example, a value of 6 (2 | 4) indicates both 'UserShare' and 'PublicShare' reasons. """, - Example = new OpenApiInteger(6) + Example = 6 }; } diff --git a/Common/DataAnnotations/PasswordAttribute.cs b/Common/DataAnnotations/PasswordAttribute.cs index ca42b60a..dd4b95b3 100644 --- a/Common/DataAnnotations/PasswordAttribute.cs +++ b/Common/DataAnnotations/PasswordAttribute.cs @@ -1,6 +1,5 @@ using System.ComponentModel.DataAnnotations; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenShock.Common.Constants; using OpenShock.Common.DataAnnotations.Interfaces; @@ -12,7 +11,7 @@ namespace OpenShock.Common.DataAnnotations; /// /// Inherits from . /// -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] public sealed class PasswordAttribute : ValidationAttribute, IParameterAttribute { /// @@ -60,7 +59,7 @@ public void Apply(OpenApiSchema schema) { //if (ShouldValidate) schema.Pattern = ???; - schema.Example = new OpenApiString(ExampleValue); + schema.Example = ExampleValue; } /// diff --git a/Common/DataAnnotations/UsernameAttribute.cs b/Common/DataAnnotations/UsernameAttribute.cs index c0b366d7..29a774dd 100644 --- a/Common/DataAnnotations/UsernameAttribute.cs +++ b/Common/DataAnnotations/UsernameAttribute.cs @@ -1,6 +1,5 @@ using System.ComponentModel.DataAnnotations; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenShock.Common.DataAnnotations.Interfaces; using OpenShock.Common.Validation; @@ -12,7 +11,7 @@ namespace OpenShock.Common.DataAnnotations; /// /// Inherits from . /// -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] public sealed class UsernameAttribute : ValidationAttribute, IParameterAttribute { /// @@ -56,7 +55,7 @@ public void Apply(OpenApiSchema schema) { //if (ShouldValidate) schema.Pattern = ???; - schema.Example = new OpenApiString(ExampleValue); + schema.Example = ExampleValue; } /// diff --git a/Common/Swagger/AttributeFilter.cs b/Common/Swagger/AttributeFilter.cs index 8634be86..191b2e32 100644 --- a/Common/Swagger/AttributeFilter.cs +++ b/Common/Swagger/AttributeFilter.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenShock.Common.Authentication; using OpenShock.Common.DataAnnotations.Interfaces; using Swashbuckle.AspNetCore.SwaggerGen; @@ -8,7 +8,7 @@ namespace OpenShock.Common.Swagger; public sealed class AttributeFilter : ISchemaFilter, IParameterFilter, IOperationFilter { - public void Apply(OpenApiParameter parameter, ParameterFilterContext context) + public void Apply(IOpenApiParameter parameter, ParameterFilterContext context) { // Apply OpenShock Parameter Attributes foreach (var attribute in context.ParameterInfo?.GetCustomAttributes(true).OfType() ?? []) @@ -23,7 +23,7 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context) } } - public void Apply(OpenApiSchema schema, SchemaFilterContext context) + public void Apply(IOpenApiSchema schema, SchemaFilterContext context) { // Apply OpenShock Parameter Attributes foreach (var attribute in context.MemberInfo?.GetCustomAttributes(true).OfType() ?? []) diff --git a/Common/Swagger/SwaggerGenExtensions.cs b/Common/Swagger/SwaggerGenExtensions.cs index 9eabfbc9..422cdac1 100644 --- a/Common/Swagger/SwaggerGenExtensions.cs +++ b/Common/Swagger/SwaggerGenExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenShock.Common.Constants; using OpenShock.Common.DataAnnotations; using OpenShock.Common.Models; diff --git a/Common/Utils/ConfigureSwaggerOptions.cs b/Common/Utils/ConfigureSwaggerOptions.cs index 2dbc2694..dedb3b5b 100644 --- a/Common/Utils/ConfigureSwaggerOptions.cs +++ b/Common/Utils/ConfigureSwaggerOptions.cs @@ -1,6 +1,6 @@ using Asp.Versioning.ApiExplorer; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; namespace OpenShock.Common.Utils; From b669fd27280e4db6b2c7a4d4141d09024e6341e8 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 11 Nov 2025 22:23:59 +0100 Subject: [PATCH 20/29] This is gonna be alot of work --- API/Program.cs | 4 +- Common/Common.csproj | 2 +- .../DataAnnotations/EmailAddressAttribute.cs | 15 +-- .../Interfaces/IOperationAttribute.cs | 15 --- .../Interfaces/IParameterAttribute.cs | 21 ---- Common/DataAnnotations/OpenApiSchemas.cs | 32 ----- Common/DataAnnotations/PasswordAttribute.cs | 15 +-- .../StringCollectionItemMaxLengthAttribute.cs | 2 +- Common/DataAnnotations/UsernameAttribute.cs | 15 +-- Common/OpenAPI/DocumentDefaults.cs | 73 +++++++++++ Common/OpenAPI/OpenApiExtensions.cs | 37 ++++++ Common/OpenShockMiddlewareHelper.cs | 3 +- Common/Swagger/AttributeFilter.cs | 118 ------------------ Common/Swagger/SwaggerGenExtensions.cs | 101 --------------- Common/Utils/ConfigureSwaggerOptions.cs | 39 ------ Cron/Program.cs | 4 +- Directory.Build.props | 1 + Directory.Packages.props | 2 +- LiveControlGateway/Program.cs | 4 +- 19 files changed, 125 insertions(+), 378 deletions(-) delete mode 100644 Common/DataAnnotations/Interfaces/IOperationAttribute.cs delete mode 100644 Common/DataAnnotations/Interfaces/IParameterAttribute.cs delete mode 100644 Common/DataAnnotations/OpenApiSchemas.cs create mode 100644 Common/OpenAPI/DocumentDefaults.cs create mode 100644 Common/OpenAPI/OpenApiExtensions.cs delete mode 100644 Common/Swagger/AttributeFilter.cs delete mode 100644 Common/Swagger/SwaggerGenExtensions.cs delete mode 100644 Common/Utils/ConfigureSwaggerOptions.cs diff --git a/API/Program.cs b/API/Program.cs index 014c094e..91db806e 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -11,11 +11,11 @@ using OpenShock.Common; using OpenShock.Common.Extensions; using OpenShock.Common.Hubs; +using OpenShock.Common.OpenAPI; using OpenShock.Common.Services; using OpenShock.Common.Services.Device; using OpenShock.Common.Services.LCGNodeProvisioner; using OpenShock.Common.Services.Ota; -using OpenShock.Common.Swagger; using Serilog; using OAuthConstants = OpenShock.API.OAuth.OAuthConstants; @@ -103,7 +103,7 @@ static void DefaultOptions(RemoteAuthenticationOptions options, string provider) builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.AddSwaggerExt(); +builder.Services.AddOpenApiExt(); builder.AddCloudflareTurnstileService(); builder.AddEmailService(); diff --git a/Common/Common.csproj b/Common/Common.csproj index 8c6c3174..accb64d8 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -13,6 +13,7 @@ + @@ -31,7 +32,6 @@ - diff --git a/Common/DataAnnotations/EmailAddressAttribute.cs b/Common/DataAnnotations/EmailAddressAttribute.cs index 030314dc..475a813a 100644 --- a/Common/DataAnnotations/EmailAddressAttribute.cs +++ b/Common/DataAnnotations/EmailAddressAttribute.cs @@ -1,8 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.Net.Mail; -using Microsoft.OpenApi; using OpenShock.Common.Constants; -using OpenShock.Common.DataAnnotations.Interfaces; namespace OpenShock.Common.DataAnnotations; @@ -13,7 +11,7 @@ namespace OpenShock.Common.DataAnnotations; /// Inherits from . /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] -public sealed class EmailAddressAttribute : ValidationAttribute, IParameterAttribute +public sealed class EmailAddressAttribute : ValidationAttribute { /// /// Example value used to generate OpenApi documentation. @@ -54,15 +52,4 @@ public sealed class EmailAddressAttribute : ValidationAttribute, IParameterAttri return ValidationResult.Success; } - - /// - public void Apply(OpenApiSchema schema) - { - //if (ShouldValidate) schema.Pattern = ???; - - schema.Example = ExampleValue; - } - - /// - public void Apply(OpenApiParameter parameter) => Apply(parameter.Schema); } \ No newline at end of file diff --git a/Common/DataAnnotations/Interfaces/IOperationAttribute.cs b/Common/DataAnnotations/Interfaces/IOperationAttribute.cs deleted file mode 100644 index 6345cb6f..00000000 --- a/Common/DataAnnotations/Interfaces/IOperationAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.OpenApi; - -namespace OpenShock.Common.DataAnnotations.Interfaces; - -/// -/// Represents an interface for operation attributes that can be applied to an OpenApiOperation instance. -/// -public interface IOperationAttribute -{ - /// - /// Applies the operation attribute to the given OpenApiOperation instance. - /// - /// The OpenApiOperation instance to apply the attribute to. - void Apply(OpenApiOperation operation); -} \ No newline at end of file diff --git a/Common/DataAnnotations/Interfaces/IParameterAttribute.cs b/Common/DataAnnotations/Interfaces/IParameterAttribute.cs deleted file mode 100644 index 896446fc..00000000 --- a/Common/DataAnnotations/Interfaces/IParameterAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.OpenApi; - -namespace OpenShock.Common.DataAnnotations.Interfaces; - -/// -/// Represents an interface for parameter attributes that can be applied to an OpenApiSchema or OpenApiParameter instance. -/// -public interface IParameterAttribute -{ - /// - /// Applies the parameter attribute to the given OpenApiSchema instance. - /// - /// The OpenApiSchema instance to apply the attribute to. - void Apply(OpenApiSchema schema); - - /// - /// Applies the parameter attribute to the given OpenApiParameter instance. - /// - /// The OpenApiParameter instance to apply the attribute to. - void Apply(OpenApiParameter parameter); -} \ No newline at end of file diff --git a/Common/DataAnnotations/OpenApiSchemas.cs b/Common/DataAnnotations/OpenApiSchemas.cs deleted file mode 100644 index 03ac265f..00000000 --- a/Common/DataAnnotations/OpenApiSchemas.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.OpenApi; -using OpenShock.Common.Models; - -namespace OpenShock.Common.DataAnnotations; - -public static class OpenApiSchemas -{ - public static OpenApiSchema SemVerSchema => new() - { - Title = "SemVer", - Type = JsonSchemaType.String, - Pattern = /* lang=regex */ @"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", - Example = "1.0.0-dev+a16f2" - }; - - public static OpenApiSchema PauseReasonEnumSchema => new() - { - Title = nameof(PauseReason), - Type = JsonSchemaType.Integer, - Description = """ - An integer representing the reason(s) for the shocker being paused, expressed as a bitfield where reasons are OR'd together. - - Each bit corresponds to: - - 1: Shocker - - 2: UserShare - - 4: PublicShare - - For example, a value of 6 (2 | 4) indicates both 'UserShare' and 'PublicShare' reasons. - """, - Example = 6 - }; -} diff --git a/Common/DataAnnotations/PasswordAttribute.cs b/Common/DataAnnotations/PasswordAttribute.cs index dd4b95b3..b6dca5bd 100644 --- a/Common/DataAnnotations/PasswordAttribute.cs +++ b/Common/DataAnnotations/PasswordAttribute.cs @@ -1,7 +1,5 @@ using System.ComponentModel.DataAnnotations; -using Microsoft.OpenApi; using OpenShock.Common.Constants; -using OpenShock.Common.DataAnnotations.Interfaces; namespace OpenShock.Common.DataAnnotations; @@ -12,7 +10,7 @@ namespace OpenShock.Common.DataAnnotations; /// Inherits from . /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] -public sealed class PasswordAttribute : ValidationAttribute, IParameterAttribute +public sealed class PasswordAttribute : ValidationAttribute { /// /// Example value used to generate OpenApi documentation. @@ -53,15 +51,4 @@ public sealed class PasswordAttribute : ValidationAttribute, IParameterAttribute return ValidationResult.Success; } - - /// - public void Apply(OpenApiSchema schema) - { - //if (ShouldValidate) schema.Pattern = ???; - - schema.Example = ExampleValue; - } - - /// - public void Apply(OpenApiParameter parameter) => Apply(parameter.Schema); } \ No newline at end of file diff --git a/Common/DataAnnotations/StringCollectionItemMaxLengthAttribute.cs b/Common/DataAnnotations/StringCollectionItemMaxLengthAttribute.cs index 08cb6eea..55fd7113 100644 --- a/Common/DataAnnotations/StringCollectionItemMaxLengthAttribute.cs +++ b/Common/DataAnnotations/StringCollectionItemMaxLengthAttribute.cs @@ -2,7 +2,7 @@ namespace OpenShock.Common.DataAnnotations; -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public sealed class StringCollectionItemMaxLengthAttribute : ValidationAttribute { public StringCollectionItemMaxLengthAttribute(int maxLength) diff --git a/Common/DataAnnotations/UsernameAttribute.cs b/Common/DataAnnotations/UsernameAttribute.cs index 29a774dd..03572ed2 100644 --- a/Common/DataAnnotations/UsernameAttribute.cs +++ b/Common/DataAnnotations/UsernameAttribute.cs @@ -1,6 +1,4 @@ using System.ComponentModel.DataAnnotations; -using Microsoft.OpenApi; -using OpenShock.Common.DataAnnotations.Interfaces; using OpenShock.Common.Validation; namespace OpenShock.Common.DataAnnotations; @@ -12,7 +10,7 @@ namespace OpenShock.Common.DataAnnotations; /// Inherits from . /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] -public sealed class UsernameAttribute : ValidationAttribute, IParameterAttribute +public sealed class UsernameAttribute : ValidationAttribute { /// /// Example value used to generate OpenApi documentation. @@ -49,15 +47,4 @@ public sealed class UsernameAttribute : ValidationAttribute, IParameterAttribute error => new ValidationResult($"{error.Type} - {error.Message}") ); } - - /// - public void Apply(OpenApiSchema schema) - { - //if (ShouldValidate) schema.Pattern = ???; - - schema.Example = ExampleValue; - } - - /// - public void Apply(OpenApiParameter parameter) => Apply(parameter.Schema); } \ No newline at end of file diff --git a/Common/OpenAPI/DocumentDefaults.cs b/Common/OpenAPI/DocumentDefaults.cs new file mode 100644 index 00000000..54764ce6 --- /dev/null +++ b/Common/OpenAPI/DocumentDefaults.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi; + +namespace OpenShock.Common.OpenAPI; + +public static class DocumentDefaults +{ + public static Func GetDocumentTransformer(string version) + { + return (document, context, _) => + { + var env = context.ApplicationServices.GetRequiredService(); + + document.Info = new OpenApiInfo + { + Title = "OpenShock API", + // Summary = ... + // Description = ... + Version = version, + // TermsOfService = ... + // Contact = ... + // License = ... + }; + + document.Servers = + [ + new OpenApiServer { Url = "https://api.openshock.app" }, + new OpenApiServer { Url = "https://api.openshock.dev" } + ]; + if (env.IsDevelopment()) + { + document.Servers.Add(new OpenApiServer { Url = "https://localhost" }); + } + + document.Components ??= new OpenApiComponents(); + document.Components.SecuritySchemes = new Dictionary + { + { + "ApiToken", + new OpenApiSecurityScheme + { + Name = "OpenShockToken", + Description = "Enter API Token", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header + } + }, + { + "HubToken", + new OpenApiSecurityScheme + { + Name = "DeviceToken", + Description = "Enter hub token", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header + } + }, + { + "UserSessionCookie", + new OpenApiSecurityScheme + { + Name = "openShockSession", + Description = "Enter user session cookie", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Cookie + } + } + }; + + return Task.CompletedTask; + }; + } +} \ No newline at end of file diff --git a/Common/OpenAPI/OpenApiExtensions.cs b/Common/OpenAPI/OpenApiExtensions.cs new file mode 100644 index 00000000..6c03fca9 --- /dev/null +++ b/Common/OpenAPI/OpenApiExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.OpenApi; + +namespace OpenShock.Common.OpenAPI; + +public static class OpenApiExtensions +{ + public static IServiceCollection AddOpenApiExt(this IServiceCollection services) where TProgram : class + { + var assembly = typeof(TProgram).Assembly; + + string assemblyName = assembly + .GetName() + .Name ?? throw new NullReferenceException("Assembly name"); + + services.AddOutputCache(options => + { + options.AddPolicy("OpenAPI", policy => policy.Expire(TimeSpan.FromMinutes(10))); + }); + services.AddOpenApi("v1", options => + { + options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; + options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "1")); + }); + services.AddOpenApi("v2", options => + { + options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; + options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "2")); + }); + services.AddOpenApi("internal", options => + { + options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; + options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "1")); + }); + + return services; + } +} \ No newline at end of file diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 1c3fd86b..85b6299e 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -79,7 +79,8 @@ public static async Task UseCommonOpenShockMiddleware(this return remoteIp is not null && metricsAllowedIpNetworks.Any(x => x.Contains(remoteIp)); }); - app.UseSwagger(); + app.MapOpenApi() + .CacheOutput("OpenAPI"); app.MapScalarApiReference("/scalar/viewer", options => options diff --git a/Common/Swagger/AttributeFilter.cs b/Common/Swagger/AttributeFilter.cs deleted file mode 100644 index 191b2e32..00000000 --- a/Common/Swagger/AttributeFilter.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.OpenApi; -using OpenShock.Common.Authentication; -using OpenShock.Common.DataAnnotations.Interfaces; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace OpenShock.Common.Swagger; - -public sealed class AttributeFilter : ISchemaFilter, IParameterFilter, IOperationFilter -{ - public void Apply(IOpenApiParameter parameter, ParameterFilterContext context) - { - // Apply OpenShock Parameter Attributes - foreach (var attribute in context.ParameterInfo?.GetCustomAttributes(true).OfType() ?? []) - { - attribute.Apply(parameter); - } - - // Apply OpenShock Parameter Attributes - foreach (var attribute in context.PropertyInfo?.GetCustomAttributes(true).OfType() ?? []) - { - attribute.Apply(parameter); - } - } - - public void Apply(IOpenApiSchema schema, SchemaFilterContext context) - { - // Apply OpenShock Parameter Attributes - foreach (var attribute in context.MemberInfo?.GetCustomAttributes(true).OfType() ?? []) - { - attribute.Apply(schema); - } - } - - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Apply OpenShock Parameter Attributes - foreach (var attribute in context.MethodInfo?.GetCustomAttributes(true).OfType() ?? []) - { - attribute.Apply(operation); - } - - // Get Authorize attribute - var attributes = context.MethodInfo?.DeclaringType?.GetCustomAttributes(true) - .Union(context.MethodInfo.GetCustomAttributes(true)) - .OfType() - .ToArray() ?? []; - - if (attributes.Length != 0) - { - if (attributes.Count(attr => !string.IsNullOrEmpty(attr.AuthenticationSchemes)) > 1) throw new Exception("Dunno what to apply to this method (multiple authentication attributes with schemes set)"); - - var scheme = attributes.Select(attr => attr.AuthenticationSchemes).SingleOrDefault(scheme => !string.IsNullOrEmpty(scheme)); - var roles = attributes.Select(attr => attr.Roles).Where(roles => !string.IsNullOrEmpty(roles)).SelectMany(roles => roles!.Split(',')).Select(role => role.Trim()).ToArray(); - var policies = attributes.Select(attr => attr.Policy).Where(policies => !string.IsNullOrEmpty(policies)).SelectMany(policies => policies!.Split(',')).Select(policy => policy.Trim()).ToArray(); - - // Add what should be show inside the security section - List securityInfos = []; - if (!string.IsNullOrEmpty(scheme)) securityInfos.Add($"{nameof(AuthorizeAttribute.AuthenticationSchemes)}:{scheme}"); - if (roles.Length > 0) securityInfos.Add($"{nameof(AuthorizeAttribute.Roles)}:{string.Join(',', roles)}"); - if (policies.Length > 0) securityInfos.Add($"{nameof(AuthorizeAttribute.Policy)}:{string.Join(',', policies)}"); - - List securityRequirements = []; - foreach (var authenticationScheme in scheme?.Split(',').Select(s => s.Trim()) ?? []) - { - securityRequirements.AddRange(authenticationScheme switch - { - OpenShockAuthSchemes.UserSessionCookie => [ - new OpenApiSecurityRequirement {{ - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemes.UserSessionCookie, - Type = ReferenceType.SecurityScheme, - } - }, - securityInfos - }} - ], - OpenShockAuthSchemes.ApiToken => [ - new OpenApiSecurityRequirement {{ - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemes.ApiToken, - Type = ReferenceType.SecurityScheme, - } - }, - securityInfos - }} - ], - OpenShockAuthSchemes.HubToken => [ - new OpenApiSecurityRequirement {{ - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemes.HubToken, - Type = ReferenceType.SecurityScheme - } - }, - securityInfos - }} - ], - _ => [], - }); - } - - operation.Security = securityRequirements; - } - else - { - operation.Security.Clear(); - } - } -} diff --git a/Common/Swagger/SwaggerGenExtensions.cs b/Common/Swagger/SwaggerGenExtensions.cs deleted file mode 100644 index 422cdac1..00000000 --- a/Common/Swagger/SwaggerGenExtensions.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Microsoft.OpenApi; -using OpenShock.Common.Constants; -using OpenShock.Common.DataAnnotations; -using OpenShock.Common.Models; -using OpenShock.Common.Utils; -using Asp.Versioning; -using OpenShock.Common.Extensions; -using OpenShock.Common.Authentication; - -namespace OpenShock.Common.Swagger; - -public static class SwaggerGenExtensions -{ - public static IServiceCollection AddSwaggerExt(this WebApplicationBuilder builder) where TProgram : class - { - var assembly = typeof(TProgram).Assembly; - - string assemblyName = assembly - .GetName() - .Name ?? throw new NullReferenceException("Assembly name"); - - var versions = assembly.GetAllControllerEndpointAttributes() - .SelectMany(type => type.Versions) - .Select(v => v.ToString()) - .ToHashSet() - .OrderBy(v => v) - .ToArray(); - - if (versions.Any(v => !int.TryParse(v, out _))) - { - throw new InvalidDataException($"Found invalid API versions: [{string.Join(", ", versions.Where(v => !int.TryParse(v, out _)))}]"); - } - - return builder.Services - .AddSwaggerGen(options => - { - options.CustomOperationIds(e => - $"{e.ActionDescriptor.RouteValues["controller"]}_{e.ActionDescriptor.AttributeRouteInfo?.Name ?? e.ActionDescriptor.RouteValues["action"]}"); - options.SchemaFilter(); - options.ParameterFilter(); - options.OperationFilter(); - options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, assemblyName + ".xml"), true); - options.AddSecurityDefinition(OpenShockAuthSchemes.UserSessionCookie, new OpenApiSecurityScheme - { - Name = AuthConstants.UserSessionCookieName, - Description = "Enter user session cookie", - In = ParameterLocation.Cookie, - Type = SecuritySchemeType.ApiKey, - Scheme = OpenShockAuthSchemes.UserSessionCookie, - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemes.UserSessionCookie, - Type = ReferenceType.SecurityScheme, - } - }); - options.AddSecurityDefinition(OpenShockAuthSchemes.ApiToken, new OpenApiSecurityScheme - { - Name = AuthConstants.ApiTokenHeaderName, - Description = "Enter API Token", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey, - Scheme = OpenShockAuthSchemes.ApiToken, - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemes.ApiToken, - Type = ReferenceType.SecurityScheme, - } - }); - options.AddSecurityDefinition(OpenShockAuthSchemes.HubToken, new OpenApiSecurityScheme - { - Name = AuthConstants.HubTokenHeaderName, - Description = "Enter hub token", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey, - Scheme = OpenShockAuthSchemes.HubToken, - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemes.HubToken, - Type = ReferenceType.SecurityScheme, - } - }); - options.AddServer(new OpenApiServer { Url = "https://api.openshock.app" }); - options.AddServer(new OpenApiServer { Url = "https://api.openshock.dev" }); - if (builder.Environment.IsDevelopment()) - { - options.AddServer(new OpenApiServer { Url = "https://localhost" }); - } - - foreach (var version in versions) - { - options.SwaggerDoc("v" + version, new OpenApiInfo { Title = "OpenShock", Version = version }); - } - options.MapType(() => OpenApiSchemas.SemVerSchema); - options.MapType(() => OpenApiSchemas.PauseReasonEnumSchema); - - // Avoid nullable strings everywhere - options.SupportNonNullableReferenceTypes(); - }) - .ConfigureOptions(); - } -} diff --git a/Common/Utils/ConfigureSwaggerOptions.cs b/Common/Utils/ConfigureSwaggerOptions.cs deleted file mode 100644 index dedb3b5b..00000000 --- a/Common/Utils/ConfigureSwaggerOptions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Asp.Versioning.ApiExplorer; -using Microsoft.Extensions.Options; -using Microsoft.OpenApi; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace OpenShock.Common.Utils; - -public sealed class ConfigureSwaggerOptions : IConfigureNamedOptions -{ - private readonly IApiVersionDescriptionProvider _provider; - - public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) - { - _provider = provider; - } - - public void Configure(SwaggerGenOptions options) - { - // add swagger document for every API version discovered - foreach (var description in _provider.ApiVersionDescriptions) - options.SwaggerDoc( - description.GroupName, - CreateVersionInfo(description)); - } - - public void Configure(string? name, SwaggerGenOptions options) => Configure(options); - - private static OpenApiInfo CreateVersionInfo( - ApiVersionDescription description) - { - var info = new OpenApiInfo - { - Title = "OpenShock.API", - Version = description.ApiVersion.ToString() - }; - if (description.IsDeprecated) info.Description += " This API version has been deprecated."; - return info; - } -} \ No newline at end of file diff --git a/Cron/Program.cs b/Cron/Program.cs index 43b5cbb2..a9f9d81d 100644 --- a/Cron/Program.cs +++ b/Cron/Program.cs @@ -2,9 +2,9 @@ using Hangfire.PostgreSql; using OpenShock.Common; using OpenShock.Common.Extensions; +using OpenShock.Common.OpenAPI; using OpenShock.Cron; using OpenShock.Cron.Utils; -using OpenShock.Common.Swagger; var builder = OpenShockApplication.CreateDefaultBuilder(args); @@ -21,7 +21,7 @@ c.UseNpgsqlConnection(databaseOptions.Conn))); builder.Services.AddHangfireServer(); -builder.AddSwaggerExt(); +builder.Services.AddOpenApiExt(); var app = builder.Build(); diff --git a/Directory.Build.props b/Directory.Build.props index 4491cfc4..57517237 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,6 +35,7 @@ $(Version) a2109c1e-fb11-44d7-8127-346ef60cb9a5 true + $(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated diff --git a/Directory.Packages.props b/Directory.Packages.props index 9146d1d7..d33dd9cd 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,6 +18,7 @@ + @@ -37,7 +38,6 @@ - diff --git a/LiveControlGateway/Program.cs b/LiveControlGateway/Program.cs index 7632bb87..c5e06895 100644 --- a/LiveControlGateway/Program.cs +++ b/LiveControlGateway/Program.cs @@ -1,10 +1,10 @@ using Microsoft.Extensions.Options; using OpenShock.Common; using OpenShock.Common.Extensions; +using OpenShock.Common.OpenAPI; using OpenShock.Common.Services; using OpenShock.Common.Services.Device; using OpenShock.Common.Services.Ota; -using OpenShock.Common.Swagger; using OpenShock.LiveControlGateway; using OpenShock.LiveControlGateway.LifetimeManager; using OpenShock.LiveControlGateway.Options; @@ -30,7 +30,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.AddSwaggerExt(); +builder.Services.AddOpenApiExt(); //services.AddHealthChecks().AddCheck("database"); From ae70c50f9d0a84ea4f08028961cb3a66f7ce6e26 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 11 Nov 2025 22:26:51 +0100 Subject: [PATCH 21/29] Oops --- API/Program.cs | 2 +- Common/OpenAPI/OpenApiExtensions.cs | 12 ++++++------ Cron/Program.cs | 2 +- LiveControlGateway/Program.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/API/Program.cs b/API/Program.cs index 91db806e..c5f707df 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -103,7 +103,7 @@ static void DefaultOptions(RemoteAuthenticationOptions options, string provider) builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddOpenApiExt(); +builder.AddOpenApiExt(); builder.AddCloudflareTurnstileService(); builder.AddEmailService(); diff --git a/Common/OpenAPI/OpenApiExtensions.cs b/Common/OpenAPI/OpenApiExtensions.cs index 6c03fca9..b3700051 100644 --- a/Common/OpenAPI/OpenApiExtensions.cs +++ b/Common/OpenAPI/OpenApiExtensions.cs @@ -4,7 +4,7 @@ namespace OpenShock.Common.OpenAPI; public static class OpenApiExtensions { - public static IServiceCollection AddOpenApiExt(this IServiceCollection services) where TProgram : class + public static IServiceCollection AddOpenApiExt(this WebApplicationBuilder builder) where TProgram : class { var assembly = typeof(TProgram).Assembly; @@ -12,26 +12,26 @@ public static IServiceCollection AddOpenApiExt(this IServiceCollection .GetName() .Name ?? throw new NullReferenceException("Assembly name"); - services.AddOutputCache(options => + builder.Services.AddOutputCache(options => { options.AddPolicy("OpenAPI", policy => policy.Expire(TimeSpan.FromMinutes(10))); }); - services.AddOpenApi("v1", options => + builder.Services.AddOpenApi("v1", options => { options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "1")); }); - services.AddOpenApi("v2", options => + builder.Services.AddOpenApi("v2", options => { options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "2")); }); - services.AddOpenApi("internal", options => + builder.Services.AddOpenApi("internal", options => { options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "1")); }); - return services; + return builder.Services; } } \ No newline at end of file diff --git a/Cron/Program.cs b/Cron/Program.cs index a9f9d81d..011ffaab 100644 --- a/Cron/Program.cs +++ b/Cron/Program.cs @@ -21,7 +21,7 @@ c.UseNpgsqlConnection(databaseOptions.Conn))); builder.Services.AddHangfireServer(); -builder.Services.AddOpenApiExt(); +builder.AddOpenApiExt(); var app = builder.Build(); diff --git a/LiveControlGateway/Program.cs b/LiveControlGateway/Program.cs index c5e06895..d3859077 100644 --- a/LiveControlGateway/Program.cs +++ b/LiveControlGateway/Program.cs @@ -30,7 +30,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddOpenApiExt(); +builder.AddOpenApiExt(); //services.AddHealthChecks().AddCheck("database"); From 1d1ee8c8ea8a012bcb1cfc773b2756dd2c5297c1 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 12 Nov 2025 11:16:56 +0100 Subject: [PATCH 22/29] Update Directory.Packages.props --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d33dd9cd..b27da062 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + From 89dadb941f4b29df031d0f6ebfdc29878dc8bce2 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 12 Nov 2025 12:06:38 +0100 Subject: [PATCH 23/29] More changes --- API/Controller/Account/Activate.cs | 1 + API/Controller/Account/CheckUsername.cs | 1 + API/Controller/Account/Login.cs | 1 + API/Controller/Account/LoginV2.cs | 1 + API/Controller/Account/Logout.cs | 1 + API/Controller/Account/PasswordResetCheckValid.cs | 1 + API/Controller/Account/PasswordResetComplete.cs | 1 + API/Controller/Account/PasswordResetInitiate.cs | 1 + API/Controller/Account/PasswordResetInitiateV2.cs | 1 + API/Controller/Account/Signup.cs | 1 + API/Controller/Account/SignupV2.cs | 1 + API/Controller/Account/VerifyEmail.cs | 1 + API/Controller/Account/_ApiController.cs | 3 +-- API/Controller/Admin/_ApiController.cs | 1 + API/Controller/Device/AssignLCG.cs | 1 + API/Controller/Device/AssignLCGV2.cs | 1 + API/Controller/Device/GetSelf.cs | 1 + API/Controller/Device/Pair.cs | 1 + API/Controller/Device/_ApiController.cs | 4 +--- API/Controller/Devices/DeviceOtaController.cs | 1 + API/Controller/Devices/DevicesController.cs | 1 + API/Controller/Devices/GetShockers.cs | 1 + API/Controller/Devices/_ApiController.cs | 3 +-- API/Controller/OAuth/Authorize.cs | 1 - API/Controller/OAuth/HandOff.cs | 1 - API/Controller/OAuth/SignupFinalize.cs | 1 - API/Controller/OAuth/SignupGetData.cs | 1 - API/Controller/OAuth/_ApiController.cs | 5 ++--- API/Controller/Public/GetStats.cs | 1 + API/Controller/Public/PublicShareController.cs | 3 ++- API/Controller/Sessions/DeleteSessions.cs | 1 + API/Controller/Sessions/ListSessions.cs | 1 + API/Controller/Sessions/SessionSelf.cs | 1 + API/Controller/Sessions/_ApiController.cs | 3 +-- Common/OpenAPI/OpenApiExtensions.cs | 11 +++++++++-- 35 files changed, 41 insertions(+), 19 deletions(-) diff --git a/API/Controller/Account/Activate.cs b/API/Controller/Account/Activate.cs index fdd484ed..29b8447d 100644 --- a/API/Controller/Account/Activate.cs +++ b/API/Controller/Account/Activate.cs @@ -16,6 +16,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] [MapToApiVersion("1")] + [EndpointGroupName("v1")] public async Task Activate([FromQuery(Name = "token")] string token, CancellationToken cancellationToken) { bool ok = await _accountService.TryActivateAccountAsync(token, cancellationToken); diff --git a/API/Controller/Account/CheckUsername.cs b/API/Controller/Account/CheckUsername.cs index 60df27fa..689b4a21 100644 --- a/API/Controller/Account/CheckUsername.cs +++ b/API/Controller/Account/CheckUsername.cs @@ -15,6 +15,7 @@ public sealed partial class AccountController /// /// [HttpPost("username/check")] // High-volume endpoint, we don't want to rate limit this + [EndpointGroupName("v1")] [Consumes(MediaTypeNames.Application.Json)] public async Task CheckUsername([FromBody] ChangeUsernameRequest body, CancellationToken cancellationToken) { diff --git a/API/Controller/Account/Login.cs b/API/Controller/Account/Login.cs index 6ec536da..59ed88e5 100644 --- a/API/Controller/Account/Login.cs +++ b/API/Controller/Account/Login.cs @@ -23,6 +23,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status401Unauthorized, MediaTypeNames.Application.ProblemJson)] // InvalidCredentials [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidDomain [MapToApiVersion("1")] + [EndpointGroupName("v1")] public async Task Login( [FromBody] Login body, CancellationToken cancellationToken) diff --git a/API/Controller/Account/LoginV2.cs b/API/Controller/Account/LoginV2.cs index 0fa3e807..c5849ad0 100644 --- a/API/Controller/Account/LoginV2.cs +++ b/API/Controller/Account/LoginV2.cs @@ -27,6 +27,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status401Unauthorized, MediaTypeNames.Application.ProblemJson)] // InvalidCredentials [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidDomain [MapToApiVersion("2")] + [EndpointGroupName("v2")] public async Task LoginV2( [FromBody] LoginV2 body, [FromServices] ICloudflareTurnstileService turnstileService, diff --git a/API/Controller/Account/Logout.cs b/API/Controller/Account/Logout.cs index 44a565d6..31d3271b 100644 --- a/API/Controller/Account/Logout.cs +++ b/API/Controller/Account/Logout.cs @@ -10,6 +10,7 @@ public sealed partial class AccountController [HttpPost("logout")] [ProducesResponseType(StatusCodes.Status200OK)] [MapToApiVersion("1")] + [EndpointGroupName("v1")] public async Task Logout([FromServices] ISessionService sessionService) { // Remove session if valid diff --git a/API/Controller/Account/PasswordResetCheckValid.cs b/API/Controller/Account/PasswordResetCheckValid.cs index d66f869d..044766f9 100644 --- a/API/Controller/Account/PasswordResetCheckValid.cs +++ b/API/Controller/Account/PasswordResetCheckValid.cs @@ -23,6 +23,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PasswordResetNotFound [MapToApiVersion("1")] + [EndpointGroupName("v1")] public async Task PasswordResetCheckValid([FromRoute] Guid passwordResetId, [FromRoute] string secret, CancellationToken cancellationToken) { var passwordResetExists = await _accountService.CheckPasswordResetExistsAsync(passwordResetId, secret, cancellationToken); diff --git a/API/Controller/Account/PasswordResetComplete.cs b/API/Controller/Account/PasswordResetComplete.cs index d9e58c93..a67b3041 100644 --- a/API/Controller/Account/PasswordResetComplete.cs +++ b/API/Controller/Account/PasswordResetComplete.cs @@ -24,6 +24,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PasswordResetNotFound [MapToApiVersion("1")] + [EndpointGroupName("v1")] public async Task PasswordResetComplete([FromRoute] Guid passwordResetId, [FromRoute] string secret, [FromBody] PasswordResetProcessData body) { diff --git a/API/Controller/Account/PasswordResetInitiate.cs b/API/Controller/Account/PasswordResetInitiate.cs index 945be724..91f18590 100644 --- a/API/Controller/Account/PasswordResetInitiate.cs +++ b/API/Controller/Account/PasswordResetInitiate.cs @@ -17,6 +17,7 @@ public sealed partial class AccountController [EnableRateLimiting("auth")] [Consumes(MediaTypeNames.Application.Json)] [MapToApiVersion("1")] + [EndpointGroupName("v1")] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task PasswordResetInitiate([FromBody] ResetRequest body) { diff --git a/API/Controller/Account/PasswordResetInitiateV2.cs b/API/Controller/Account/PasswordResetInitiateV2.cs index 2c9e1a76..8a28437d 100644 --- a/API/Controller/Account/PasswordResetInitiateV2.cs +++ b/API/Controller/Account/PasswordResetInitiateV2.cs @@ -23,6 +23,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] [MapToApiVersion("2")] + [EndpointGroupName("v1")] public async Task PasswordResetInitiateV2([FromBody] PasswordResetRequestV2 body, [FromServices] ICloudflareTurnstileService turnstileService, CancellationToken cancellationToken) { var turnStile = await turnstileService.VerifyUserResponseTokenAsync(body.TurnstileResponse, HttpContext.GetRemoteIP(), cancellationToken); diff --git a/API/Controller/Account/Signup.cs b/API/Controller/Account/Signup.cs index 4aea610d..53aa2a68 100644 --- a/API/Controller/Account/Signup.cs +++ b/API/Controller/Account/Signup.cs @@ -23,6 +23,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status409Conflict, MediaTypeNames.Application.ProblemJson)] // EmailOrUsernameAlreadyExists [MapToApiVersion("1")] + [EndpointGroupName("v1")] public async Task SignUp([FromBody] SignUp body) { var creationAction = await _accountService.CreateAccountWithoutActivationFlowLegacyAsync(body.Email, body.Username, body.Password); diff --git a/API/Controller/Account/SignupV2.cs b/API/Controller/Account/SignupV2.cs index 9f707bb0..4edc9942 100644 --- a/API/Controller/Account/SignupV2.cs +++ b/API/Controller/Account/SignupV2.cs @@ -29,6 +29,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status409Conflict, MediaTypeNames.Application.ProblemJson)] // EmailOrUsernameAlreadyExists [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidTurnstileResponse [MapToApiVersion("2")] + [EndpointGroupName("v2")] public async Task SignUpV2( [FromBody] SignUpV2 body, [FromServices] ICloudflareTurnstileService turnstileService, diff --git a/API/Controller/Account/VerifyEmail.cs b/API/Controller/Account/VerifyEmail.cs index f107bf40..aa11e4bf 100644 --- a/API/Controller/Account/VerifyEmail.cs +++ b/API/Controller/Account/VerifyEmail.cs @@ -15,6 +15,7 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] [MapToApiVersion("1")] + [EndpointGroupName("v1")] public async Task EmailVerify([FromQuery(Name = "token")] string token, CancellationToken cancellationToken) { bool ok = await _accountService.TryVerifyEmailAsync(token, cancellationToken); diff --git a/API/Controller/Account/_ApiController.cs b/API/Controller/Account/_ApiController.cs index a68582c7..da01487d 100644 --- a/API/Controller/Account/_ApiController.cs +++ b/API/Controller/Account/_ApiController.cs @@ -10,8 +10,7 @@ namespace OpenShock.API.Controller.Account; /// [ApiController] [Tags("Account")] -[ApiVersion("1"), ApiVersion("2")] -[Route("/{version:apiVersion}/account")] +[Route("/{version:apiVersion}/account"), ApiVersion("1"), ApiVersion("2")] public sealed partial class AccountController : OpenShockControllerBase { private readonly IAccountService _accountService; diff --git a/API/Controller/Admin/_ApiController.cs b/API/Controller/Admin/_ApiController.cs index 6bcb2d71..134ce0d5 100644 --- a/API/Controller/Admin/_ApiController.cs +++ b/API/Controller/Admin/_ApiController.cs @@ -9,6 +9,7 @@ namespace OpenShock.API.Controller.Admin; [ApiController] [Tags("Admin")] +[EndpointGroupName("admin")] [Route("/{version:apiVersion}/admin")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemes.UserSessionCookie, Roles = "Admin")] public sealed partial class AdminController : AuthenticatedSessionControllerBase diff --git a/API/Controller/Device/AssignLCG.cs b/API/Controller/Device/AssignLCG.cs index 77fabd18..b2fcf9f6 100644 --- a/API/Controller/Device/AssignLCG.cs +++ b/API/Controller/Device/AssignLCG.cs @@ -19,6 +19,7 @@ public sealed partial class DeviceController /// Unable to find suitable LCG node [HttpGet("assignLCG")] [MapToApiVersion("1")] + [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable, MediaTypeNames.Application.ProblemJson)] // NoLcgNodesAvailable public async Task GetLiveControlGateway([FromServices] ILCGNodeProvisioner geoLocation) diff --git a/API/Controller/Device/AssignLCGV2.cs b/API/Controller/Device/AssignLCGV2.cs index ddfd80ae..052191f8 100644 --- a/API/Controller/Device/AssignLCGV2.cs +++ b/API/Controller/Device/AssignLCGV2.cs @@ -18,6 +18,7 @@ public sealed partial class DeviceController /// Unable to find suitable LCG node [HttpGet("assignLCG")] [MapToApiVersion("2")] + [EndpointGroupName("v2")] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] // BadSchemaVersion [ProducesResponseType(StatusCodes.Status503ServiceUnavailable, MediaTypeNames.Application.ProblemJson)] // NoLcgNodesAvailable diff --git a/API/Controller/Device/GetSelf.cs b/API/Controller/Device/GetSelf.cs index 8ddaf3ed..81c90d03 100644 --- a/API/Controller/Device/GetSelf.cs +++ b/API/Controller/Device/GetSelf.cs @@ -17,6 +17,7 @@ public sealed partial class DeviceController /// The device information was successfully retrieved. [HttpGet("self")] [MapToApiVersion("1")] + [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task GetSelf() { diff --git a/API/Controller/Device/Pair.cs b/API/Controller/Device/Pair.cs index 3f3bf01b..6dcc08a5 100644 --- a/API/Controller/Device/Pair.cs +++ b/API/Controller/Device/Pair.cs @@ -21,6 +21,7 @@ public sealed partial class DeviceController /// No such pair code exists [AllowAnonymous] [MapToApiVersion("1")] + [EndpointGroupName("v1")] [HttpGet("pair/{pairCode}", Name = "Pair")] [HttpGet("~/{version:apiVersion}/pair/{pairCode}", Name = "Pair_DEPRECATED")] // Backwards compatibility [EnableRateLimiting("auth")] diff --git a/API/Controller/Device/_ApiController.cs b/API/Controller/Device/_ApiController.cs index 74c7a249..e00331dd 100644 --- a/API/Controller/Device/_ApiController.cs +++ b/API/Controller/Device/_ApiController.cs @@ -12,10 +12,8 @@ namespace OpenShock.API.Controller.Device; /// For devices (ESP's) /// [ApiController] -[ApiVersion("1")] -[ApiVersion("2")] [Tags("Hub Endpoints")] -[Route("/{version:apiVersion}/device")] +[Route("/{version:apiVersion}/device"), ApiVersion("1"), ApiVersion("2")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemes.HubToken)] public sealed partial class DeviceController : OpenShockControllerBase { diff --git a/API/Controller/Devices/DeviceOtaController.cs b/API/Controller/Devices/DeviceOtaController.cs index fa2de294..4827ea34 100644 --- a/API/Controller/Devices/DeviceOtaController.cs +++ b/API/Controller/Devices/DeviceOtaController.cs @@ -39,6 +39,7 @@ public DevicesOtaController(OpenShockContext db, ILogger logg /// Could not find device or you do not have access to it [HttpGet("{deviceId}/ota")] [MapToApiVersion("1")] + [EndpointGroupName("v1")] [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound public async Task GetOtaUpdateHistory([FromRoute] Guid deviceId, [FromServices] IOtaService otaService) diff --git a/API/Controller/Devices/DevicesController.cs b/API/Controller/Devices/DevicesController.cs index 036e7cb4..2d5f1d09 100644 --- a/API/Controller/Devices/DevicesController.cs +++ b/API/Controller/Devices/DevicesController.cs @@ -25,6 +25,7 @@ public sealed partial class DevicesController /// All devices for the current user [HttpGet] [MapToApiVersion("1")] + [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public IActionResult ListDevices() { diff --git a/API/Controller/Devices/GetShockers.cs b/API/Controller/Devices/GetShockers.cs index bbff7340..45dcbbe5 100644 --- a/API/Controller/Devices/GetShockers.cs +++ b/API/Controller/Devices/GetShockers.cs @@ -21,6 +21,7 @@ public sealed partial class DevicesController [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [MapToApiVersion("1")] + [EndpointGroupName("v1")] public async Task GetShockers([FromRoute] Guid deviceId) { var deviceExists = await _db.Devices.AnyAsync(x => x.OwnerId == CurrentUser.Id && x.Id == deviceId); diff --git a/API/Controller/Devices/_ApiController.cs b/API/Controller/Devices/_ApiController.cs index c123f6f3..76fc274c 100644 --- a/API/Controller/Devices/_ApiController.cs +++ b/API/Controller/Devices/_ApiController.cs @@ -13,8 +13,7 @@ namespace OpenShock.API.Controller.Devices; /// [ApiController] [Tags("Hub Management")] -[ApiVersion("1"), ApiVersion("2")] -[Route("/{version:apiVersion}/devices")] +[Route("/{version:apiVersion}/devices"), ApiVersion("1"), ApiVersion("2")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemes.UserSessionApiTokenCombo)] public sealed partial class DevicesController : AuthenticatedSessionControllerBase { diff --git a/API/Controller/OAuth/Authorize.cs b/API/Controller/OAuth/Authorize.cs index 05de0fdd..60ebf430 100644 --- a/API/Controller/OAuth/Authorize.cs +++ b/API/Controller/OAuth/Authorize.cs @@ -20,7 +20,6 @@ public sealed partial class OAuthController /// Unsupported or misconfigured provider. [EnableRateLimiting("auth")] [HttpPost("{provider}/authorize")] - [ApiExplorerSettings(IgnoreApi = true)] public async Task OAuthAuthorize([FromRoute] string provider, [FromQuery(Name="flow"), Required] OAuthFlow flow) { if (!await _schemeProvider.IsSupportedOAuthScheme(provider)) diff --git a/API/Controller/OAuth/HandOff.cs b/API/Controller/OAuth/HandOff.cs index 6d673f85..7c3e20ad 100644 --- a/API/Controller/OAuth/HandOff.cs +++ b/API/Controller/OAuth/HandOff.cs @@ -21,7 +21,6 @@ public sealed partial class OAuthController /// [EnableRateLimiting("auth")] [HttpGet("{provider}/handoff")] - [ApiExplorerSettings(IgnoreApi = true)] public async Task OAuthHandOff( [FromRoute] string provider, [FromServices] IOAuthConnectionService connectionService, diff --git a/API/Controller/OAuth/SignupFinalize.cs b/API/Controller/OAuth/SignupFinalize.cs index 4f0d5ef4..c5dc2127 100644 --- a/API/Controller/OAuth/SignupFinalize.cs +++ b/API/Controller/OAuth/SignupFinalize.cs @@ -26,7 +26,6 @@ public sealed partial class OAuthController /// [EnableRateLimiting("auth")] [HttpPost("{provider}/signup-finalize")] - [ApiExplorerSettings(IgnoreApi = true)] public async Task OAuthSignupFinalize( [FromRoute] string provider, [FromBody] OAuthFinalizeRequest body, diff --git a/API/Controller/OAuth/SignupGetData.cs b/API/Controller/OAuth/SignupGetData.cs index 0bfc3b47..3abfcb70 100644 --- a/API/Controller/OAuth/SignupGetData.cs +++ b/API/Controller/OAuth/SignupGetData.cs @@ -22,7 +22,6 @@ public sealed partial class OAuthController [ResponseCache(NoStore = true)] [EnableRateLimiting("auth")] [HttpGet("{provider}/signup-data")] - [ApiExplorerSettings(IgnoreApi = true)] public async Task OAuthSignupGetData([FromRoute] string provider) { if (User.HasOpenShockUserIdentity()) diff --git a/API/Controller/OAuth/_ApiController.cs b/API/Controller/OAuth/_ApiController.cs index 05486d31..4251a97b 100644 --- a/API/Controller/OAuth/_ApiController.cs +++ b/API/Controller/OAuth/_ApiController.cs @@ -13,9 +13,8 @@ namespace OpenShock.API.Controller.OAuth; /// OAuth management endpoints (provider listing, authorize, data handoff). /// [ApiController] -[Tags("OAuth")] -[ApiVersion("1")] -[Route("/{version:apiVersion}/oauth")] +[EndpointGroupName("oauth")] +[Route("/{version:apiVersion}/oauth"), ApiVersion("1")] public sealed partial class OAuthController : OpenShockControllerBase { private readonly IAccountService _accountService; diff --git a/API/Controller/Public/GetStats.cs b/API/Controller/Public/GetStats.cs index ff07f15c..dad82e3c 100644 --- a/API/Controller/Public/GetStats.cs +++ b/API/Controller/Public/GetStats.cs @@ -15,6 +15,7 @@ public sealed partial class PublicController /// The statistics were successfully retrieved. [Tags("Meta")] [HttpGet("stats")] + [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task GetOnlineDevicesStatistics([FromServices] IConnectionMultiplexer redisConnectionMultiplexer) { diff --git a/API/Controller/Public/PublicShareController.cs b/API/Controller/Public/PublicShareController.cs index fdcfc560..f1354700 100644 --- a/API/Controller/Public/PublicShareController.cs +++ b/API/Controller/Public/PublicShareController.cs @@ -18,8 +18,9 @@ public sealed partial class PublicController /// /// The public share information was successfully retrieved. /// The public share does not exist. - [HttpGet("shares/links/{publicShareId}")] [Tags("Public Shocker Shares")] + [HttpGet("shares/links/{publicShareId}")] + [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PublicShareNotFound public async Task GetPublicShare([FromRoute] Guid publicShareId) diff --git a/API/Controller/Sessions/DeleteSessions.cs b/API/Controller/Sessions/DeleteSessions.cs index 74c5b87c..7a66bf23 100644 --- a/API/Controller/Sessions/DeleteSessions.cs +++ b/API/Controller/Sessions/DeleteSessions.cs @@ -10,6 +10,7 @@ namespace OpenShock.API.Controller.Sessions; public sealed partial class SessionsController { [HttpDelete("{sessionId}")] + [EndpointGroupName("v1")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // SessionNotFound public async Task DeleteSession([FromRoute] Guid sessionId) diff --git a/API/Controller/Sessions/ListSessions.cs b/API/Controller/Sessions/ListSessions.cs index 964b1c26..dab8877b 100644 --- a/API/Controller/Sessions/ListSessions.cs +++ b/API/Controller/Sessions/ListSessions.cs @@ -7,6 +7,7 @@ namespace OpenShock.API.Controller.Sessions; public sealed partial class SessionsController { [HttpGet] + [EndpointGroupName("v1")] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public IAsyncEnumerable ListSessions() { diff --git a/API/Controller/Sessions/SessionSelf.cs b/API/Controller/Sessions/SessionSelf.cs index 53995c33..e39e7d11 100644 --- a/API/Controller/Sessions/SessionSelf.cs +++ b/API/Controller/Sessions/SessionSelf.cs @@ -13,6 +13,7 @@ public sealed partial class SessionsController /// /// [HttpGet("self")] + [EndpointGroupName("v1")] public LoginSessionResponse GetSelfSession([FromServices] IUserReferenceService userReferenceService) { var x = userReferenceService.AuthReference; diff --git a/API/Controller/Sessions/_ApiController.cs b/API/Controller/Sessions/_ApiController.cs index d86febb1..70ec65f5 100644 --- a/API/Controller/Sessions/_ApiController.cs +++ b/API/Controller/Sessions/_ApiController.cs @@ -12,8 +12,7 @@ namespace OpenShock.API.Controller.Sessions; /// [ApiController] [Tags("Sessions")] -[ApiVersion("1")] -[Route("/{version:apiVersion}/sessions")] +[Route("/{version:apiVersion}/sessions"), ApiVersion("1")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemes.UserSessionCookie)] public sealed partial class SessionsController : AuthenticatedSessionControllerBase { diff --git a/Common/OpenAPI/OpenApiExtensions.cs b/Common/OpenAPI/OpenApiExtensions.cs index b3700051..bed9be8b 100644 --- a/Common/OpenAPI/OpenApiExtensions.cs +++ b/Common/OpenAPI/OpenApiExtensions.cs @@ -16,7 +16,7 @@ public static IServiceCollection AddOpenApiExt(this WebApplicationBuil { options.AddPolicy("OpenAPI", policy => policy.Expire(TimeSpan.FromMinutes(10))); }); - builder.Services.AddOpenApi("v1", options => + builder.Services.AddOpenApi(options => { options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "1")); @@ -26,9 +26,16 @@ public static IServiceCollection AddOpenApiExt(this WebApplicationBuil options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "2")); }); - builder.Services.AddOpenApi("internal", options => + builder.Services.AddOpenApi("oauth", options => { options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; + options.ShouldInclude = apiDescription => apiDescription.GroupName is "oauth"; + options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "1")); + }); + builder.Services.AddOpenApi("admin", options => + { + options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1; + options.ShouldInclude = apiDescription => apiDescription.GroupName is "admin"; options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(version: "1")); }); From 22647c39018681ece07e716927f707d6eb4ca3a0 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 12 Nov 2025 13:11:36 +0100 Subject: [PATCH 24/29] Revert "More changes" This reverts commit 89dadb941f4b29df031d0f6ebfdc29878dc8bce2. --- API/Controller/Account/Activate.cs | 1 - API/Controller/Account/CheckUsername.cs | 1 - API/Controller/Account/Login.cs | 1 - API/Controller/Account/LoginV2.cs | 1 - API/Controller/Account/Logout.cs | 1 - API/Controller/Account/PasswordResetCheckValid.cs | 1 - API/Controller/Account/PasswordResetComplete.cs | 1 - API/Controller/Account/PasswordResetInitiate.cs | 1 - API/Controller/Account/PasswordResetInitiateV2.cs | 1 - API/Controller/Account/Signup.cs | 1 - API/Controller/Account/SignupV2.cs | 1 - API/Controller/Account/VerifyEmail.cs | 1 - API/Controller/Account/_ApiController.cs | 3 ++- API/Controller/Device/AssignLCG.cs | 1 - API/Controller/Device/AssignLCGV2.cs | 1 - API/Controller/Device/GetSelf.cs | 1 - API/Controller/Device/Pair.cs | 1 - API/Controller/Device/_ApiController.cs | 4 +++- API/Controller/Devices/DeviceOtaController.cs | 1 - API/Controller/Devices/DevicesController.cs | 1 - API/Controller/Devices/GetShockers.cs | 1 - API/Controller/Devices/_ApiController.cs | 3 ++- API/Controller/Public/GetStats.cs | 1 - API/Controller/Public/PublicShareController.cs | 3 +-- API/Controller/Sessions/DeleteSessions.cs | 1 - API/Controller/Sessions/ListSessions.cs | 1 - API/Controller/Sessions/SessionSelf.cs | 1 - API/Controller/Sessions/_ApiController.cs | 3 ++- 28 files changed, 10 insertions(+), 29 deletions(-) diff --git a/API/Controller/Account/Activate.cs b/API/Controller/Account/Activate.cs index 29b8447d..fdd484ed 100644 --- a/API/Controller/Account/Activate.cs +++ b/API/Controller/Account/Activate.cs @@ -16,7 +16,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] [MapToApiVersion("1")] - [EndpointGroupName("v1")] public async Task Activate([FromQuery(Name = "token")] string token, CancellationToken cancellationToken) { bool ok = await _accountService.TryActivateAccountAsync(token, cancellationToken); diff --git a/API/Controller/Account/CheckUsername.cs b/API/Controller/Account/CheckUsername.cs index 689b4a21..60df27fa 100644 --- a/API/Controller/Account/CheckUsername.cs +++ b/API/Controller/Account/CheckUsername.cs @@ -15,7 +15,6 @@ public sealed partial class AccountController /// /// [HttpPost("username/check")] // High-volume endpoint, we don't want to rate limit this - [EndpointGroupName("v1")] [Consumes(MediaTypeNames.Application.Json)] public async Task CheckUsername([FromBody] ChangeUsernameRequest body, CancellationToken cancellationToken) { diff --git a/API/Controller/Account/Login.cs b/API/Controller/Account/Login.cs index 59ed88e5..6ec536da 100644 --- a/API/Controller/Account/Login.cs +++ b/API/Controller/Account/Login.cs @@ -23,7 +23,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status401Unauthorized, MediaTypeNames.Application.ProblemJson)] // InvalidCredentials [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidDomain [MapToApiVersion("1")] - [EndpointGroupName("v1")] public async Task Login( [FromBody] Login body, CancellationToken cancellationToken) diff --git a/API/Controller/Account/LoginV2.cs b/API/Controller/Account/LoginV2.cs index c5849ad0..0fa3e807 100644 --- a/API/Controller/Account/LoginV2.cs +++ b/API/Controller/Account/LoginV2.cs @@ -27,7 +27,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status401Unauthorized, MediaTypeNames.Application.ProblemJson)] // InvalidCredentials [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidDomain [MapToApiVersion("2")] - [EndpointGroupName("v2")] public async Task LoginV2( [FromBody] LoginV2 body, [FromServices] ICloudflareTurnstileService turnstileService, diff --git a/API/Controller/Account/Logout.cs b/API/Controller/Account/Logout.cs index 31d3271b..44a565d6 100644 --- a/API/Controller/Account/Logout.cs +++ b/API/Controller/Account/Logout.cs @@ -10,7 +10,6 @@ public sealed partial class AccountController [HttpPost("logout")] [ProducesResponseType(StatusCodes.Status200OK)] [MapToApiVersion("1")] - [EndpointGroupName("v1")] public async Task Logout([FromServices] ISessionService sessionService) { // Remove session if valid diff --git a/API/Controller/Account/PasswordResetCheckValid.cs b/API/Controller/Account/PasswordResetCheckValid.cs index 044766f9..d66f869d 100644 --- a/API/Controller/Account/PasswordResetCheckValid.cs +++ b/API/Controller/Account/PasswordResetCheckValid.cs @@ -23,7 +23,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PasswordResetNotFound [MapToApiVersion("1")] - [EndpointGroupName("v1")] public async Task PasswordResetCheckValid([FromRoute] Guid passwordResetId, [FromRoute] string secret, CancellationToken cancellationToken) { var passwordResetExists = await _accountService.CheckPasswordResetExistsAsync(passwordResetId, secret, cancellationToken); diff --git a/API/Controller/Account/PasswordResetComplete.cs b/API/Controller/Account/PasswordResetComplete.cs index a67b3041..d9e58c93 100644 --- a/API/Controller/Account/PasswordResetComplete.cs +++ b/API/Controller/Account/PasswordResetComplete.cs @@ -24,7 +24,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PasswordResetNotFound [MapToApiVersion("1")] - [EndpointGroupName("v1")] public async Task PasswordResetComplete([FromRoute] Guid passwordResetId, [FromRoute] string secret, [FromBody] PasswordResetProcessData body) { diff --git a/API/Controller/Account/PasswordResetInitiate.cs b/API/Controller/Account/PasswordResetInitiate.cs index 91f18590..945be724 100644 --- a/API/Controller/Account/PasswordResetInitiate.cs +++ b/API/Controller/Account/PasswordResetInitiate.cs @@ -17,7 +17,6 @@ public sealed partial class AccountController [EnableRateLimiting("auth")] [Consumes(MediaTypeNames.Application.Json)] [MapToApiVersion("1")] - [EndpointGroupName("v1")] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task PasswordResetInitiate([FromBody] ResetRequest body) { diff --git a/API/Controller/Account/PasswordResetInitiateV2.cs b/API/Controller/Account/PasswordResetInitiateV2.cs index 8a28437d..2c9e1a76 100644 --- a/API/Controller/Account/PasswordResetInitiateV2.cs +++ b/API/Controller/Account/PasswordResetInitiateV2.cs @@ -23,7 +23,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] [MapToApiVersion("2")] - [EndpointGroupName("v1")] public async Task PasswordResetInitiateV2([FromBody] PasswordResetRequestV2 body, [FromServices] ICloudflareTurnstileService turnstileService, CancellationToken cancellationToken) { var turnStile = await turnstileService.VerifyUserResponseTokenAsync(body.TurnstileResponse, HttpContext.GetRemoteIP(), cancellationToken); diff --git a/API/Controller/Account/Signup.cs b/API/Controller/Account/Signup.cs index 53aa2a68..4aea610d 100644 --- a/API/Controller/Account/Signup.cs +++ b/API/Controller/Account/Signup.cs @@ -23,7 +23,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status409Conflict, MediaTypeNames.Application.ProblemJson)] // EmailOrUsernameAlreadyExists [MapToApiVersion("1")] - [EndpointGroupName("v1")] public async Task SignUp([FromBody] SignUp body) { var creationAction = await _accountService.CreateAccountWithoutActivationFlowLegacyAsync(body.Email, body.Username, body.Password); diff --git a/API/Controller/Account/SignupV2.cs b/API/Controller/Account/SignupV2.cs index 4edc9942..9f707bb0 100644 --- a/API/Controller/Account/SignupV2.cs +++ b/API/Controller/Account/SignupV2.cs @@ -29,7 +29,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status409Conflict, MediaTypeNames.Application.ProblemJson)] // EmailOrUsernameAlreadyExists [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidTurnstileResponse [MapToApiVersion("2")] - [EndpointGroupName("v2")] public async Task SignUpV2( [FromBody] SignUpV2 body, [FromServices] ICloudflareTurnstileService turnstileService, diff --git a/API/Controller/Account/VerifyEmail.cs b/API/Controller/Account/VerifyEmail.cs index aa11e4bf..f107bf40 100644 --- a/API/Controller/Account/VerifyEmail.cs +++ b/API/Controller/Account/VerifyEmail.cs @@ -15,7 +15,6 @@ public sealed partial class AccountController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] [MapToApiVersion("1")] - [EndpointGroupName("v1")] public async Task EmailVerify([FromQuery(Name = "token")] string token, CancellationToken cancellationToken) { bool ok = await _accountService.TryVerifyEmailAsync(token, cancellationToken); diff --git a/API/Controller/Account/_ApiController.cs b/API/Controller/Account/_ApiController.cs index da01487d..a68582c7 100644 --- a/API/Controller/Account/_ApiController.cs +++ b/API/Controller/Account/_ApiController.cs @@ -10,7 +10,8 @@ namespace OpenShock.API.Controller.Account; /// [ApiController] [Tags("Account")] -[Route("/{version:apiVersion}/account"), ApiVersion("1"), ApiVersion("2")] +[ApiVersion("1"), ApiVersion("2")] +[Route("/{version:apiVersion}/account")] public sealed partial class AccountController : OpenShockControllerBase { private readonly IAccountService _accountService; diff --git a/API/Controller/Device/AssignLCG.cs b/API/Controller/Device/AssignLCG.cs index b2fcf9f6..77fabd18 100644 --- a/API/Controller/Device/AssignLCG.cs +++ b/API/Controller/Device/AssignLCG.cs @@ -19,7 +19,6 @@ public sealed partial class DeviceController /// Unable to find suitable LCG node [HttpGet("assignLCG")] [MapToApiVersion("1")] - [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable, MediaTypeNames.Application.ProblemJson)] // NoLcgNodesAvailable public async Task GetLiveControlGateway([FromServices] ILCGNodeProvisioner geoLocation) diff --git a/API/Controller/Device/AssignLCGV2.cs b/API/Controller/Device/AssignLCGV2.cs index 052191f8..ddfd80ae 100644 --- a/API/Controller/Device/AssignLCGV2.cs +++ b/API/Controller/Device/AssignLCGV2.cs @@ -18,7 +18,6 @@ public sealed partial class DeviceController /// Unable to find suitable LCG node [HttpGet("assignLCG")] [MapToApiVersion("2")] - [EndpointGroupName("v2")] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] // BadSchemaVersion [ProducesResponseType(StatusCodes.Status503ServiceUnavailable, MediaTypeNames.Application.ProblemJson)] // NoLcgNodesAvailable diff --git a/API/Controller/Device/GetSelf.cs b/API/Controller/Device/GetSelf.cs index 81c90d03..8ddaf3ed 100644 --- a/API/Controller/Device/GetSelf.cs +++ b/API/Controller/Device/GetSelf.cs @@ -17,7 +17,6 @@ public sealed partial class DeviceController /// The device information was successfully retrieved. [HttpGet("self")] [MapToApiVersion("1")] - [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task GetSelf() { diff --git a/API/Controller/Device/Pair.cs b/API/Controller/Device/Pair.cs index 6dcc08a5..3f3bf01b 100644 --- a/API/Controller/Device/Pair.cs +++ b/API/Controller/Device/Pair.cs @@ -21,7 +21,6 @@ public sealed partial class DeviceController /// No such pair code exists [AllowAnonymous] [MapToApiVersion("1")] - [EndpointGroupName("v1")] [HttpGet("pair/{pairCode}", Name = "Pair")] [HttpGet("~/{version:apiVersion}/pair/{pairCode}", Name = "Pair_DEPRECATED")] // Backwards compatibility [EnableRateLimiting("auth")] diff --git a/API/Controller/Device/_ApiController.cs b/API/Controller/Device/_ApiController.cs index e00331dd..74c7a249 100644 --- a/API/Controller/Device/_ApiController.cs +++ b/API/Controller/Device/_ApiController.cs @@ -12,8 +12,10 @@ namespace OpenShock.API.Controller.Device; /// For devices (ESP's) /// [ApiController] +[ApiVersion("1")] +[ApiVersion("2")] [Tags("Hub Endpoints")] -[Route("/{version:apiVersion}/device"), ApiVersion("1"), ApiVersion("2")] +[Route("/{version:apiVersion}/device")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemes.HubToken)] public sealed partial class DeviceController : OpenShockControllerBase { diff --git a/API/Controller/Devices/DeviceOtaController.cs b/API/Controller/Devices/DeviceOtaController.cs index 4827ea34..fa2de294 100644 --- a/API/Controller/Devices/DeviceOtaController.cs +++ b/API/Controller/Devices/DeviceOtaController.cs @@ -39,7 +39,6 @@ public DevicesOtaController(OpenShockContext db, ILogger logg /// Could not find device or you do not have access to it [HttpGet("{deviceId}/ota")] [MapToApiVersion("1")] - [EndpointGroupName("v1")] [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound public async Task GetOtaUpdateHistory([FromRoute] Guid deviceId, [FromServices] IOtaService otaService) diff --git a/API/Controller/Devices/DevicesController.cs b/API/Controller/Devices/DevicesController.cs index 2d5f1d09..036e7cb4 100644 --- a/API/Controller/Devices/DevicesController.cs +++ b/API/Controller/Devices/DevicesController.cs @@ -25,7 +25,6 @@ public sealed partial class DevicesController /// All devices for the current user [HttpGet] [MapToApiVersion("1")] - [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public IActionResult ListDevices() { diff --git a/API/Controller/Devices/GetShockers.cs b/API/Controller/Devices/GetShockers.cs index 45dcbbe5..bbff7340 100644 --- a/API/Controller/Devices/GetShockers.cs +++ b/API/Controller/Devices/GetShockers.cs @@ -21,7 +21,6 @@ public sealed partial class DevicesController [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [MapToApiVersion("1")] - [EndpointGroupName("v1")] public async Task GetShockers([FromRoute] Guid deviceId) { var deviceExists = await _db.Devices.AnyAsync(x => x.OwnerId == CurrentUser.Id && x.Id == deviceId); diff --git a/API/Controller/Devices/_ApiController.cs b/API/Controller/Devices/_ApiController.cs index 76fc274c..c123f6f3 100644 --- a/API/Controller/Devices/_ApiController.cs +++ b/API/Controller/Devices/_ApiController.cs @@ -13,7 +13,8 @@ namespace OpenShock.API.Controller.Devices; /// [ApiController] [Tags("Hub Management")] -[Route("/{version:apiVersion}/devices"), ApiVersion("1"), ApiVersion("2")] +[ApiVersion("1"), ApiVersion("2")] +[Route("/{version:apiVersion}/devices")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemes.UserSessionApiTokenCombo)] public sealed partial class DevicesController : AuthenticatedSessionControllerBase { diff --git a/API/Controller/Public/GetStats.cs b/API/Controller/Public/GetStats.cs index dad82e3c..ff07f15c 100644 --- a/API/Controller/Public/GetStats.cs +++ b/API/Controller/Public/GetStats.cs @@ -15,7 +15,6 @@ public sealed partial class PublicController /// The statistics were successfully retrieved. [Tags("Meta")] [HttpGet("stats")] - [EndpointGroupName("v1")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task GetOnlineDevicesStatistics([FromServices] IConnectionMultiplexer redisConnectionMultiplexer) { diff --git a/API/Controller/Public/PublicShareController.cs b/API/Controller/Public/PublicShareController.cs index f1354700..fdcfc560 100644 --- a/API/Controller/Public/PublicShareController.cs +++ b/API/Controller/Public/PublicShareController.cs @@ -18,9 +18,8 @@ public sealed partial class PublicController /// /// The public share information was successfully retrieved. /// The public share does not exist. - [Tags("Public Shocker Shares")] [HttpGet("shares/links/{publicShareId}")] - [EndpointGroupName("v1")] + [Tags("Public Shocker Shares")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PublicShareNotFound public async Task GetPublicShare([FromRoute] Guid publicShareId) diff --git a/API/Controller/Sessions/DeleteSessions.cs b/API/Controller/Sessions/DeleteSessions.cs index 7a66bf23..74c5b87c 100644 --- a/API/Controller/Sessions/DeleteSessions.cs +++ b/API/Controller/Sessions/DeleteSessions.cs @@ -10,7 +10,6 @@ namespace OpenShock.API.Controller.Sessions; public sealed partial class SessionsController { [HttpDelete("{sessionId}")] - [EndpointGroupName("v1")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // SessionNotFound public async Task DeleteSession([FromRoute] Guid sessionId) diff --git a/API/Controller/Sessions/ListSessions.cs b/API/Controller/Sessions/ListSessions.cs index dab8877b..964b1c26 100644 --- a/API/Controller/Sessions/ListSessions.cs +++ b/API/Controller/Sessions/ListSessions.cs @@ -7,7 +7,6 @@ namespace OpenShock.API.Controller.Sessions; public sealed partial class SessionsController { [HttpGet] - [EndpointGroupName("v1")] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public IAsyncEnumerable ListSessions() { diff --git a/API/Controller/Sessions/SessionSelf.cs b/API/Controller/Sessions/SessionSelf.cs index e39e7d11..53995c33 100644 --- a/API/Controller/Sessions/SessionSelf.cs +++ b/API/Controller/Sessions/SessionSelf.cs @@ -13,7 +13,6 @@ public sealed partial class SessionsController /// /// [HttpGet("self")] - [EndpointGroupName("v1")] public LoginSessionResponse GetSelfSession([FromServices] IUserReferenceService userReferenceService) { var x = userReferenceService.AuthReference; diff --git a/API/Controller/Sessions/_ApiController.cs b/API/Controller/Sessions/_ApiController.cs index 70ec65f5..d86febb1 100644 --- a/API/Controller/Sessions/_ApiController.cs +++ b/API/Controller/Sessions/_ApiController.cs @@ -12,7 +12,8 @@ namespace OpenShock.API.Controller.Sessions; /// [ApiController] [Tags("Sessions")] -[Route("/{version:apiVersion}/sessions"), ApiVersion("1")] +[ApiVersion("1")] +[Route("/{version:apiVersion}/sessions")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemes.UserSessionCookie)] public sealed partial class SessionsController : AuthenticatedSessionControllerBase { From e90f7ef4fe24564644dbd937210345c3fd5b8e86 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Sat, 22 Nov 2025 20:49:14 +0100 Subject: [PATCH 25/29] Update Directory.Packages.props --- Directory.Packages.props | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b27da062..66947fa9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,15 +16,15 @@ - + - - + + - + @@ -34,13 +34,13 @@ - + - + \ No newline at end of file From 7bb85985893c85c240a71db63253d897502d29a3 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Sat, 22 Nov 2025 20:52:58 +0100 Subject: [PATCH 26/29] Update global.json --- global.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/global.json b/global.json index cb40a970..2e206e82 100644 --- a/global.json +++ b/global.json @@ -3,5 +3,8 @@ "version": "10.0.0", "rollForward": "latestMinor", "allowPrerelease": false + }, + "test": { + "runner": "Microsoft.Testing.Platform" } } From 9981506197d0a26ffddc1ef961f0e13a183a68ff Mon Sep 17 00:00:00 2001 From: hhvrc Date: Sat, 22 Nov 2025 20:57:08 +0100 Subject: [PATCH 27/29] Update ci-build.yml --- .github/workflows/ci-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 7dbf7709..4281b505 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -60,7 +60,7 @@ jobs: - name: Run ${{ matrix.name }} tests run: | set -euo pipefail - dotnet test -c Release ${{ matrix.project }} + dotnet test -c Release --project ${{ matrix.project }} build: name: Build (${{ matrix.image }}) From ac1dd0d65533f952696cd97b0dc09745ab4631d1 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Sun, 30 Nov 2025 03:16:17 +0100 Subject: [PATCH 28/29] Update Serilog.AspNetCore to 10.0.0 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 66947fa9..bf7e707a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,7 +35,7 @@ - + From ac363cfb81ca5e7806f265eaeedd939c40a3be4b Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Mon, 1 Dec 2025 11:40:29 +0100 Subject: [PATCH 29/29] Additional fixes --- API.IntegrationTests/Tests/LcgAssignmentTests.cs | 2 +- API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/API.IntegrationTests/Tests/LcgAssignmentTests.cs b/API.IntegrationTests/Tests/LcgAssignmentTests.cs index 8642508c..197a5901 100644 --- a/API.IntegrationTests/Tests/LcgAssignmentTests.cs +++ b/API.IntegrationTests/Tests/LcgAssignmentTests.cs @@ -66,7 +66,7 @@ public async Task Teardown() await db.Devices.Where(x => x.Id == _hubId).ExecuteDeleteAsync(); await db.Users.Where(x => x.Id == _userId).ExecuteDeleteAsync(); - var allLcg = await lcgNodesCollection.ToArrayAsync(); + var allLcg = await lcgNodesCollection.ToListAsync(); await lcgNodesCollection.DeleteAsync(allLcg); } diff --git a/API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs b/API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs index 719bdd7a..a943820f 100644 --- a/API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs +++ b/API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs @@ -46,7 +46,7 @@ public LCGNodeProvisioner(IRedisConnectionProvider redisConnectionProvider, IWeb .Where(x => x.Environment == _environmentName) .ToListAsync(); - if(nodes.Length < 1) + if(nodes.Count < 1) { _logger.LogWarning("No LCG nodes available after filtering by environment [{Environment}]!", _environmentName); return null;