Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f18ee5c
Create initial draft for .NET 10 changes
hhvrc Sep 24, 2025
3143802
Merge branch 'develop' into feature/dotnet-10
hhvrc Oct 1, 2025
b283abf
Update Directory.Packages.props
hhvrc Oct 1, 2025
510d44e
Use field keyword
hhvrc Oct 1, 2025
a6c9ec1
Merge branch 'develop' into feature/dotnet-10
hhvrc Oct 17, 2025
1320cbb
Update Directory.Packages.props
hhvrc Oct 17, 2025
2735fa6
Fix some errors
hhvrc Oct 17, 2025
67c7a4d
Merge branch 'develop' into feature/dotnet-10
hhvrc Oct 17, 2025
590ea33
Update Directory.Packages.props
hhvrc Oct 20, 2025
a9b75ad
Update global.json
hhvrc Oct 28, 2025
70124a0
Merge branch 'develop' into feature/dotnet-10
hhvrc Oct 28, 2025
6775c2d
Revert some changes
hhvrc Oct 28, 2025
cc11289
Improve SessionService SessionLister
hhvrc Oct 28, 2025
ba965b6
Update global.json
hhvrc Nov 6, 2025
d1adb30
Revert some changes
hhvrc Nov 6, 2025
e0307a5
Merge branch 'develop' into feature/dotnet-10
hhvrc Nov 6, 2025
c18cd36
Update ListSessions.cs
hhvrc Nov 6, 2025
63c6565
Update ListSessions.cs
hhvrc Nov 6, 2025
16ff23c
Merge branch 'develop' into feature/dotnet-10
hhvrc Nov 10, 2025
296df41
Update Directory.Packages.props
hhvrc Nov 11, 2025
3b10388
Update Directory.Packages.props
hhvrc Nov 11, 2025
a550140
Update workflows
hhvrc Nov 11, 2025
2395048
Update docker files
hhvrc Nov 11, 2025
63df5fd
Revert "Update Directory.Packages.props"
hhvrc Nov 11, 2025
35b530d
OpenAPI broke
hhvrc Nov 11, 2025
b669fd2
This is gonna be alot of work
hhvrc Nov 11, 2025
ae70c50
Oops
hhvrc Nov 11, 2025
1d1ee8c
Update Directory.Packages.props
hhvrc Nov 12, 2025
89dadb9
More changes
hhvrc Nov 12, 2025
22647c3
Revert "More changes"
hhvrc Nov 12, 2025
e90f7ef
Update Directory.Packages.props
hhvrc Nov 22, 2025
7bb8598
Update global.json
hhvrc Nov 22, 2025
9981506
Update ci-build.yml
hhvrc Nov 22, 2025
126f33d
Merge branch 'develop' into feature/dotnet-10
hhvrc Nov 25, 2025
ac1dd0d
Update Serilog.AspNetCore to 10.0.0
hhvrc Nov 30, 2025
8fb7a5c
Merge branch 'develop' into feature/dotnet-10
hhvrc Dec 1, 2025
ac363cf
Additional fixes
hhvrc Dec 1, 2025
356f514
fix: dotnet 10 openapi (#261)
TuTiDore Dec 22, 2025
250b27a
Fix/dotnet 10 openapi 2 (#264)
TuTiDore Dec 25, 2025
56a876d
Update API/Controller/Device/Pair.cs
LucHeart Dec 26, 2025
35cb74d
Exclude legacy endpoint from generated OpenAPI document
hhvrc Dec 26, 2025
196ac0d
More comprehensive SchemaReferenceId generator
hhvrc Dec 26, 2025
6684a17
Fix doc exclude
hhvrc Dec 26, 2025
f0edbb7
Update OpenApiExtensions.cs
hhvrc Dec 26, 2025
2bdd976
Update OpenApiExtensions.cs
hhvrc Dec 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ name: ci-build

env:
REGISTRY: ghcr.io
DOTNET_VERSION: 9.0.x
DOTNET_VERSION: 10.0.x

jobs:

Expand All @@ -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 }}
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --project flag is incorrect syntax for dotnet test. The correct syntax is to pass the project path directly without a flag, as it was before. Change back to: dotnet test -c Release ${{ matrix.project }}

Suggested change
dotnet test -c Release --project ${{ matrix.project }}
dotnet test -c Release ${{ matrix.project }}

Copilot uses AI. Check for mistakes.

build:
name: Build (${{ matrix.image }})
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
- cron: '0 6 * * 1'

env:
DOTNET_VERSION: 9.x.x
DOTNET_VERSION: 10.x.x

jobs:
analyze:
Expand Down
2 changes: 1 addition & 1 deletion API.IntegrationTests/Tests/LcgAssignmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion API/Controller/Admin/GetOnlineDevices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public async Task<IActionResult> GetOnlineDevices()
{
var devicesOnline = _redis.RedisCollection<DeviceOnline>(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
Expand Down
1 change: 1 addition & 0 deletions API/Controller/Admin/_ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion API/Controller/Device/Pair.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,29 @@ public sealed partial class DeviceController
[AllowAnonymous]
[MapToApiVersion("1")]
[HttpGet("pair/{pairCode}", Name = "Pair")]
[HttpGet("~/{version:apiVersion}/pair/{pairCode}", Name = "Pair_DEPRECATED")] // Backwards compatibility
[EndpointName("PairDeviceByCode")]
[EnableRateLimiting("auth")]
[ProducesResponseType<LegacyDataResponse<string>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PairCodeNotFound
public async Task<IActionResult> Pair([FromRoute] string pairCode)
{
return await PairInternal(pairCode);
}

/// <summary>
/// Pair a device with a pair code, legacy endpoint kept for backwards compatibility
/// </summary>
[AllowAnonymous]
[MapToApiVersion("1")]
[HttpGet("~/{version:apiVersion}/pair/{pairCode}")]
[ApiExplorerSettings(IgnoreApi = true)]
[EnableRateLimiting("auth")]
public async Task<IActionResult> PairDeprecated([FromRoute] string pairCode)
{
return await PairInternal(pairCode);
}

public async Task<IActionResult> PairInternal(string pairCode)
{
var devicePairs = _redis.RedisCollection<DevicePair>();

Expand Down
8 changes: 4 additions & 4 deletions API/Controller/Devices/DevicesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public sealed partial class DevicesController
/// <response code="200">All devices for the current user</response>
[HttpGet]
[MapToApiVersion("1")]
[ProducesResponseType<LegacyDataResponse<Models.Response.ResponseDevice[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
[ProducesResponseType<LegacyDataResponse<Models.Response.DeviceResponse[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
public IActionResult ListDevices()
{
var devices = _db.Devices
.Where(x => x.OwnerId == CurrentUser.Id)
.Select(x => new Models.Response.ResponseDevice
.Select(x => new Models.Response.DeviceResponse
{
Id = x.Id,
Name = x.Name,
Expand All @@ -47,7 +47,7 @@ public IActionResult ListDevices()
/// <param name="deviceId"></param>
/// <response code="200">The device</response>
[HttpGet("{deviceId}")]
[ProducesResponseType<LegacyDataResponse<Models.Response.ResponseDeviceWithToken>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
[ProducesResponseType<LegacyDataResponse<Models.Response.DeviceWithTokenResponse>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound
[MapToApiVersion("1")]
public async Task<IActionResult> GetDeviceById([FromRoute] Guid deviceId)
Expand All @@ -56,7 +56,7 @@ public async Task<IActionResult> GetDeviceById([FromRoute] Guid deviceId)


var device = await _db.Devices.Where(x => x.OwnerId == CurrentUser.Id && x.Id == deviceId)
.Select(x => new Models.Response.ResponseDeviceWithToken
.Select(x => new Models.Response.DeviceWithTokenResponse
{
Id = x.Id,
Name = x.Name,
Expand Down
1 change: 0 additions & 1 deletion API/Controller/OAuth/Authorize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public sealed partial class OAuthController
/// <response code="400">Unsupported or misconfigured provider.</response>
[EnableRateLimiting("auth")]
[HttpPost("{provider}/authorize")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> OAuthAuthorize([FromRoute] string provider, [FromQuery(Name="flow"), Required] OAuthFlow flow)
{
if (!await _schemeProvider.IsSupportedOAuthScheme(provider))
Expand Down
1 change: 0 additions & 1 deletion API/Controller/OAuth/HandOff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public sealed partial class OAuthController
/// </remarks>
[EnableRateLimiting("auth")]
[HttpGet("{provider}/handoff")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> OAuthHandOff(
[FromRoute] string provider,
[FromServices] IOAuthConnectionService connectionService,
Expand Down
1 change: 0 additions & 1 deletion API/Controller/OAuth/SignupFinalize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ public sealed partial class OAuthController
/// <param name="cancellationToken"></param>
[EnableRateLimiting("auth")]
[HttpPost("{provider}/signup-finalize")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> OAuthSignupFinalize(
[FromRoute] string provider,
[FromBody] OAuthFinalizeRequest body,
Expand Down
1 change: 0 additions & 1 deletion API/Controller/OAuth/SignupGetData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public sealed partial class OAuthController
[ResponseCache(NoStore = true)]
[EnableRateLimiting("auth")]
[HttpGet("{provider}/signup-data")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> OAuthSignupGetData([FromRoute] string provider)
{
if (User.HasOpenShockUserIdentity())
Expand Down
5 changes: 2 additions & 3 deletions API/Controller/OAuth/_ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ namespace OpenShock.API.Controller.OAuth;
/// OAuth management endpoints (provider listing, authorize, data handoff).
/// </summary>
[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;
Expand Down
9 changes: 3 additions & 6 deletions API/Controller/Sessions/ListSessions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Net.Mime;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Mvc;
using OpenShock.API.Models.Response;

Expand All @@ -9,11 +8,9 @@ public sealed partial class SessionsController
{
[HttpGet]
[ProducesResponseType<LoginSessionResponse[]>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
public async IAsyncEnumerable<LoginSessionResponse> ListSessions([EnumeratorCancellation] CancellationToken cancellationToken)
public IAsyncEnumerable<LoginSessionResponse> ListSessions()
{
await foreach (var session in _sessionService.ListSessionsByUserIdAsync(CurrentUser.Id).WithCancellation(cancellationToken))
{
yield return LoginSessionResponse.MapFrom(session);
}
return _sessionService.ListSessionsByUserIdAsync(CurrentUser.Id)
.Select(LoginSessionResponse.MapFrom);
}
}
4 changes: 2 additions & 2 deletions API/Controller/Shockers/ListShockers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public sealed partial class ShockerController
/// <response code="200">The shockers were successfully retrieved.</response>
[HttpGet("own")]
[MapToApiVersion("1")]
[ProducesResponseType<LegacyDataResponse<ResponseDeviceWithShockers[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
[ProducesResponseType<LegacyDataResponse<DeviceWithShockersResponse[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
public IActionResult ListShockers()
{
var shockers = _db.Devices
.Where(x => x.OwnerId == CurrentUser.Id)
.OrderBy(x => x.CreatedAt).Select(x => new ResponseDeviceWithShockers
.OrderBy(x => x.CreatedAt).Select(x => new DeviceWithShockersResponse
{
Id = x.Id,
Name = x.Name,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace OpenShock.API.Models.Response;

public class ResponseDevice
public class DeviceResponse
{
public required Guid Id { get; init; }
public required string Name { get; init; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace OpenShock.API.Models.Response;

public sealed class ResponseDeviceWithShockers : ResponseDevice
public sealed class DeviceWithShockersResponse : DeviceResponse
{
public required ShockerResponse[] Shockers { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace OpenShock.API.Models.Response;

public sealed class ResponseDeviceWithToken : ResponseDevice
public sealed class DeviceWithTokenResponse : DeviceResponse
{
public required string? Token { get; init; }
}
4 changes: 2 additions & 2 deletions API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
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.Ota;
using OpenShock.Common.Swagger;
using Serilog;
using OAuthConstants = OpenShock.API.OAuth.OAuthConstants;

Expand Down Expand Up @@ -103,7 +103,7 @@ static void DefaultOptions(RemoteAuthenticationOptions options, string provider)
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ILCGNodeProvisioner, LCGNodeProvisioner>();

builder.AddSwaggerExt<Program>();
builder.AddOpenApiExt<Program>();

builder.AddCloudflareTurnstileService();
builder.AddEmailService();
Expand Down
4 changes: 2 additions & 2 deletions API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ public LCGNodeProvisioner(IRedisConnectionProvider redisConnectionProvider, IWeb
// Load all nodes for our environment
var nodes = await _lcgNodes
.Where(x => x.Environment == _environmentName)
.ToArrayAsync();
.ToListAsync();

if(nodes.Length < 1)
if(nodes.Count < 1)
{
_logger.LogWarning("No LCG nodes available after filtering by environment [{Environment}]!", _environmentName);
return null;
Expand Down
2 changes: 1 addition & 1 deletion Common/Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</PackageReference>
<PackageReference Include="MessagePack" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
Expand All @@ -31,7 +32,6 @@
<PackageReference Include="OneOf" />
<PackageReference Include="Serilog.AspNetCore" />
<PackageReference Include="Serilog.Sinks.OpenTelemetry" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" />
<PackageReference Include="Z.EntityFramework.Plus.EFCore" />
</ItemGroup>

Expand Down
18 changes: 2 additions & 16 deletions Common/DataAnnotations/EmailAddressAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
using System.ComponentModel.DataAnnotations;
using System.Net.Mail;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using OpenShock.Common.Constants;
using OpenShock.Common.DataAnnotations.Interfaces;

namespace OpenShock.Common.DataAnnotations;

Expand All @@ -13,8 +10,8 @@ namespace OpenShock.Common.DataAnnotations;
/// <remarks>
/// Inherits from <see cref="ValidationAttribute"/>.
/// </remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class EmailAddressAttribute : ValidationAttribute, IParameterAttribute
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public sealed class EmailAddressAttribute : ValidationAttribute
{
/// <summary>
/// Example value used to generate OpenApi documentation.
Expand Down Expand Up @@ -55,15 +52,4 @@ public sealed class EmailAddressAttribute : ValidationAttribute, IParameterAttri

return ValidationResult.Success;
}

/// <inheritdoc/>
public void Apply(OpenApiSchema schema)
{
//if (ShouldValidate) schema.Pattern = ???;

schema.Example = new OpenApiString(ExampleValue);
}

/// <inheritdoc/>
public void Apply(OpenApiParameter parameter) => Apply(parameter.Schema);
}
15 changes: 0 additions & 15 deletions Common/DataAnnotations/Interfaces/IOperationAttribute.cs

This file was deleted.

21 changes: 0 additions & 21 deletions Common/DataAnnotations/Interfaces/IParameterAttribute.cs

This file was deleted.

31 changes: 0 additions & 31 deletions Common/DataAnnotations/OpenApiSchemas.cs

This file was deleted.

Loading