From 49e37db242877139d47da48242c730246950d6aa Mon Sep 17 00:00:00 2001 From: hhvrc Date: Mon, 28 Apr 2025 14:24:00 +0200 Subject: [PATCH 01/19] Remove swagger completely --- API/Program.cs | 3 - Common/Common.csproj | 2 +- .../DataAnnotations/EmailAddressAttribute.cs | 16 +-- .../Interfaces/IOperationAttribute.cs | 15 --- .../Interfaces/IParameterAttribute.cs | 21 ---- Common/DataAnnotations/OpenApiSchemas.cs | 31 ----- Common/DataAnnotations/PasswordAttribute.cs | 17 +-- Common/DataAnnotations/UsernameAttribute.cs | 16 +-- Common/OpenApi/OpenApiDocumentTransformer.cs | 12 ++ Common/OpenApi/OpenApiOperationTransformer.cs | 12 ++ Common/OpenApi/OpenApiSchemaTransformer.cs | 12 ++ Common/OpenShockMiddlewareHelper.cs | 6 +- Common/OpenShockServiceHelper.cs | 15 ++- Common/Swagger/AttributeFilter.cs | 118 ------------------ Common/Swagger/SwaggerGenExtensions.cs | 100 --------------- Common/Utils/ConfigureSwaggerOptions.cs | 39 ------ LiveControlGateway/Program.cs | 3 - 17 files changed, 56 insertions(+), 382 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/OpenApiDocumentTransformer.cs create mode 100644 Common/OpenApi/OpenApiOperationTransformer.cs create mode 100644 Common/OpenApi/OpenApiSchemaTransformer.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 b4688eae..d7b34a15 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -15,7 +15,6 @@ using OpenShock.Common.Services.LCGNodeProvisioner; using OpenShock.Common.Services.Ota; using OpenShock.Common.Services.Turnstile; -using OpenShock.Common.Swagger; using Serilog; var builder = OpenShockApplication.CreateDefaultBuilder(args); @@ -47,8 +46,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSwaggerExt(); - builder.Services.AddSingleton(); builder.AddCloudflareTurnstileService(); diff --git a/Common/Common.csproj b/Common/Common.csproj index b175c2ef..4f7465d8 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -11,6 +11,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -30,7 +31,6 @@ - diff --git a/Common/DataAnnotations/EmailAddressAttribute.cs b/Common/DataAnnotations/EmailAddressAttribute.cs index cf7a3ec0..269d08aa 100644 --- a/Common/DataAnnotations/EmailAddressAttribute.cs +++ b/Common/DataAnnotations/EmailAddressAttribute.cs @@ -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; @@ -14,7 +11,7 @@ namespace OpenShock.Common.DataAnnotations; /// Inherits from . /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] -public sealed class EmailAddressAttribute : ValidationAttribute, IParameterAttribute +public sealed class EmailAddressAttribute : ValidationAttribute { /// /// Example value used to generate OpenApi documentation. @@ -55,15 +52,4 @@ public sealed class EmailAddressAttribute : ValidationAttribute, IParameterAttri return ValidationResult.Success; } - - /// - public void Apply(OpenApiSchema schema) - { - //if (ShouldValidate) schema.Pattern = ???; - - schema.Example = new OpenApiString(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 d47bd156..00000000 --- a/Common/DataAnnotations/Interfaces/IOperationAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.OpenApi.Models; - -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 295f4d5e..00000000 --- a/Common/DataAnnotations/Interfaces/IParameterAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.OpenApi.Models; - -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 fe003b4e..00000000 --- a/Common/DataAnnotations/OpenApiSchemas.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; -using OpenShock.Common.Models; - -namespace OpenShock.Common.DataAnnotations; - -public static class OpenApiSchemas -{ - public static OpenApiSchema SemVerSchema => new OpenApiSchema { - 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") - }; - - public static OpenApiSchema PauseReasonEnumSchema => new OpenApiSchema { - Title = nameof(PauseReason), - Type = "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: Share - - 4: ShareLink - - For example, a value of 6 (2 | 4) indicates both 'Share' and 'ShareLink' reasons. - """, - Example = new OpenApiInteger(6) - }; -} diff --git a/Common/DataAnnotations/PasswordAttribute.cs b/Common/DataAnnotations/PasswordAttribute.cs index 2aa2a8f1..f0fa11b7 100644 --- a/Common/DataAnnotations/PasswordAttribute.cs +++ b/Common/DataAnnotations/PasswordAttribute.cs @@ -1,9 +1,5 @@ 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; @@ -14,7 +10,7 @@ namespace OpenShock.Common.DataAnnotations; /// Inherits from . /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] -public sealed class PasswordAttribute : ValidationAttribute, IParameterAttribute +public sealed class PasswordAttribute : ValidationAttribute { /// /// Example value used to generate OpenApi documentation. @@ -55,15 +51,4 @@ public sealed class PasswordAttribute : ValidationAttribute, IParameterAttribute return ValidationResult.Success; } - - /// - public void Apply(OpenApiSchema schema) - { - //if (ShouldValidate) schema.Pattern = ???; - - schema.Example = new OpenApiString(ExampleValue); - } - - /// - public void Apply(OpenApiParameter parameter) => Apply(parameter.Schema); } \ No newline at end of file diff --git a/Common/DataAnnotations/UsernameAttribute.cs b/Common/DataAnnotations/UsernameAttribute.cs index c0b366d7..2b4692d0 100644 --- a/Common/DataAnnotations/UsernameAttribute.cs +++ b/Common/DataAnnotations/UsernameAttribute.cs @@ -1,7 +1,4 @@ using System.ComponentModel.DataAnnotations; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; -using OpenShock.Common.DataAnnotations.Interfaces; using OpenShock.Common.Validation; namespace OpenShock.Common.DataAnnotations; @@ -13,7 +10,7 @@ namespace OpenShock.Common.DataAnnotations; /// Inherits from . /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] -public sealed class UsernameAttribute : ValidationAttribute, IParameterAttribute +public sealed class UsernameAttribute : ValidationAttribute { /// /// Example value used to generate OpenApi documentation. @@ -50,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 = new OpenApiString(ExampleValue); - } - - /// - public void Apply(OpenApiParameter parameter) => Apply(parameter.Schema); } \ No newline at end of file diff --git a/Common/OpenApi/OpenApiDocumentTransformer.cs b/Common/OpenApi/OpenApiDocumentTransformer.cs new file mode 100644 index 00000000..8a31993e --- /dev/null +++ b/Common/OpenApi/OpenApiDocumentTransformer.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi.Models; + +namespace OpenShock.Common.OpenApi; + +public sealed class OpenApiDocumentTransformer : IOpenApiDocumentTransformer +{ + public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/Common/OpenApi/OpenApiOperationTransformer.cs b/Common/OpenApi/OpenApiOperationTransformer.cs new file mode 100644 index 00000000..03b0f035 --- /dev/null +++ b/Common/OpenApi/OpenApiOperationTransformer.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi.Models; + +namespace OpenShock.Common.OpenApi; + +public sealed class OpenApiOperationTransformer : IOpenApiOperationTransformer +{ + public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/Common/OpenApi/OpenApiSchemaTransformer.cs b/Common/OpenApi/OpenApiSchemaTransformer.cs new file mode 100644 index 00000000..e8d1089d --- /dev/null +++ b/Common/OpenApi/OpenApiSchemaTransformer.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi.Models; + +namespace OpenShock.Common.OpenApi; + +public sealed class OpenApiSchemaTransformer : IOpenApiSchemaTransformer +{ + public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 64ceb641..57257419 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -73,13 +73,11 @@ public static IApplicationBuilder UseCommonOpenShockMiddleware(this WebApplicati return remoteIp != null && metricsAllowedIpNetworks.Any(x => x.Contains(remoteIp)); }); - app.UseSwagger(); + app.MapOpenApi(); Action scalarOptions = options => options - .WithOpenApiRoutePattern("/swagger/{documentName}/swagger.json") - .AddDocument("1", "Version 1") - .AddDocument("2", "Version 2"); + .WithOpenApiRoutePattern("/openapi/{documentName}.json"); app.MapScalarApiReference("/scalar/viewer", scalarOptions); diff --git a/Common/OpenShockServiceHelper.cs b/Common/OpenShockServiceHelper.cs index c325590a..33abb2fd 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OpenApi; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection.Extensions; using OpenShock.Common.Authentication; @@ -9,6 +10,7 @@ using OpenShock.Common.Authentication.Services; using OpenShock.Common.ExceptionHandle; using OpenShock.Common.JsonSerialization; +using OpenShock.Common.OpenApi; using OpenShock.Common.OpenShockDb; using OpenShock.Common.Options; using OpenShock.Common.Problems; @@ -116,7 +118,18 @@ public static IServiceCollection AddOpenShockServices(this IServiceCollection se x.JsonSerializerOptions.Converters.Add(new PermissionTypeConverter()); x.JsonSerializerOptions.Converters.Add(new CustomJsonStringEnumConverter()); }); - + + services.AddOpenApi(options => + { + // Always inline enum schemas + options.CreateSchemaReferenceId = (type) => + type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type); + + options.AddDocumentTransformer(); + options.AddOperationTransformer(); + options.AddSchemaTransformer(); + }); + var apiVersioningBuilder = services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); diff --git a/Common/Swagger/AttributeFilter.cs b/Common/Swagger/AttributeFilter.cs deleted file mode 100644 index 0010df57..00000000 --- a/Common/Swagger/AttributeFilter.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.OpenApi.Models; -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(OpenApiParameter 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(OpenApiSchema 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 - { - OpenShockAuthSchemas.UserSessionCookie => [ - new OpenApiSecurityRequirement {{ - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemas.UserSessionCookie, - Type = ReferenceType.SecurityScheme, - } - }, - securityInfos - }} - ], - OpenShockAuthSchemas.ApiToken => [ - new OpenApiSecurityRequirement {{ - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemas.ApiToken, - Type = ReferenceType.SecurityScheme, - } - }, - securityInfos - }} - ], - OpenShockAuthSchemas.HubToken => [ - new OpenApiSecurityRequirement {{ - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemas.HubToken, - Type = ReferenceType.SecurityScheme - } - }, - securityInfos - }} - ], - _ => [], - }); - } - - operation.Security = securityRequirements; - } - else - { - operation.Security.Clear(); - } - } -} \ No newline at end of file diff --git a/Common/Swagger/SwaggerGenExtensions.cs b/Common/Swagger/SwaggerGenExtensions.cs deleted file mode 100644 index c0b130ee..00000000 --- a/Common/Swagger/SwaggerGenExtensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Microsoft.OpenApi.Models; -using OpenShock.Common.Constants; -using OpenShock.Common.DataAnnotations; -using OpenShock.Common.Models; -using Semver; -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 IServiceCollection services) 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 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(OpenShockAuthSchemas.UserSessionCookie, new OpenApiSecurityScheme - { - Name = AuthConstants.UserSessionCookieName, - Description = "Enter user session cookie", - In = ParameterLocation.Cookie, - Type = SecuritySchemeType.ApiKey, - Scheme = OpenShockAuthSchemas.UserSessionCookie, - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemas.UserSessionCookie, - Type = ReferenceType.SecurityScheme, - } - }); - options.AddSecurityDefinition(OpenShockAuthSchemas.ApiToken, new OpenApiSecurityScheme - { - Name = AuthConstants.ApiTokenHeaderName, - Description = "Enter API Token", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey, - Scheme = OpenShockAuthSchemas.ApiToken, - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemas.ApiToken, - Type = ReferenceType.SecurityScheme, - } - }); - options.AddSecurityDefinition(OpenShockAuthSchemas.HubToken, new OpenApiSecurityScheme - { - Name = AuthConstants.HubTokenHeaderName, - Description = "Enter hub token", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey, - Scheme = OpenShockAuthSchemas.HubToken, - Reference = new OpenApiReference - { - Id = OpenShockAuthSchemas.HubToken, - Type = ReferenceType.SecurityScheme, - } - }); - options.AddServer(new OpenApiServer { Url = "https://api.openshock.app" }); - options.AddServer(new OpenApiServer { Url = "https://api.openshock.dev" }); -#if DEBUG - options.AddServer(new OpenApiServer { Url = "https://localhost" }); -#endif - 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 2dbc2694..00000000 --- a/Common/Utils/ConfigureSwaggerOptions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Asp.Versioning.ApiExplorer; -using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; -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/LiveControlGateway/Program.cs b/LiveControlGateway/Program.cs index 42d553fe..1d3b4629 100644 --- a/LiveControlGateway/Program.cs +++ b/LiveControlGateway/Program.cs @@ -4,7 +4,6 @@ using OpenShock.Common.JsonSerialization; 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; @@ -33,8 +32,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSwaggerExt(); - //services.AddHealthChecks().AddCheck("database"); builder.Services.AddHostedService(); From b3ad971534e115a2be1ec7096c7fd3ae5091f85e Mon Sep 17 00:00:00 2001 From: hhvrc Date: Mon, 28 Apr 2025 15:37:02 +0200 Subject: [PATCH 02/19] Cache OpenApi documents and configure versions seperately --- Common/OpenShockMiddlewareHelper.cs | 3 ++- Common/OpenShockServiceHelper.cs | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 57257419..3e32cd45 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -73,7 +73,8 @@ public static IApplicationBuilder UseCommonOpenShockMiddleware(this WebApplicati return remoteIp != null && metricsAllowedIpNetworks.Any(x => x.Contains(remoteIp)); }); - app.MapOpenApi(); + app.MapOpenApi() + .CacheOutput(); Action scalarOptions = options => options diff --git a/Common/OpenShockServiceHelper.cs b/Common/OpenShockServiceHelper.cs index 33abb2fd..463fb2b2 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -119,7 +119,17 @@ public static IServiceCollection AddOpenShockServices(this IServiceCollection se x.JsonSerializerOptions.Converters.Add(new CustomJsonStringEnumConverter()); }); - services.AddOpenApi(options => + services.AddOpenApi("1", options => + { + // Always inline enum schemas + options.CreateSchemaReferenceId = (type) => + type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type); + + options.AddDocumentTransformer(); + options.AddOperationTransformer(); + options.AddSchemaTransformer(); + }); + services.AddOpenApi("2", options => { // Always inline enum schemas options.CreateSchemaReferenceId = (type) => From 1af6d7c4fecd401f579a5771ee4456358f3f6ca7 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 29 Apr 2025 12:04:53 +0200 Subject: [PATCH 03/19] Cleaner scalar config --- Common/OpenShockMiddlewareHelper.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 3e32cd45..5c6258a8 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -76,11 +76,12 @@ public static IApplicationBuilder UseCommonOpenShockMiddleware(this WebApplicati app.MapOpenApi() .CacheOutput(); - Action scalarOptions = options => + app.MapScalarApiReference("/scalar/viewer", options => options - .WithOpenApiRoutePattern("/openapi/{documentName}.json"); - - app.MapScalarApiReference("/scalar/viewer", scalarOptions); + .WithOpenApiRoutePattern("/openapi/{documentName}.json") + .AddDocument("1", "Version 1") + .AddDocument("2", "Version 2") + ); app.MapControllers(); From a16017e4a391279f47b6a4a501e19ee062199805 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 29 Apr 2025 12:26:02 +0200 Subject: [PATCH 04/19] Specify servers in OpenAPI docs --- Common/OpenApi/OpenApiDocumentTransformer.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Common/OpenApi/OpenApiDocumentTransformer.cs b/Common/OpenApi/OpenApiDocumentTransformer.cs index 8a31993e..071ab238 100644 --- a/Common/OpenApi/OpenApiDocumentTransformer.cs +++ b/Common/OpenApi/OpenApiDocumentTransformer.cs @@ -1,12 +1,22 @@ using Microsoft.AspNetCore.OpenApi; using Microsoft.OpenApi.Models; +using OpenShock.Common.Authentication; +using OpenShock.Common.Constants; namespace OpenShock.Common.OpenApi; -public sealed class OpenApiDocumentTransformer : IOpenApiDocumentTransformer +public sealed class OpenApiDocumentTransformer : IOpenApiDocumentTransformer where TProgram : class { public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) { + document.Servers = [ +#if DEBUG + new OpenApiServer { Url = "https://localhost" }, +#endif + new OpenApiServer { Url = "https://api.openshock.app" }, + new OpenApiServer { Url = "https://api.openshock.dev" } + ]; + return Task.CompletedTask; } } From 0537d2fc294d675d4144b0f55b80f2127989212b Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 29 Apr 2025 12:40:58 +0200 Subject: [PATCH 05/19] Update document metadata --- Common/OpenApi/OpenApiDocumentTransformer.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Common/OpenApi/OpenApiDocumentTransformer.cs b/Common/OpenApi/OpenApiDocumentTransformer.cs index 071ab238..5bb34329 100644 --- a/Common/OpenApi/OpenApiDocumentTransformer.cs +++ b/Common/OpenApi/OpenApiDocumentTransformer.cs @@ -9,6 +9,24 @@ public sealed class OpenApiDocumentTransformer : IOpenApiDocumentTrans { public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) { + document.Info = new() + { + Title = "OpenShock API", + Description = "Test description of API", + Version = "V" + context.DocumentName, + TermsOfService = new Uri("https://github.com/OpenShock/"), + Contact = new() + { + Name = "Support", + Url = new Uri("mailto:support@openshock.app"), + Email = "support@openshock.app" + }, + License = new() + { + Name = "GNU affero General Public License v3.0", + Url = new Uri("https://github.com/OpenShock/API/blob/develop/LICENSE") + } + }; document.Servers = [ #if DEBUG new OpenApiServer { Url = "https://localhost" }, From 44fdf36f6bdb94a29ad48aa9fe00da9c77bb729d Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 29 Apr 2025 12:41:45 +0200 Subject: [PATCH 06/19] Oops --- Common/OpenApi/OpenApiDocumentTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/OpenApi/OpenApiDocumentTransformer.cs b/Common/OpenApi/OpenApiDocumentTransformer.cs index 5bb34329..fb715a1d 100644 --- a/Common/OpenApi/OpenApiDocumentTransformer.cs +++ b/Common/OpenApi/OpenApiDocumentTransformer.cs @@ -5,7 +5,7 @@ namespace OpenShock.Common.OpenApi; -public sealed class OpenApiDocumentTransformer : IOpenApiDocumentTransformer where TProgram : class +public sealed class OpenApiDocumentTransformer : IOpenApiDocumentTransformer { public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) { From 3f950a8a649f33c23d924cd4065064c3c541409f Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 29 Apr 2025 14:06:06 +0200 Subject: [PATCH 07/19] Temp fix for schema generation exceptions --- API/Controller/Devices/DevicesController.cs | 4 ++-- API/Controller/Shares/Links/CreateShareLink.cs | 2 ++ API/Controller/Shares/Links/PauseShockerShareLink.cs | 2 ++ API/Controller/Shares/V2CreateShareRequest.cs | 2 +- API/Controller/Shockers/CreateShockerController.cs | 2 ++ API/Controller/Shockers/PauseShockerController.cs | 4 +++- API/Controller/Shockers/ShareShockerController.cs | 4 ++++ 7 files changed, 16 insertions(+), 4 deletions(-) diff --git a/API/Controller/Devices/DevicesController.cs b/API/Controller/Devices/DevicesController.cs index 99b3f5b4..c7691bff 100644 --- a/API/Controller/Devices/DevicesController.cs +++ b/API/Controller/Devices/DevicesController.cs @@ -147,7 +147,7 @@ public async Task RemoveDevice([FromRoute] Guid deviceId, [FromSe /// Successfully created device [HttpPost] [TokenPermission(PermissionType.Devices_Edit)] - [ProducesResponseType(StatusCodes.Status201Created, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status201Created, MediaTypeNames.Text.Plain)] [MapToApiVersion("1")] public Task CreateDevice([FromServices] IDeviceUpdateService updateService) => CreateDeviceV2(new HubCreateRequest @@ -162,7 +162,7 @@ public Task CreateDevice([FromServices] IDeviceUpdateService updateService /// Successfully created device [HttpPost] [TokenPermission(PermissionType.Devices_Edit)] - [ProducesResponseType(StatusCodes.Status201Created, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status201Created, MediaTypeNames.Text.Plain)] [MapToApiVersion("2")] public async Task CreateDeviceV2([FromBody] HubCreateRequest data, [FromServices] IDeviceUpdateService updateService) { diff --git a/API/Controller/Shares/Links/CreateShareLink.cs b/API/Controller/Shares/Links/CreateShareLink.cs index 8e96579f..859cc9f0 100644 --- a/API/Controller/Shares/Links/CreateShareLink.cs +++ b/API/Controller/Shares/Links/CreateShareLink.cs @@ -9,6 +9,7 @@ namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController { + /* /// /// Create a new share link /// @@ -29,4 +30,5 @@ public async Task CreateShareLink([FromBody] ShareLinkCreate body return RespondSuccessLegacy(entity.Id); } + */ } \ No newline at end of file diff --git a/API/Controller/Shares/Links/PauseShockerShareLink.cs b/API/Controller/Shares/Links/PauseShockerShareLink.cs index 35214e46..c7d5e704 100644 --- a/API/Controller/Shares/Links/PauseShockerShareLink.cs +++ b/API/Controller/Shares/Links/PauseShockerShareLink.cs @@ -12,6 +12,7 @@ namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController { + /* /// /// Pause a shocker in a share link /// @@ -39,4 +40,5 @@ await _db.ShockerSharesLinksShockers.Where(x => return RespondSuccessLegacy(ShareLinkUtils.GetPausedReason(shocker.Paused, shocker.Shocker.Paused)); } + */ } \ No newline at end of file diff --git a/API/Controller/Shares/V2CreateShareRequest.cs b/API/Controller/Shares/V2CreateShareRequest.cs index 06877990..c0e73412 100644 --- a/API/Controller/Shares/V2CreateShareRequest.cs +++ b/API/Controller/Shares/V2CreateShareRequest.cs @@ -14,7 +14,7 @@ namespace OpenShock.API.Controller.Shares; public sealed partial class SharesController { [HttpPost("requests")] - [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Text.Plain)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // UserNotFound, ShareCreateShockerNotFound [ProducesResponseType(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] // ShareCreateCannotShareWithSelf [ApiVersion("2")] diff --git a/API/Controller/Shockers/CreateShockerController.cs b/API/Controller/Shockers/CreateShockerController.cs index 520a045e..7f4afe2e 100644 --- a/API/Controller/Shockers/CreateShockerController.cs +++ b/API/Controller/Shockers/CreateShockerController.cs @@ -15,6 +15,7 @@ namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController { + /* /// /// Register a shocker /// @@ -54,4 +55,5 @@ await deviceUpdateService.UpdateDeviceForAllShared(CurrentUser.Id, device, return RespondSuccessLegacy(shocker.Id, statusCode: HttpStatusCode.Created); } + */ } \ No newline at end of file diff --git a/API/Controller/Shockers/PauseShockerController.cs b/API/Controller/Shockers/PauseShockerController.cs index 5c653004..8b68b4e8 100644 --- a/API/Controller/Shockers/PauseShockerController.cs +++ b/API/Controller/Shockers/PauseShockerController.cs @@ -14,6 +14,7 @@ namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController { + /* /// /// Pause or unpause a shocker /// @@ -24,7 +25,7 @@ public sealed partial class ShockerController /// Shocker not found or does not belong to you [HttpPost("{shockerId}/pause")] [TokenPermission(PermissionType.Shockers_Pause)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task PauseShocker([FromRoute] Guid shockerId, [FromBody] PauseRequest body, @@ -40,4 +41,5 @@ public async Task PauseShocker([FromRoute] Guid shockerId, [FromB return RespondSuccessLegacy(body.Pause); } + */ } \ No newline at end of file diff --git a/API/Controller/Shockers/ShareShockerController.cs b/API/Controller/Shockers/ShareShockerController.cs index e2c960b2..55187354 100644 --- a/API/Controller/Shockers/ShareShockerController.cs +++ b/API/Controller/Shockers/ShareShockerController.cs @@ -97,6 +97,7 @@ public sealed class ShareCodeInfo public required DateTime CreatedOn { get; set; } } + /* /// /// Create a share code for a shocker /// @@ -138,6 +139,7 @@ [FromServices] IDeviceUpdateService deviceUpdateService return RespondSuccessLegacy(newCode.Id); } + */ /// /// Remove a share code for a shocker @@ -214,6 +216,7 @@ public async Task ShockerShareCodeUpdate( return Ok(); } + /* /// /// Pause/Unpause a share code for a shocker /// @@ -248,4 +251,5 @@ public async Task ShockerShareCodePause( return RespondSuccessLegacy(body.Pause); } + */ } \ No newline at end of file From eb183a93f2c0d1d1894a3bb0164e1f7062ec2e74 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Tue, 29 Apr 2025 14:06:24 +0200 Subject: [PATCH 08/19] Change scalar endpoint --- Common/OpenShockMiddlewareHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 5c6258a8..591087a6 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -76,7 +76,7 @@ public static IApplicationBuilder UseCommonOpenShockMiddleware(this WebApplicati app.MapOpenApi() .CacheOutput(); - app.MapScalarApiReference("/scalar/viewer", options => + app.MapScalarApiReference("/openapi/scalar", options => options .WithOpenApiRoutePattern("/openapi/{documentName}.json") .AddDocument("1", "Version 1") From ff7b8593d7c80b7d198bd88d36a7405f4aca4c6a Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 29 Apr 2025 18:32:55 +0200 Subject: [PATCH 09/19] Well... that fixed it lol --- API/Controller/Admin/GetOnlineDevices.cs | 2 +- API/Controller/Device/AssignLCG.cs | 2 +- API/Controller/Device/GetSelf.cs | 2 +- API/Controller/Device/Pair.cs | 2 +- API/Controller/Devices/DeviceOtaController.cs | 2 +- API/Controller/Devices/DevicesController.cs | 8 ++++---- API/Controller/Devices/ShockersController.cs | 2 +- API/Controller/Public/GetStats.cs | 5 ++--- API/Controller/Public/PublicShareController.cs | 4 ++-- API/Controller/Shares/Links/CreateShareLink.cs | 7 ++----- API/Controller/Shares/Links/ListShareLinks.cs | 5 ++--- .../Shares/Links/PauseShockerShareLink.cs | 4 +--- .../Shockers/ControlLogController.cs | 2 +- .../Shockers/CreateShockerController.cs | 4 +--- .../Shockers/GetShockerController.cs | 2 +- .../Shockers/OwnShockerController.cs | 5 ++--- .../Shockers/PauseShockerController.cs | 4 +--- .../Shockers/ShareShockerController.cs | 12 ++++-------- .../Shockers/SharedShockersController.cs | 5 ++--- API/Controller/Version/_ApiController.cs | 11 +++++------ Common/Models/LegacySuccessResponse.cs | 18 ++++++++++++++++++ Common/OpenShockControllerBase.cs | 8 ++------ 22 files changed, 56 insertions(+), 60 deletions(-) create mode 100644 Common/Models/LegacySuccessResponse.cs diff --git a/API/Controller/Admin/GetOnlineDevices.cs b/API/Controller/Admin/GetOnlineDevices.cs index 403a851c..9806f7f2 100644 --- a/API/Controller/Admin/GetOnlineDevices.cs +++ b/API/Controller/Admin/GetOnlineDevices.cs @@ -20,7 +20,7 @@ public sealed partial class AdminController /// All online devices /// Unauthorized [HttpGet("monitoring/onlineDevices")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task GetOnlineDevices() { var devicesOnline = _redis.RedisCollection(false); diff --git a/API/Controller/Device/AssignLCG.cs b/API/Controller/Device/AssignLCG.cs index 9bc65e40..6c4bcb33 100644 --- a/API/Controller/Device/AssignLCG.cs +++ b/API/Controller/Device/AssignLCG.cs @@ -19,7 +19,7 @@ public sealed partial class DeviceController /// Successfully assigned LCG node /// Unable to find suitable LCG node [HttpGet("assignLCG")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable, MediaTypeNames.Application.ProblemJson)] // NoLcgNodesAvailable public async Task GetLiveControlGateway([FromServices] ILCGNodeProvisioner geoLocation, [FromServices] IWebHostEnvironment env) diff --git a/API/Controller/Device/GetSelf.cs b/API/Controller/Device/GetSelf.cs index fce30776..32792b1d 100644 --- a/API/Controller/Device/GetSelf.cs +++ b/API/Controller/Device/GetSelf.cs @@ -14,7 +14,7 @@ public sealed partial class DeviceController /// /// The device information was successfully retrieved. [HttpGet("self")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task GetSelf() { var shockers = await _db.Shockers.Where(x => x.Device == CurrentDevice.Id).Select(x => new MinimalShocker diff --git a/API/Controller/Device/Pair.cs b/API/Controller/Device/Pair.cs index 4d842ef8..e01bb242 100644 --- a/API/Controller/Device/Pair.cs +++ b/API/Controller/Device/Pair.cs @@ -22,7 +22,7 @@ public sealed partial class DeviceController [AllowAnonymous] [HttpGet("pair/{pairCode}", Name = "Pair")] [HttpGet("~/{version:apiVersion}/pair/{pairCode}", Name = "Pair_DEPRECATED")] // Backwards compatibility - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PairCodeNotFound public async Task Pair([FromRoute] string pairCode) { diff --git a/API/Controller/Devices/DeviceOtaController.cs b/API/Controller/Devices/DeviceOtaController.cs index d4650859..10b4466d 100644 --- a/API/Controller/Devices/DeviceOtaController.cs +++ b/API/Controller/Devices/DeviceOtaController.cs @@ -40,7 +40,7 @@ public DevicesOtaController(OpenShockContext db, ILogger logg /// Could not find device or you do not have access to it [HttpGet("{deviceId}/ota")] [MapToApiVersion("1")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [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 c7691bff..af35e681 100644 --- a/API/Controller/Devices/DevicesController.cs +++ b/API/Controller/Devices/DevicesController.cs @@ -22,7 +22,7 @@ public sealed partial class DevicesController /// /// All devices for the current user [HttpGet] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [MapToApiVersion("1")] public IActionResult ListDevices() { @@ -45,7 +45,7 @@ public IActionResult ListDevices() /// /// The device [HttpGet("{deviceId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [MapToApiVersion("1")] public async Task GetDeviceById([FromRoute] Guid deviceId) @@ -190,7 +190,7 @@ public async Task CreateDeviceV2([FromBody] HubCreateRequest data, [FromSe /// Device does not exist or does not belong to you [HttpGet("{deviceId}/pair")] [TokenPermission(PermissionType.Devices_Edit)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [MapToApiVersion("1")] public async Task GetPairCode([FromRoute] Guid deviceId) @@ -225,7 +225,7 @@ public async Task GetPairCode([FromRoute] Guid deviceId) /// Device is online but not connected to a LCG node, you might need to upgrade your firmware to use this feature /// Internal server error, lcg node could not be found [HttpGet("{deviceId}/lcg")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound, DeviceIsNotOnline [ProducesResponseType(StatusCodes.Status412PreconditionFailed, MediaTypeNames.Application.ProblemJson)] // DeviceNotConnectedToGateway [MapToApiVersion("1")] diff --git a/API/Controller/Devices/ShockersController.cs b/API/Controller/Devices/ShockersController.cs index 4283b9bf..1f0f6b06 100644 --- a/API/Controller/Devices/ShockersController.cs +++ b/API/Controller/Devices/ShockersController.cs @@ -19,7 +19,7 @@ public sealed partial class DevicesController /// All shockers for the device /// Device does not exists or you do not have access to it. [HttpGet("{deviceId}/shockers")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [MapToApiVersion("1")] public async Task GetShockers([FromRoute] Guid deviceId) diff --git a/API/Controller/Public/GetStats.cs b/API/Controller/Public/GetStats.cs index 1f8153ad..6f590786 100644 --- a/API/Controller/Public/GetStats.cs +++ b/API/Controller/Public/GetStats.cs @@ -17,13 +17,12 @@ public sealed partial class PublicController /// /// The statistics were successfully retrieved. [HttpGet("stats")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - public async Task GetOnlineDevicesStatistics([FromServices] IConnectionMultiplexer redisConnectionMultiplexer) + public async Task> GetOnlineDevicesStatistics([FromServices] IConnectionMultiplexer redisConnectionMultiplexer) { var ft = redisConnectionMultiplexer.GetDatabase().FT(); var deviceOnlineInfo = await ft.InfoAsync(DeviceOnline.IndexName); - return RespondSuccessLegacy(new StatsResponse + return new LegacySuccessResponse(new StatsResponse { DevicesOnline = deviceOnlineInfo.NumDocs }); diff --git a/API/Controller/Public/PublicShareController.cs b/API/Controller/Public/PublicShareController.cs index 85988bfd..cff6eddb 100644 --- a/API/Controller/Public/PublicShareController.cs +++ b/API/Controller/Public/PublicShareController.cs @@ -21,8 +21,8 @@ public sealed partial class PublicController /// The share link information was successfully retrieved. /// The share link does not exist. [HttpGet("shares/links/{shareLinkId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound public async Task GetShareLink([FromRoute] Guid shareLinkId) { var shareLink = await _db.ShockerSharesLinks.Where(x => x.Id == shareLinkId).Select(x => new diff --git a/API/Controller/Shares/Links/CreateShareLink.cs b/API/Controller/Shares/Links/CreateShareLink.cs index 859cc9f0..0f3db334 100644 --- a/API/Controller/Shares/Links/CreateShareLink.cs +++ b/API/Controller/Shares/Links/CreateShareLink.cs @@ -9,14 +9,12 @@ namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController { - /* /// /// Create a new share link /// /// The created share link [HttpPost(Name = "CreateShareLink")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - public async Task CreateShareLink([FromBody] ShareLinkCreate body) + public async Task> CreateShareLink([FromBody] ShareLinkCreate body) { var entity = new ShockerSharesLink { @@ -28,7 +26,6 @@ public async Task CreateShareLink([FromBody] ShareLinkCreate body _db.ShockerSharesLinks.Add(entity); await _db.SaveChangesAsync(); - return RespondSuccessLegacy(entity.Id); + return new LegacySuccessResponse(entity.Id); } - */ } \ No newline at end of file diff --git a/API/Controller/Shares/Links/ListShareLinks.cs b/API/Controller/Shares/Links/ListShareLinks.cs index 1c21fe92..b7885630 100644 --- a/API/Controller/Shares/Links/ListShareLinks.cs +++ b/API/Controller/Shares/Links/ListShareLinks.cs @@ -14,14 +14,13 @@ public sealed partial class ShareLinksController /// /// All share links for the current user [HttpGet] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - public IActionResult List() + public LegacySuccessResponse> List() { var ownShareLinks = _db.ShockerSharesLinks .Where(x => x.OwnerId == CurrentUser.Id) .Select(x => ShareLinkResponse.GetFromEf(x)) .AsAsyncEnumerable(); - return RespondSuccessLegacy(ownShareLinks); + return new LegacySuccessResponse>(ownShareLinks); } } \ No newline at end of file diff --git a/API/Controller/Shares/Links/PauseShockerShareLink.cs b/API/Controller/Shares/Links/PauseShockerShareLink.cs index c7d5e704..ca50b4a9 100644 --- a/API/Controller/Shares/Links/PauseShockerShareLink.cs +++ b/API/Controller/Shares/Links/PauseShockerShareLink.cs @@ -12,7 +12,6 @@ namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController { - /* /// /// Pause a shocker in a share link /// @@ -23,7 +22,7 @@ public sealed partial class ShareLinksController /// Share link or shocker does not exist /// Shocker does not exist in share link [HttpPost("{shareLinkId}/{shockerId}/pause")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound, ShockerNotInShareLink public async Task PauseShocker([FromRoute] Guid shareLinkId, [FromRoute] Guid shockerId, [FromBody] PauseRequest body) { @@ -40,5 +39,4 @@ await _db.ShockerSharesLinksShockers.Where(x => return RespondSuccessLegacy(ShareLinkUtils.GetPausedReason(shocker.Paused, shocker.Shocker.Paused)); } - */ } \ No newline at end of file diff --git a/API/Controller/Shockers/ControlLogController.cs b/API/Controller/Shockers/ControlLogController.cs index 28990325..d1878ff6 100644 --- a/API/Controller/Shockers/ControlLogController.cs +++ b/API/Controller/Shockers/ControlLogController.cs @@ -24,7 +24,7 @@ public sealed partial class ShockerController /// The logs /// Shocker does not exist [HttpGet("{shockerId}/logs")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task GetShockerLogs([FromRoute] Guid shockerId, [FromQuery] uint offset = 0, diff --git a/API/Controller/Shockers/CreateShockerController.cs b/API/Controller/Shockers/CreateShockerController.cs index 7f4afe2e..fbfbcaf1 100644 --- a/API/Controller/Shockers/CreateShockerController.cs +++ b/API/Controller/Shockers/CreateShockerController.cs @@ -15,7 +15,6 @@ namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController { - /* /// /// Register a shocker /// @@ -23,7 +22,7 @@ public sealed partial class ShockerController /// You can have a maximum of 11 Shockers per Device. /// Device does not exist [HttpPost] - [ProducesResponseType>(StatusCodes.Status201Created, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status201Created, MediaTypeNames.Application.Json)] [TokenPermission(PermissionType.Shockers_Edit)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [ProducesResponseType(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] // TooManyShockers @@ -55,5 +54,4 @@ await deviceUpdateService.UpdateDeviceForAllShared(CurrentUser.Id, device, return RespondSuccessLegacy(shocker.Id, statusCode: HttpStatusCode.Created); } - */ } \ No newline at end of file diff --git a/API/Controller/Shockers/GetShockerController.cs b/API/Controller/Shockers/GetShockerController.cs index eb94b85c..cfe892e7 100644 --- a/API/Controller/Shockers/GetShockerController.cs +++ b/API/Controller/Shockers/GetShockerController.cs @@ -19,7 +19,7 @@ public sealed partial class ShockerController /// The shocker information was successfully retrieved. /// The shocker does not exist or you do not have access to it. [HttpGet("{shockerId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task GetShockerById([FromRoute] Guid shockerId) diff --git a/API/Controller/Shockers/OwnShockerController.cs b/API/Controller/Shockers/OwnShockerController.cs index 6721007e..b8ba725a 100644 --- a/API/Controller/Shockers/OwnShockerController.cs +++ b/API/Controller/Shockers/OwnShockerController.cs @@ -15,9 +15,8 @@ public sealed partial class ShockerController /// /// The shockers were successfully retrieved. [HttpGet("own")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [MapToApiVersion("1")] - public IActionResult ListShockers() + public LegacySuccessResponse> ListShockers() { var shockers = _db.Devices .Where(x => x.Owner == CurrentUser.Id) @@ -41,6 +40,6 @@ public IActionResult ListShockers() }) .AsAsyncEnumerable(); - return RespondSuccessLegacy(shockers); + return new LegacySuccessResponse>(shockers); } } \ No newline at end of file diff --git a/API/Controller/Shockers/PauseShockerController.cs b/API/Controller/Shockers/PauseShockerController.cs index 8b68b4e8..3bbb2005 100644 --- a/API/Controller/Shockers/PauseShockerController.cs +++ b/API/Controller/Shockers/PauseShockerController.cs @@ -14,7 +14,6 @@ namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController { - /* /// /// Pause or unpause a shocker /// @@ -25,7 +24,7 @@ public sealed partial class ShockerController /// Shocker not found or does not belong to you [HttpPost("{shockerId}/pause")] [TokenPermission(PermissionType.Shockers_Pause)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task PauseShocker([FromRoute] Guid shockerId, [FromBody] PauseRequest body, @@ -41,5 +40,4 @@ public async Task PauseShocker([FromRoute] Guid shockerId, [FromB return RespondSuccessLegacy(body.Pause); } - */ } \ No newline at end of file diff --git a/API/Controller/Shockers/ShareShockerController.cs b/API/Controller/Shockers/ShareShockerController.cs index 55187354..ebdf5a44 100644 --- a/API/Controller/Shockers/ShareShockerController.cs +++ b/API/Controller/Shockers/ShareShockerController.cs @@ -25,7 +25,7 @@ public sealed partial class ShockerController /// OK /// The shocker does not exist or you do not have access to it. [HttpGet("{shockerId}/shares")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task GetShockerShares([FromRoute] Guid shockerId) @@ -71,7 +71,7 @@ public async Task GetShockerShares([FromRoute] Guid shockerId) /// /// OK [HttpGet("{shockerId}/shareCodes")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task ShockerShareCodeList([FromRoute] Guid shockerId) @@ -97,7 +97,6 @@ public sealed class ShareCodeInfo public required DateTime CreatedOn { get; set; } } - /* /// /// Create a share code for a shocker /// @@ -109,7 +108,7 @@ public sealed class ShareCodeInfo /// [HttpPost("{shockerId}/shares")] [TokenPermission(PermissionType.Shockers_Edit)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task ShockerShareCodeCreate( @@ -139,7 +138,6 @@ [FromServices] IDeviceUpdateService deviceUpdateService return RespondSuccessLegacy(newCode.Id); } - */ /// /// Remove a share code for a shocker @@ -216,7 +214,6 @@ public async Task ShockerShareCodeUpdate( return Ok(); } - /* /// /// Pause/Unpause a share code for a shocker /// @@ -228,7 +225,7 @@ public async Task ShockerShareCodeUpdate( /// The share code does not exist or you do not have access to it. [HttpPost("{shockerId}/shares/{sharedWithUserId}/pause")] [TokenPermission(PermissionType.Shockers_Pause)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task ShockerShareCodePause( @@ -251,5 +248,4 @@ public async Task ShockerShareCodePause( return RespondSuccessLegacy(body.Pause); } - */ } \ No newline at end of file diff --git a/API/Controller/Shockers/SharedShockersController.cs b/API/Controller/Shockers/SharedShockersController.cs index 7c71e8da..c343124f 100644 --- a/API/Controller/Shockers/SharedShockersController.cs +++ b/API/Controller/Shockers/SharedShockersController.cs @@ -25,9 +25,8 @@ public sealed partial class ShockerController /// /// The shockers were successfully retrieved. [HttpGet("shared")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [MapToApiVersion("1")] - public async Task ListSharedShockers() + public async Task>> ListSharedShockers() { var sharedShockersData = await _db.ShockerShares .AsNoTracking() @@ -81,6 +80,6 @@ public async Task ListSharedShockers() .ToArray() }); - return RespondSuccessLegacy(sharesResponse); + return new LegacySuccessResponse>(sharesResponse); } } \ No newline at end of file diff --git a/API/Controller/Version/_ApiController.cs b/API/Controller/Version/_ApiController.cs index 7414696c..1c68335f 100644 --- a/API/Controller/Version/_ApiController.cs +++ b/API/Controller/Version/_ApiController.cs @@ -25,8 +25,7 @@ public sealed partial class VersionController : OpenShockControllerBase /// /// The version was successfully retrieved. [HttpGet] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - public IActionResult GetBackendVersion( + public BaseResponse GetBackendVersion( [FromServices] IOptions frontendOptions, [FromServices] IOptions turnstileOptions ) @@ -34,8 +33,9 @@ [FromServices] IOptions turnstileOptions var frontendConfig = frontendOptions.Value; var turnstileConfig = turnstileOptions.Value; - return RespondSuccessLegacy( - data: new RootResponse + return new BaseResponse( + "OpenShock", + new RootResponse { Version = OpenShockBackendVersion, Commit = GitHashAttribute.FullHash, @@ -43,8 +43,7 @@ [FromServices] IOptions turnstileOptions FrontendUrl = frontendConfig.BaseUrl, ShortLinkUrl = frontendConfig.ShortUrl, TurnstileSiteKey = turnstileConfig.SiteKey - }, - message: "OpenShock" + } ); } diff --git a/Common/Models/LegacySuccessResponse.cs b/Common/Models/LegacySuccessResponse.cs new file mode 100644 index 00000000..77391660 --- /dev/null +++ b/Common/Models/LegacySuccessResponse.cs @@ -0,0 +1,18 @@ +// ReSharper disable UnusedAutoPropertyAccessor.Global + +using System.Text.Json; + +namespace OpenShock.Common.Models; + +public sealed class LegacySuccessResponse +{ + public string Message { get; } = ""; + public T Data { get; set; } + + public LegacySuccessResponse(T data) + { + Data = data; + } + + public override string ToString() => JsonSerializer.Serialize(this); +} \ No newline at end of file diff --git a/Common/OpenShockControllerBase.cs b/Common/OpenShockControllerBase.cs index efcf697a..f5732abe 100644 --- a/Common/OpenShockControllerBase.cs +++ b/Common/OpenShockControllerBase.cs @@ -13,14 +13,10 @@ public class OpenShockControllerBase : ControllerBase public ObjectResult Problem(OpenShockProblem problem) => problem.ToObjectResult(HttpContext); [NonAction] - public ObjectResult RespondSuccessLegacy(T data, string message = "", HttpStatusCode statusCode = HttpStatusCode.OK) + public ObjectResult RespondSuccessLegacy(T data, HttpStatusCode statusCode = HttpStatusCode.OK) { Response.StatusCode = (int)statusCode; - return new ObjectResult(new BaseResponse - { - Data = data, - Message = message - }); + return new ObjectResult(new LegacySuccessResponse(data)); } [NonAction] From e41942fcf43fcf37c1b9668417121d3356f4044c Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 29 Apr 2025 18:40:38 +0200 Subject: [PATCH 10/19] more fixes --- API/Controller/Version/_ApiController.cs | 8 ++++---- Common/Models/LegacySuccessResponse.cs | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/API/Controller/Version/_ApiController.cs b/API/Controller/Version/_ApiController.cs index 1c68335f..3d64ac25 100644 --- a/API/Controller/Version/_ApiController.cs +++ b/API/Controller/Version/_ApiController.cs @@ -25,7 +25,7 @@ public sealed partial class VersionController : OpenShockControllerBase /// /// The version was successfully retrieved. [HttpGet] - public BaseResponse GetBackendVersion( + public LegacySuccessResponse GetBackendVersion( [FromServices] IOptions frontendOptions, [FromServices] IOptions turnstileOptions ) @@ -33,8 +33,7 @@ [FromServices] IOptions turnstileOptions var frontendConfig = frontendOptions.Value; var turnstileConfig = turnstileOptions.Value; - return new BaseResponse( - "OpenShock", + return new LegacySuccessResponse( new RootResponse { Version = OpenShockBackendVersion, @@ -43,7 +42,8 @@ [FromServices] IOptions turnstileOptions FrontendUrl = frontendConfig.BaseUrl, ShortLinkUrl = frontendConfig.ShortUrl, TurnstileSiteKey = turnstileConfig.SiteKey - } + }, + "OpenShock" ); } diff --git a/Common/Models/LegacySuccessResponse.cs b/Common/Models/LegacySuccessResponse.cs index 77391660..b3c70162 100644 --- a/Common/Models/LegacySuccessResponse.cs +++ b/Common/Models/LegacySuccessResponse.cs @@ -6,11 +6,12 @@ namespace OpenShock.Common.Models; public sealed class LegacySuccessResponse { - public string Message { get; } = ""; + public string Message { get; set; } public T Data { get; set; } - public LegacySuccessResponse(T data) + public LegacySuccessResponse(T data, string message = "") { + Message = message; Data = data; } From 14b5900a283643984dbf5ae8e305bc34b99d569a Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 29 Apr 2025 19:12:37 +0200 Subject: [PATCH 11/19] Update OpenApiDocumentTransformer.cs --- Common/OpenApi/OpenApiDocumentTransformer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Common/OpenApi/OpenApiDocumentTransformer.cs b/Common/OpenApi/OpenApiDocumentTransformer.cs index fb715a1d..3e85d35a 100644 --- a/Common/OpenApi/OpenApiDocumentTransformer.cs +++ b/Common/OpenApi/OpenApiDocumentTransformer.cs @@ -9,19 +9,19 @@ public sealed class OpenApiDocumentTransformer : IOpenApiDocumentTransformer { public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) { - document.Info = new() + document.Info = new OpenApiInfo { Title = "OpenShock API", Description = "Test description of API", - Version = "V" + context.DocumentName, + Version = "v" + context.DocumentName, TermsOfService = new Uri("https://github.com/OpenShock/"), - Contact = new() + Contact = new OpenApiContact { Name = "Support", Url = new Uri("mailto:support@openshock.app"), Email = "support@openshock.app" }, - License = new() + License = new OpenApiLicense { Name = "GNU affero General Public License v3.0", Url = new Uri("https://github.com/OpenShock/API/blob/develop/LICENSE") From 42ec961d0fb799c6479d919d38969b335fb16c3b Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 29 Apr 2025 22:56:37 +0200 Subject: [PATCH 12/19] Yup --- API/Controller/Account/Authenticated/ChangeEmail.cs | 2 +- API/Controller/Account/Login.cs | 4 ++-- API/Controller/Account/LoginV2.cs | 4 ++-- API/Controller/Account/PasswordResetCheckValid.cs | 4 ++-- API/Controller/Account/PasswordResetComplete.cs | 4 ++-- API/Controller/Account/PasswordResetInitiate.cs | 6 +++--- API/Controller/Account/Signup.cs | 4 ++-- API/Controller/Account/SignupV2.cs | 4 ++-- API/Controller/Admin/GetOnlineDevices.cs | 2 +- API/Controller/Device/AssignLCG.cs | 2 +- API/Controller/Device/GetSelf.cs | 2 +- API/Controller/Device/Pair.cs | 2 +- API/Controller/Devices/DeviceOtaController.cs | 2 +- API/Controller/Devices/DevicesController.cs | 8 ++++---- API/Controller/Devices/ShockersController.cs | 2 +- API/Controller/Public/GetStats.cs | 4 ++-- API/Controller/Public/PublicShareController.cs | 4 ++-- API/Controller/Shares/DeleteShareCode.cs | 4 ++-- API/Controller/Shares/LinkShareCode.cs | 4 ++-- API/Controller/Shares/Links/AddShocker.cs | 4 ++-- API/Controller/Shares/Links/CreateShareLink.cs | 4 ++-- API/Controller/Shares/Links/DeleteShareLink.cs | 4 ++-- .../Shares/Links/DeleteShockerShareLink.cs | 4 ++-- API/Controller/Shares/Links/EditShockerShareLink.cs | 4 ++-- API/Controller/Shares/Links/ListShareLinks.cs | 4 ++-- API/Controller/Shares/Links/PauseShockerShareLink.cs | 2 +- API/Controller/Shockers/ControlLogController.cs | 2 +- API/Controller/Shockers/ControlShockerController.cs | 6 +++--- API/Controller/Shockers/CreateShockerController.cs | 2 +- API/Controller/Shockers/DeleteShockerController.cs | 4 ++-- API/Controller/Shockers/GetShockerController.cs | 2 +- API/Controller/Shockers/OwnShockerController.cs | 4 ++-- API/Controller/Shockers/PatchShockerController.cs | 4 ++-- API/Controller/Shockers/PauseShockerController.cs | 2 +- API/Controller/Shockers/ShareShockerController.cs | 8 ++++---- API/Controller/Shockers/SharedShockersController.cs | 4 ++-- API/Controller/Users/GetSelf.cs | 10 ++++------ API/Controller/Version/_ApiController.cs | 4 ++-- ...egacySuccessResponse.cs => LegacyDataResponse.cs} | 4 ++-- .../{BaseResponse.cs => LegacyEmptyResponse.cs} | 7 +++---- Common/OpenShockControllerBase.cs | 12 +----------- 41 files changed, 78 insertions(+), 91 deletions(-) rename Common/Models/{LegacySuccessResponse.cs => LegacyDataResponse.cs} (75%) rename Common/Models/{BaseResponse.cs => LegacyEmptyResponse.cs} (64%) diff --git a/API/Controller/Account/Authenticated/ChangeEmail.cs b/API/Controller/Account/Authenticated/ChangeEmail.cs index 7932bc7c..1d1b6a2d 100644 --- a/API/Controller/Account/Authenticated/ChangeEmail.cs +++ b/API/Controller/Account/Authenticated/ChangeEmail.cs @@ -14,7 +14,7 @@ public sealed partial class AuthenticatedAccountController /// /// [HttpPost("email")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public Task ChangeEmail(ChangeEmailRequest data) { throw new NotImplementedException(); diff --git a/API/Controller/Account/Login.cs b/API/Controller/Account/Login.cs index 52d185ef..096fc1f2 100644 --- a/API/Controller/Account/Login.cs +++ b/API/Controller/Account/Login.cs @@ -20,7 +20,7 @@ public sealed partial class AccountController /// User successfully logged in /// Invalid username or password [HttpPost("login")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status401Unauthorized, MediaTypeNames.Application.ProblemJson)] // InvalidCredentials [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidDomain [MapToApiVersion("1")] @@ -42,6 +42,6 @@ public async Task Login( HttpContext.SetSessionKeyCookie(loginAction.AsT0.Value, "." + cookieDomainToUse); - return RespondSuccessLegacySimple("Successfully logged in"); + return Ok(new LegacyEmptyResponse("Successfully logged in")); } } \ No newline at end of file diff --git a/API/Controller/Account/LoginV2.cs b/API/Controller/Account/LoginV2.cs index 2efc4850..e9d0f8d7 100644 --- a/API/Controller/Account/LoginV2.cs +++ b/API/Controller/Account/LoginV2.cs @@ -22,7 +22,7 @@ public sealed partial class AccountController /// User successfully logged in /// Invalid username or password [HttpPost("login")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status401Unauthorized, MediaTypeNames.Application.ProblemJson)] // InvalidCredentials [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidDomain [MapToApiVersion("2")] @@ -57,6 +57,6 @@ public async Task LoginV2( HttpContext.SetSessionKeyCookie(loginAction.AsT0.Value, "." + cookieDomainToUse); - return RespondSuccessLegacySimple("Successfully logged in"); + return Ok(new LegacyEmptyResponse("Successfully logged in")); } } \ No newline at end of file diff --git a/API/Controller/Account/PasswordResetCheckValid.cs b/API/Controller/Account/PasswordResetCheckValid.cs index 5b4f99f9..aa91783c 100644 --- a/API/Controller/Account/PasswordResetCheckValid.cs +++ b/API/Controller/Account/PasswordResetCheckValid.cs @@ -18,14 +18,14 @@ public sealed partial class AccountController /// Valid password reset process /// Password reset process not found [HttpHead("recover/{passwordResetId}/{secret}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PasswordResetNotFound [MapToApiVersion("1")] public async Task PasswordResetCheckValid([FromRoute] Guid passwordResetId, [FromRoute] string secret, CancellationToken cancellationToken) { var passwordResetExists = await _accountService.PasswordResetExists(passwordResetId, secret, cancellationToken); return passwordResetExists.Match( - success => RespondSuccessLegacySimple("Valid password reset process"), + success => Ok(new LegacyEmptyResponse("Valid password reset process")), notFound => Problem(PasswordResetError.PasswordResetNotFound), invalid => Problem(PasswordResetError.PasswordResetNotFound) ); diff --git a/API/Controller/Account/PasswordResetComplete.cs b/API/Controller/Account/PasswordResetComplete.cs index 574f68ed..45c5ae62 100644 --- a/API/Controller/Account/PasswordResetComplete.cs +++ b/API/Controller/Account/PasswordResetComplete.cs @@ -18,7 +18,7 @@ public sealed partial class AccountController /// Password successfully changed /// Password reset process not found [HttpPost("recover/{passwordResetId}/{secret}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PasswordResetNotFound [MapToApiVersion("1")] public async Task PasswordResetComplete([FromRoute] Guid passwordResetId, @@ -27,7 +27,7 @@ public async Task PasswordResetComplete([FromRoute] Guid password var passwordResetComplete = await _accountService.PasswordResetComplete(passwordResetId, secret, body.Password); return passwordResetComplete.Match( - success => RespondSuccessLegacySimple("Password successfully changed"), + success => Ok(new LegacyEmptyResponse("Password successfully changed")), notFound => Problem(PasswordResetError.PasswordResetNotFound), invalid => Problem(PasswordResetError.PasswordResetNotFound)); } diff --git a/API/Controller/Account/PasswordResetInitiate.cs b/API/Controller/Account/PasswordResetInitiate.cs index 2165aa54..30d4bab8 100644 --- a/API/Controller/Account/PasswordResetInitiate.cs +++ b/API/Controller/Account/PasswordResetInitiate.cs @@ -14,15 +14,15 @@ public sealed partial class AccountController /// /// Password reset email sent if the email is associated to an registered account [HttpPost("reset")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [MapToApiVersion("1")] - public async Task> PasswordResetInitiate([FromBody] ResetRequest body) + public async Task PasswordResetInitiate([FromBody] ResetRequest body) { await _accountService.CreatePasswordReset(body.Email); return SendResponse(); } - private static BaseResponse SendResponse() => new("Password reset has been sent via email if the email is associated to an registered account"); + private static LegacyEmptyResponse SendResponse() => new("Password reset has been sent via email if the email is associated to an registered account"); public sealed class ResetRequest { diff --git a/API/Controller/Account/Signup.cs b/API/Controller/Account/Signup.cs index 080bce28..4c76d936 100644 --- a/API/Controller/Account/Signup.cs +++ b/API/Controller/Account/Signup.cs @@ -19,7 +19,7 @@ public sealed partial class AccountController /// User successfully signed up /// Username or email already exists [HttpPost("signup")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status409Conflict, MediaTypeNames.Application.ProblemJson)] // EmailOrUsernameAlreadyExists [MapToApiVersion("1")] public async Task SignUp([FromBody] SignUp body) @@ -28,6 +28,6 @@ public async Task SignUp([FromBody] SignUp body) if (creationAction.IsT1) return Problem(SignupError.EmailAlreadyExists); - return RespondSuccessLegacySimple("Successfully signed up"); + return Ok(new LegacyEmptyResponse("Successfully signed up")); } } \ No newline at end of file diff --git a/API/Controller/Account/SignupV2.cs b/API/Controller/Account/SignupV2.cs index 981fba11..be80f6f1 100644 --- a/API/Controller/Account/SignupV2.cs +++ b/API/Controller/Account/SignupV2.cs @@ -23,7 +23,7 @@ public sealed partial class AccountController /// User successfully signed up /// Username or email already exists [HttpPost("signup")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status409Conflict, MediaTypeNames.Application.ProblemJson)] // EmailOrUsernameAlreadyExists [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // InvalidTurnstileResponse [MapToApiVersion("2")] @@ -44,7 +44,7 @@ public async Task SignUpV2( var creationAction = await _accountService.Signup(body.Email, body.Username, body.Password); return creationAction.Match( - _ => RespondSuccessLegacySimple("Successfully signed up"), + _ => Ok(new LegacyEmptyResponse("Successfully signed up")), _ => Problem(SignupError.EmailAlreadyExists) ); } diff --git a/API/Controller/Admin/GetOnlineDevices.cs b/API/Controller/Admin/GetOnlineDevices.cs index 9806f7f2..548ca05b 100644 --- a/API/Controller/Admin/GetOnlineDevices.cs +++ b/API/Controller/Admin/GetOnlineDevices.cs @@ -20,7 +20,7 @@ public sealed partial class AdminController /// All online devices /// Unauthorized [HttpGet("monitoring/onlineDevices")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task GetOnlineDevices() { var devicesOnline = _redis.RedisCollection(false); diff --git a/API/Controller/Device/AssignLCG.cs b/API/Controller/Device/AssignLCG.cs index 6c4bcb33..fcad6130 100644 --- a/API/Controller/Device/AssignLCG.cs +++ b/API/Controller/Device/AssignLCG.cs @@ -19,7 +19,7 @@ public sealed partial class DeviceController /// Successfully assigned LCG node /// Unable to find suitable LCG node [HttpGet("assignLCG")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable, MediaTypeNames.Application.ProblemJson)] // NoLcgNodesAvailable public async Task GetLiveControlGateway([FromServices] ILCGNodeProvisioner geoLocation, [FromServices] IWebHostEnvironment env) diff --git a/API/Controller/Device/GetSelf.cs b/API/Controller/Device/GetSelf.cs index 32792b1d..02f8f156 100644 --- a/API/Controller/Device/GetSelf.cs +++ b/API/Controller/Device/GetSelf.cs @@ -14,7 +14,7 @@ public sealed partial class DeviceController /// /// The device information was successfully retrieved. [HttpGet("self")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] public async Task GetSelf() { var shockers = await _db.Shockers.Where(x => x.Device == CurrentDevice.Id).Select(x => new MinimalShocker diff --git a/API/Controller/Device/Pair.cs b/API/Controller/Device/Pair.cs index e01bb242..c1393c55 100644 --- a/API/Controller/Device/Pair.cs +++ b/API/Controller/Device/Pair.cs @@ -22,7 +22,7 @@ public sealed partial class DeviceController [AllowAnonymous] [HttpGet("pair/{pairCode}", Name = "Pair")] [HttpGet("~/{version:apiVersion}/pair/{pairCode}", Name = "Pair_DEPRECATED")] // Backwards compatibility - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // PairCodeNotFound public async Task Pair([FromRoute] string pairCode) { diff --git a/API/Controller/Devices/DeviceOtaController.cs b/API/Controller/Devices/DeviceOtaController.cs index 10b4466d..99952e15 100644 --- a/API/Controller/Devices/DeviceOtaController.cs +++ b/API/Controller/Devices/DeviceOtaController.cs @@ -40,7 +40,7 @@ public DevicesOtaController(OpenShockContext db, ILogger logg /// Could not find device or you do not have access to it [HttpGet("{deviceId}/ota")] [MapToApiVersion("1")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [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 af35e681..e0a41529 100644 --- a/API/Controller/Devices/DevicesController.cs +++ b/API/Controller/Devices/DevicesController.cs @@ -22,7 +22,7 @@ public sealed partial class DevicesController /// /// All devices for the current user [HttpGet] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [MapToApiVersion("1")] public IActionResult ListDevices() { @@ -45,7 +45,7 @@ public IActionResult ListDevices() /// /// The device [HttpGet("{deviceId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [MapToApiVersion("1")] public async Task GetDeviceById([FromRoute] Guid deviceId) @@ -190,7 +190,7 @@ public async Task CreateDeviceV2([FromBody] HubCreateRequest data, [FromSe /// Device does not exist or does not belong to you [HttpGet("{deviceId}/pair")] [TokenPermission(PermissionType.Devices_Edit)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [MapToApiVersion("1")] public async Task GetPairCode([FromRoute] Guid deviceId) @@ -225,7 +225,7 @@ public async Task GetPairCode([FromRoute] Guid deviceId) /// Device is online but not connected to a LCG node, you might need to upgrade your firmware to use this feature /// Internal server error, lcg node could not be found [HttpGet("{deviceId}/lcg")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound, DeviceIsNotOnline [ProducesResponseType(StatusCodes.Status412PreconditionFailed, MediaTypeNames.Application.ProblemJson)] // DeviceNotConnectedToGateway [MapToApiVersion("1")] diff --git a/API/Controller/Devices/ShockersController.cs b/API/Controller/Devices/ShockersController.cs index 1f0f6b06..b1ea1351 100644 --- a/API/Controller/Devices/ShockersController.cs +++ b/API/Controller/Devices/ShockersController.cs @@ -19,7 +19,7 @@ public sealed partial class DevicesController /// All shockers for the device /// Device does not exists or you do not have access to it. [HttpGet("{deviceId}/shockers")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [MapToApiVersion("1")] public async Task GetShockers([FromRoute] Guid deviceId) diff --git a/API/Controller/Public/GetStats.cs b/API/Controller/Public/GetStats.cs index 6f590786..e8408c21 100644 --- a/API/Controller/Public/GetStats.cs +++ b/API/Controller/Public/GetStats.cs @@ -17,12 +17,12 @@ public sealed partial class PublicController /// /// The statistics were successfully retrieved. [HttpGet("stats")] - public async Task> GetOnlineDevicesStatistics([FromServices] IConnectionMultiplexer redisConnectionMultiplexer) + public async Task> GetOnlineDevicesStatistics([FromServices] IConnectionMultiplexer redisConnectionMultiplexer) { var ft = redisConnectionMultiplexer.GetDatabase().FT(); var deviceOnlineInfo = await ft.InfoAsync(DeviceOnline.IndexName); - return new LegacySuccessResponse(new StatsResponse + return new LegacyDataResponse(new StatsResponse { DevicesOnline = deviceOnlineInfo.NumDocs }); diff --git a/API/Controller/Public/PublicShareController.cs b/API/Controller/Public/PublicShareController.cs index cff6eddb..6221a224 100644 --- a/API/Controller/Public/PublicShareController.cs +++ b/API/Controller/Public/PublicShareController.cs @@ -21,8 +21,8 @@ public sealed partial class PublicController /// The share link information was successfully retrieved. /// The share link does not exist. [HttpGet("shares/links/{shareLinkId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - [ProducesResponseType>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound public async Task GetShareLink([FromRoute] Guid shareLinkId) { var shareLink = await _db.ShockerSharesLinks.Where(x => x.Id == shareLinkId).Select(x => new diff --git a/API/Controller/Shares/DeleteShareCode.cs b/API/Controller/Shares/DeleteShareCode.cs index f884aede..0d3b91d8 100644 --- a/API/Controller/Shares/DeleteShareCode.cs +++ b/API/Controller/Shares/DeleteShareCode.cs @@ -18,7 +18,7 @@ public sealed partial class SharesController /// Deleted share code /// Share code not found or does not belong to you [HttpDelete("code/{shareCodeId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareCodeNotFound [MapToApiVersion("1")] public async Task DeleteShareCode([FromRoute] Guid shareCodeId) @@ -32,6 +32,6 @@ public async Task DeleteShareCode([FromRoute] Guid shareCodeId) return Problem(ShareCodeError.ShareCodeNotFound); } - return RespondSuccessLegacySimple("Successfully deleted share code"); + return Ok(new LegacyEmptyResponse("Successfully deleted share code")); } } \ No newline at end of file diff --git a/API/Controller/Shares/LinkShareCode.cs b/API/Controller/Shares/LinkShareCode.cs index 796450b0..841f920c 100644 --- a/API/Controller/Shares/LinkShareCode.cs +++ b/API/Controller/Shares/LinkShareCode.cs @@ -23,7 +23,7 @@ public sealed partial class SharesController /// You cannot link your own shocker code / You already have this shocker linked to your account /// Error while linking share code to your account [HttpPost("code/{shareCodeId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareCodeNotFound [ProducesResponseType(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] // CantLinkOwnShareCode, ShockerAlreadyLinked [MapToApiVersion("1")] @@ -58,6 +58,6 @@ [FromServices] IDeviceUpdateService deviceUpdateService await deviceUpdateService.UpdateDevice(shareCode.Owner, shareCode.Device, DeviceUpdateType.ShockerUpdated, CurrentUser.Id); - return RespondSuccessLegacySimple("Successfully linked share code"); + return Ok(new LegacyEmptyResponse("Successfully linked share code")); } } \ No newline at end of file diff --git a/API/Controller/Shares/Links/AddShocker.cs b/API/Controller/Shares/Links/AddShocker.cs index aa6cc972..282a69a4 100644 --- a/API/Controller/Shares/Links/AddShocker.cs +++ b/API/Controller/Shares/Links/AddShocker.cs @@ -20,7 +20,7 @@ public sealed partial class ShareLinksController /// Share link or shocker does not exist /// Shocker already exists in share link [HttpPost("{shareLinkId}/{shockerId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound, ShockerNotFound [ProducesResponseType(StatusCodes.Status409Conflict, MediaTypeNames.Application.ProblemJson)] // ShockerAlreadyInShareLink public async Task AddShocker([FromRoute] Guid shareLinkId, [FromRoute] Guid shockerId) @@ -46,6 +46,6 @@ public async Task AddShocker([FromRoute] Guid shareLinkId, [FromR await _db.SaveChangesAsync(); - return RespondSuccessLegacySimple("Successfully added shocker"); + return Ok(new LegacyEmptyResponse("Successfully added shocker")); } } \ No newline at end of file diff --git a/API/Controller/Shares/Links/CreateShareLink.cs b/API/Controller/Shares/Links/CreateShareLink.cs index 0f3db334..42e3dd6f 100644 --- a/API/Controller/Shares/Links/CreateShareLink.cs +++ b/API/Controller/Shares/Links/CreateShareLink.cs @@ -14,7 +14,7 @@ public sealed partial class ShareLinksController /// /// The created share link [HttpPost(Name = "CreateShareLink")] - public async Task> CreateShareLink([FromBody] ShareLinkCreate body) + public async Task> CreateShareLink([FromBody] ShareLinkCreate body) { var entity = new ShockerSharesLink { @@ -26,6 +26,6 @@ public async Task> CreateShareLink([FromBody] ShareL _db.ShockerSharesLinks.Add(entity); await _db.SaveChangesAsync(); - return new LegacySuccessResponse(entity.Id); + return new LegacyDataResponse(entity.Id); } } \ No newline at end of file diff --git a/API/Controller/Shares/Links/DeleteShareLink.cs b/API/Controller/Shares/Links/DeleteShareLink.cs index 11caf5f1..b707abd1 100644 --- a/API/Controller/Shares/Links/DeleteShareLink.cs +++ b/API/Controller/Shares/Links/DeleteShareLink.cs @@ -17,7 +17,7 @@ public sealed partial class ShareLinksController /// Deleted share link /// Share link not found or does not belong to you [HttpDelete("{shareLinkId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound public async Task DeleteShareLink([FromRoute] Guid shareLinkId) { @@ -27,7 +27,7 @@ public async Task DeleteShareLink([FromRoute] Guid shareLinkId) .ExecuteDeleteAsync(); return result > 0 - ? RespondSuccessLegacySimple("Deleted share link") + ? Ok(new LegacyEmptyResponse("Deleted share link")) : Problem(ShareLinkError.ShareLinkNotFound); } } \ No newline at end of file diff --git a/API/Controller/Shares/Links/DeleteShockerShareLink.cs b/API/Controller/Shares/Links/DeleteShockerShareLink.cs index 46382bf8..007a4da5 100644 --- a/API/Controller/Shares/Links/DeleteShockerShareLink.cs +++ b/API/Controller/Shares/Links/DeleteShockerShareLink.cs @@ -19,7 +19,7 @@ public sealed partial class ShareLinksController /// Share link or shocker does not exist /// Shocker does not exist in share link [HttpDelete("{shareLinkId}/{shockerId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound, ShockerNotInShareLink public async Task RemoveShocker([FromRoute] Guid shareLinkId, [FromRoute] Guid shockerId) { @@ -40,6 +40,6 @@ public async Task RemoveShocker([FromRoute] Guid shareLinkId, [Fr return Problem(ShareLinkError.ShockerNotInShareLink); } - return RespondSuccessLegacySimple($"Successfully removed {affected} {(affected == 1 ? "shocker" : "shockers")}"); + return Ok(new LegacyEmptyResponse($"Successfully removed {affected} {(affected == 1 ? "shocker" : "shockers")}")); } } \ No newline at end of file diff --git a/API/Controller/Shares/Links/EditShockerShareLink.cs b/API/Controller/Shares/Links/EditShockerShareLink.cs index b5480a59..0723ec28 100644 --- a/API/Controller/Shares/Links/EditShockerShareLink.cs +++ b/API/Controller/Shares/Links/EditShockerShareLink.cs @@ -22,7 +22,7 @@ public sealed partial class ShareLinksController /// Share link or shocker does not exist /// Shocker does not exist in share link [HttpPatch("{shareLinkId}/{shockerId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound, ShockerNotInShareLink public async Task EditShocker([FromRoute] Guid shareLinkId, [FromRoute] Guid shockerId, [FromBody] ShareLinkEditShocker body) { @@ -43,6 +43,6 @@ await _db.ShockerSharesLinksShockers.FirstOrDefaultAsync(x => shocker.Cooldown = body.Cooldown; await _db.SaveChangesAsync(); - return RespondSuccessLegacySimple("Successfully updated shocker"); + return Ok(new LegacyEmptyResponse("Successfully updated shocker")); } } \ No newline at end of file diff --git a/API/Controller/Shares/Links/ListShareLinks.cs b/API/Controller/Shares/Links/ListShareLinks.cs index b7885630..1ce555ce 100644 --- a/API/Controller/Shares/Links/ListShareLinks.cs +++ b/API/Controller/Shares/Links/ListShareLinks.cs @@ -14,13 +14,13 @@ public sealed partial class ShareLinksController /// /// All share links for the current user [HttpGet] - public LegacySuccessResponse> List() + public LegacyDataResponse> List() { var ownShareLinks = _db.ShockerSharesLinks .Where(x => x.OwnerId == CurrentUser.Id) .Select(x => ShareLinkResponse.GetFromEf(x)) .AsAsyncEnumerable(); - return new LegacySuccessResponse>(ownShareLinks); + return new LegacyDataResponse>(ownShareLinks); } } \ No newline at end of file diff --git a/API/Controller/Shares/Links/PauseShockerShareLink.cs b/API/Controller/Shares/Links/PauseShockerShareLink.cs index ca50b4a9..ab5018bf 100644 --- a/API/Controller/Shares/Links/PauseShockerShareLink.cs +++ b/API/Controller/Shares/Links/PauseShockerShareLink.cs @@ -22,7 +22,7 @@ public sealed partial class ShareLinksController /// Share link or shocker does not exist /// Shocker does not exist in share link [HttpPost("{shareLinkId}/{shockerId}/pause")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound, ShockerNotInShareLink public async Task PauseShocker([FromRoute] Guid shareLinkId, [FromRoute] Guid shockerId, [FromBody] PauseRequest body) { diff --git a/API/Controller/Shockers/ControlLogController.cs b/API/Controller/Shockers/ControlLogController.cs index d1878ff6..144a0a62 100644 --- a/API/Controller/Shockers/ControlLogController.cs +++ b/API/Controller/Shockers/ControlLogController.cs @@ -24,7 +24,7 @@ public sealed partial class ShockerController /// The logs /// Shocker does not exist [HttpGet("{shockerId}/logs")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task GetShockerLogs([FromRoute] Guid shockerId, [FromQuery] uint offset = 0, diff --git a/API/Controller/Shockers/ControlShockerController.cs b/API/Controller/Shockers/ControlShockerController.cs index 0fc70285..6b7000e7 100644 --- a/API/Controller/Shockers/ControlShockerController.cs +++ b/API/Controller/Shockers/ControlShockerController.cs @@ -24,7 +24,7 @@ public sealed partial class ShockerController [MapToApiVersion("2")] [HttpPost("control")] [TokenPermission(PermissionType.Shockers_Use)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // Shocker not found [ProducesResponseType(StatusCodes.Status412PreconditionFailed, MediaTypeNames.Application.ProblemJson)] // Shocker is paused [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // You don't have permission to control this shocker @@ -45,7 +45,7 @@ public async Task SendControl( var controlAction = await ControlLogic.ControlByUser(body.Shocks, _db, sender, userHub.Clients, redisPubService); return controlAction.Match( - success => RespondSuccessLegacySimple("Successfully sent control messages"), + success => Ok(new LegacyEmptyResponse("Successfully sent control messages")), notFound => Problem(ShockerControlError.ShockerControlNotFound(notFound.Value)), paused => Problem(ShockerControlError.ShockerControlPaused(paused.Value)), noPermission => Problem(ShockerControlError.ShockerControlNoPermission(noPermission.Value))); @@ -58,7 +58,7 @@ public async Task SendControl( [MapToApiVersion("1")] [HttpPost("control")] [TokenPermission(PermissionType.Shockers_Use)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // Shocker not found [ProducesResponseType(StatusCodes.Status412PreconditionFailed, MediaTypeNames.Application.ProblemJson)] // Shocker is paused [ProducesResponseType(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // You don't have permission to control this shocker diff --git a/API/Controller/Shockers/CreateShockerController.cs b/API/Controller/Shockers/CreateShockerController.cs index fbfbcaf1..10286e90 100644 --- a/API/Controller/Shockers/CreateShockerController.cs +++ b/API/Controller/Shockers/CreateShockerController.cs @@ -22,7 +22,7 @@ public sealed partial class ShockerController /// You can have a maximum of 11 Shockers per Device. /// Device does not exist [HttpPost] - [ProducesResponseType>(StatusCodes.Status201Created, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status201Created, MediaTypeNames.Application.Json)] [TokenPermission(PermissionType.Shockers_Edit)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound [ProducesResponseType(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] // TooManyShockers diff --git a/API/Controller/Shockers/DeleteShockerController.cs b/API/Controller/Shockers/DeleteShockerController.cs index 8f4c4e6e..5911c031 100644 --- a/API/Controller/Shockers/DeleteShockerController.cs +++ b/API/Controller/Shockers/DeleteShockerController.cs @@ -22,7 +22,7 @@ public sealed partial class ShockerController /// Shocker does not exist [HttpDelete("{shockerId}")] [TokenPermission(PermissionType.Shockers_Edit)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task RemoveShocker( @@ -44,6 +44,6 @@ public async Task RemoveShocker( await deviceUpdateService.UpdateDeviceForAllShared(CurrentUser.Id, affected.Device, DeviceUpdateType.ShockerUpdated); - return RespondSuccessLegacySimple("Shocker removed successfully"); + return Ok(new LegacyEmptyResponse("Shocker removed successfully")); } } \ No newline at end of file diff --git a/API/Controller/Shockers/GetShockerController.cs b/API/Controller/Shockers/GetShockerController.cs index cfe892e7..177ea874 100644 --- a/API/Controller/Shockers/GetShockerController.cs +++ b/API/Controller/Shockers/GetShockerController.cs @@ -19,7 +19,7 @@ public sealed partial class ShockerController /// The shocker information was successfully retrieved. /// The shocker does not exist or you do not have access to it. [HttpGet("{shockerId}")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task GetShockerById([FromRoute] Guid shockerId) diff --git a/API/Controller/Shockers/OwnShockerController.cs b/API/Controller/Shockers/OwnShockerController.cs index b8ba725a..e9288505 100644 --- a/API/Controller/Shockers/OwnShockerController.cs +++ b/API/Controller/Shockers/OwnShockerController.cs @@ -16,7 +16,7 @@ public sealed partial class ShockerController /// The shockers were successfully retrieved. [HttpGet("own")] [MapToApiVersion("1")] - public LegacySuccessResponse> ListShockers() + public LegacyDataResponse> ListShockers() { var shockers = _db.Devices .Where(x => x.Owner == CurrentUser.Id) @@ -40,6 +40,6 @@ public LegacySuccessResponse> ListS }) .AsAsyncEnumerable(); - return new LegacySuccessResponse>(shockers); + return new LegacyDataResponse>(shockers); } } \ No newline at end of file diff --git a/API/Controller/Shockers/PatchShockerController.cs b/API/Controller/Shockers/PatchShockerController.cs index cb0e6ea3..a737a91b 100644 --- a/API/Controller/Shockers/PatchShockerController.cs +++ b/API/Controller/Shockers/PatchShockerController.cs @@ -24,7 +24,7 @@ public sealed partial class ShockerController /// Shocker does not exist [HttpPatch("{shockerId}")] [TokenPermission(PermissionType.Shockers_Edit)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound, ShockerNotFound [MapToApiVersion("1")] public async Task EditShocker( @@ -52,6 +52,6 @@ public async Task EditShocker( await deviceUpdateService.UpdateDeviceForAllShared(CurrentUser.Id, body.Device, DeviceUpdateType.ShockerUpdated); - return RespondSuccessLegacySimple("Shocker updated successfully"); + return Ok(new LegacyEmptyResponse("Shocker updated successfully")); } } \ No newline at end of file diff --git a/API/Controller/Shockers/PauseShockerController.cs b/API/Controller/Shockers/PauseShockerController.cs index 3bbb2005..4d81386f 100644 --- a/API/Controller/Shockers/PauseShockerController.cs +++ b/API/Controller/Shockers/PauseShockerController.cs @@ -24,7 +24,7 @@ public sealed partial class ShockerController /// Shocker not found or does not belong to you [HttpPost("{shockerId}/pause")] [TokenPermission(PermissionType.Shockers_Pause)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task PauseShocker([FromRoute] Guid shockerId, [FromBody] PauseRequest body, diff --git a/API/Controller/Shockers/ShareShockerController.cs b/API/Controller/Shockers/ShareShockerController.cs index ebdf5a44..2b4971de 100644 --- a/API/Controller/Shockers/ShareShockerController.cs +++ b/API/Controller/Shockers/ShareShockerController.cs @@ -25,7 +25,7 @@ public sealed partial class ShockerController /// OK /// The shocker does not exist or you do not have access to it. [HttpGet("{shockerId}/shares")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task GetShockerShares([FromRoute] Guid shockerId) @@ -71,7 +71,7 @@ public async Task GetShockerShares([FromRoute] Guid shockerId) /// /// OK [HttpGet("{shockerId}/shareCodes")] - [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task ShockerShareCodeList([FromRoute] Guid shockerId) @@ -108,7 +108,7 @@ public sealed class ShareCodeInfo /// [HttpPost("{shockerId}/shares")] [TokenPermission(PermissionType.Shockers_Edit)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task ShockerShareCodeCreate( @@ -225,7 +225,7 @@ public async Task ShockerShareCodeUpdate( /// The share code does not exist or you do not have access to it. [HttpPost("{shockerId}/shares/{sharedWithUserId}/pause")] [TokenPermission(PermissionType.Shockers_Pause)] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] + [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound [MapToApiVersion("1")] public async Task ShockerShareCodePause( diff --git a/API/Controller/Shockers/SharedShockersController.cs b/API/Controller/Shockers/SharedShockersController.cs index c343124f..b3fd2451 100644 --- a/API/Controller/Shockers/SharedShockersController.cs +++ b/API/Controller/Shockers/SharedShockersController.cs @@ -26,7 +26,7 @@ public sealed partial class ShockerController /// The shockers were successfully retrieved. [HttpGet("shared")] [MapToApiVersion("1")] - public async Task>> ListSharedShockers() + public async Task>> ListSharedShockers() { var sharedShockersData = await _db.ShockerShares .AsNoTracking() @@ -80,6 +80,6 @@ public async Task>> List .ToArray() }); - return new LegacySuccessResponse>(sharesResponse); + return new LegacyDataResponse>(sharesResponse); } } \ No newline at end of file diff --git a/API/Controller/Users/GetSelf.cs b/API/Controller/Users/GetSelf.cs index f4f8fb5d..44f6e2fc 100644 --- a/API/Controller/Users/GetSelf.cs +++ b/API/Controller/Users/GetSelf.cs @@ -12,12 +12,10 @@ public sealed partial class UsersController /// /// The user's information was successfully retrieved. [HttpGet("self")] - [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - public BaseResponse GetSelf() + public LegacyDataResponse GetSelf() { - return new BaseResponse - { - Data = new SelfResponse + return new LegacyDataResponse( + new SelfResponse { Id = CurrentUser.Id, Name = CurrentUser.Name, @@ -26,7 +24,7 @@ public BaseResponse GetSelf() Roles = CurrentUser.Roles, Rank = CurrentUser.Roles.Count > 0 ? CurrentUser.Roles.Max().ToString() : "User" } - }; + ); } public sealed class SelfResponse diff --git a/API/Controller/Version/_ApiController.cs b/API/Controller/Version/_ApiController.cs index 3d64ac25..de7d20a9 100644 --- a/API/Controller/Version/_ApiController.cs +++ b/API/Controller/Version/_ApiController.cs @@ -25,7 +25,7 @@ public sealed partial class VersionController : OpenShockControllerBase /// /// The version was successfully retrieved. [HttpGet] - public LegacySuccessResponse GetBackendVersion( + public LegacyDataResponse GetBackendVersion( [FromServices] IOptions frontendOptions, [FromServices] IOptions turnstileOptions ) @@ -33,7 +33,7 @@ [FromServices] IOptions turnstileOptions var frontendConfig = frontendOptions.Value; var turnstileConfig = turnstileOptions.Value; - return new LegacySuccessResponse( + return new LegacyDataResponse( new RootResponse { Version = OpenShockBackendVersion, diff --git a/Common/Models/LegacySuccessResponse.cs b/Common/Models/LegacyDataResponse.cs similarity index 75% rename from Common/Models/LegacySuccessResponse.cs rename to Common/Models/LegacyDataResponse.cs index b3c70162..ed9ab5b9 100644 --- a/Common/Models/LegacySuccessResponse.cs +++ b/Common/Models/LegacyDataResponse.cs @@ -4,12 +4,12 @@ namespace OpenShock.Common.Models; -public sealed class LegacySuccessResponse +public sealed class LegacyDataResponse { public string Message { get; set; } public T Data { get; set; } - public LegacySuccessResponse(T data, string message = "") + public LegacyDataResponse(T data, string message = "") { Message = message; Data = data; diff --git a/Common/Models/BaseResponse.cs b/Common/Models/LegacyEmptyResponse.cs similarity index 64% rename from Common/Models/BaseResponse.cs rename to Common/Models/LegacyEmptyResponse.cs index 832b8ac0..ceeb0eea 100644 --- a/Common/Models/BaseResponse.cs +++ b/Common/Models/LegacyEmptyResponse.cs @@ -4,15 +4,14 @@ namespace OpenShock.Common.Models; -public sealed class BaseResponse +public sealed class LegacyEmptyResponse { public string? Message { get; set; } - public T? Data { get; set; } + public object? Data { get; set; } = null; - public BaseResponse(string? message = null, T? data = default) + public LegacyEmptyResponse(string? message = null) { Message = message; - Data = data; } public override string ToString() => JsonSerializer.Serialize(this); diff --git a/Common/OpenShockControllerBase.cs b/Common/OpenShockControllerBase.cs index f5732abe..efe17b2a 100644 --- a/Common/OpenShockControllerBase.cs +++ b/Common/OpenShockControllerBase.cs @@ -16,16 +16,6 @@ public class OpenShockControllerBase : ControllerBase public ObjectResult RespondSuccessLegacy(T data, HttpStatusCode statusCode = HttpStatusCode.OK) { Response.StatusCode = (int)statusCode; - return new ObjectResult(new LegacySuccessResponse(data)); - } - - [NonAction] - public ObjectResult RespondSuccessLegacySimple(string message = "", HttpStatusCode statusCode = HttpStatusCode.OK) - { - Response.StatusCode = (int)statusCode; - return new ObjectResult(new BaseResponse - { - Message = message - }); + return new ObjectResult(new LegacyDataResponse(data)); } } \ No newline at end of file From 8aadd48546ee84cfcd4750bbc665cce392844c29 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 29 Apr 2025 23:06:03 +0200 Subject: [PATCH 13/19] weee --- API/Controller/Public/PublicShareController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API/Controller/Public/PublicShareController.cs b/API/Controller/Public/PublicShareController.cs index 6221a224..21a174c3 100644 --- a/API/Controller/Public/PublicShareController.cs +++ b/API/Controller/Public/PublicShareController.cs @@ -22,7 +22,7 @@ public sealed partial class PublicController /// The share link does not exist. [HttpGet("shares/links/{shareLinkId}")] [ProducesResponseType>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] - [ProducesResponseType>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound + [ProducesResponseType(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareLinkNotFound public async Task GetShareLink([FromRoute] Guid shareLinkId) { var shareLink = await _db.ShockerSharesLinks.Where(x => x.Id == shareLinkId).Select(x => new @@ -62,7 +62,7 @@ public async Task GetShareLink([FromRoute] Guid shareLinkId) }) }).FirstOrDefaultAsync(); - if (shareLink == null) return RespondSuccessLegacy(ShareLinkError.ShareLinkNotFound); + if (shareLink == null) return Problem(ShareLinkError.ShareLinkNotFound); var final = new PublicShareLinkResponse From 868d77726b873733edbf954784cbe6c89b557643 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 29 Apr 2025 23:06:09 +0200 Subject: [PATCH 14/19] tidy --- API/Controller/Account/_ApiController.cs | 3 +-- API/Controller/Devices/_ApiController.cs | 3 +-- API/Controller/Shares/_ApiController.cs | 3 +-- API/Controller/Shockers/_ApiController.cs | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/API/Controller/Account/_ApiController.cs b/API/Controller/Account/_ApiController.cs index 3b7f4a99..e1803eac 100644 --- a/API/Controller/Account/_ApiController.cs +++ b/API/Controller/Account/_ApiController.cs @@ -12,8 +12,7 @@ namespace OpenShock.API.Controller.Account; /// User account management /// [ApiController] -[ApiVersion("1")] -[ApiVersion("2")] +[ApiVersion("1"), ApiVersion("2")] [Route("/{version:apiVersion}/account")] public sealed partial class AccountController : OpenShockControllerBase { diff --git a/API/Controller/Devices/_ApiController.cs b/API/Controller/Devices/_ApiController.cs index 75137a4a..cbb14bfb 100644 --- a/API/Controller/Devices/_ApiController.cs +++ b/API/Controller/Devices/_ApiController.cs @@ -12,8 +12,7 @@ namespace OpenShock.API.Controller.Devices; /// Device management /// [ApiController] -[ApiVersion("1")] -[ApiVersion("2")] +[ApiVersion("1"), ApiVersion("2")] [Route("/{version:apiVersion}/devices")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class DevicesController : AuthenticatedSessionControllerBase diff --git a/API/Controller/Shares/_ApiController.cs b/API/Controller/Shares/_ApiController.cs index 5b515222..1cce95a8 100644 --- a/API/Controller/Shares/_ApiController.cs +++ b/API/Controller/Shares/_ApiController.cs @@ -11,8 +11,7 @@ namespace OpenShock.API.Controller.Shares; /// Shocker share management /// [ApiController] -[ApiVersion("1")] -[ApiVersion("2")] +[ApiVersion("1"), ApiVersion("2")] [Route("/{version:apiVersion}/shares")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class SharesController : AuthenticatedSessionControllerBase diff --git a/API/Controller/Shockers/_ApiController.cs b/API/Controller/Shockers/_ApiController.cs index c07746a8..b50d3c11 100644 --- a/API/Controller/Shockers/_ApiController.cs +++ b/API/Controller/Shockers/_ApiController.cs @@ -11,8 +11,7 @@ namespace OpenShock.API.Controller.Shockers; /// Shocker management /// [ApiController] -[ApiVersion("1")] -[ApiVersion("2")] +[ApiVersion("1"), ApiVersion("2")] [Route("/{version:apiVersion}/shockers")] [Authorize(AuthenticationSchemes = OpenShockAuthSchemas.UserSessionApiTokenCombo)] public sealed partial class ShockerController : AuthenticatedSessionControllerBase From 9b7cc0e7df7268ab61741b1a04d8ecd08803b495 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 30 Apr 2025 00:43:44 +0200 Subject: [PATCH 15/19] Tiny improvement --- Common/OpenApi/OpenApiOperationTransformer.cs | 10 ++- .../OpenApi/OpenApiSchemaReferenceIdUtil.cs | 66 +++++++++++++++++++ Common/OpenApi/OpenApiSchemaTransformer.cs | 12 ---- Common/OpenShockServiceHelper.cs | 10 +-- 4 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs delete mode 100644 Common/OpenApi/OpenApiSchemaTransformer.cs diff --git a/Common/OpenApi/OpenApiOperationTransformer.cs b/Common/OpenApi/OpenApiOperationTransformer.cs index 03b0f035..05c30bcd 100644 --- a/Common/OpenApi/OpenApiOperationTransformer.cs +++ b/Common/OpenApi/OpenApiOperationTransformer.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.OpenApi; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.OpenApi; using Microsoft.OpenApi.Models; namespace OpenShock.Common.OpenApi; @@ -7,6 +8,13 @@ public sealed class OpenApiOperationTransformer : IOpenApiOperationTransformer { public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) { + if (context.Description.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) + { + throw new NotImplementedException(); + } + + operation.OperationId = actionDescriptor.ControllerName + actionDescriptor.ActionName; + return Task.CompletedTask; } } diff --git a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs b/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs new file mode 100644 index 00000000..59fbfe53 --- /dev/null +++ b/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs @@ -0,0 +1,66 @@ +using System.Text; +using System.Text.Json.Serialization.Metadata; +using OpenShock.Common.Models; + +namespace OpenShock.Common.OpenApi; + +public static class OpenApiSchemaReferenceIdUtil +{ + private static bool IsCollection(Type genericDef) + => genericDef == typeof(IEnumerable<>) + || genericDef == typeof(IAsyncEnumerable<>) + || genericDef == typeof(IReadOnlyList<>) + || genericDef == typeof(IReadOnlyCollection<>) + || genericDef == typeof(IList<>) + || genericDef == typeof(List<>); + + public static string GetFriendlyName(Type type) + { + if (!type.IsGenericType) + { + if (type.IsArray) + { + return "CollectionOf" + type.GetElementType()!.Name; + } + + return type.Name; + } + + StringBuilder sb = new(); + + while (type.IsGenericType) + { + var genericTypeDefinition = type.GetGenericTypeDefinition(); + + if (genericTypeDefinition == typeof(LegacyDataResponse<>) || genericTypeDefinition == typeof(Nullable<>)) + { + type = type.GetGenericArguments()[0]; + continue; + } + + if (IsCollection(genericTypeDefinition)) + { + sb.Append("CollectionOf"); + type = type.GetGenericArguments()[0]; + continue; + } + + if (genericTypeDefinition == typeof(Paginated<>)) + { + sb.Append("Paginated"); + type = type.GetGenericArguments()[0]; + continue; + } + + throw new NotImplementedException(); + } + + sb.Append(type.Name); + + return sb.ToString(); + } + public static string GetFriendlyName(JsonTypeInfo type) + { + return GetFriendlyName(type.Type); + } +} diff --git a/Common/OpenApi/OpenApiSchemaTransformer.cs b/Common/OpenApi/OpenApiSchemaTransformer.cs deleted file mode 100644 index e8d1089d..00000000 --- a/Common/OpenApi/OpenApiSchemaTransformer.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.AspNetCore.OpenApi; -using Microsoft.OpenApi.Models; - -namespace OpenShock.Common.OpenApi; - -public sealed class OpenApiSchemaTransformer : IOpenApiSchemaTransformer -{ - public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } -} diff --git a/Common/OpenShockServiceHelper.cs b/Common/OpenShockServiceHelper.cs index 463fb2b2..56de6e1a 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -121,23 +121,17 @@ public static IServiceCollection AddOpenShockServices(this IServiceCollection se services.AddOpenApi("1", options => { - // Always inline enum schemas - options.CreateSchemaReferenceId = (type) => - type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type); + options.CreateSchemaReferenceId = OpenApiSchemaReferenceIdUtil.GetFriendlyName; options.AddDocumentTransformer(); options.AddOperationTransformer(); - options.AddSchemaTransformer(); }); services.AddOpenApi("2", options => { - // Always inline enum schemas - options.CreateSchemaReferenceId = (type) => - type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type); + options.CreateSchemaReferenceId = OpenApiSchemaReferenceIdUtil.GetFriendlyName; options.AddDocumentTransformer(); options.AddOperationTransformer(); - options.AddSchemaTransformer(); }); var apiVersioningBuilder = services.AddApiVersioning(options => From 68cac8d90896bd00e3af8f150e280fcbdb00f801 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 30 Apr 2025 01:11:34 +0200 Subject: [PATCH 16/19] more improvement --- .../OpenApi/OpenApiSchemaReferenceIdUtil.cs | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs b/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs index 59fbfe53..270c0cc7 100644 --- a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs +++ b/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs @@ -14,18 +14,16 @@ private static bool IsCollection(Type genericDef) || genericDef == typeof(IList<>) || genericDef == typeof(List<>); - public static string GetFriendlyName(Type type) + private static bool IsSystemType(Type type) { - if (!type.IsGenericType) - { - if (type.IsArray) - { - return "CollectionOf" + type.GetElementType()!.Name; - } - - return type.Name; - } + if (Type.GetTypeCode(type) is not (TypeCode.Empty or TypeCode.Object or TypeCode.DBNull)) + return true; + return type == typeof(Guid) || type == typeof(DateTimeOffset) || type == typeof(TimeSpan) || type == typeof(Uri); + } + + private static string? GetFriendlyGenericTypeName(Type type) + { StringBuilder sb = new(); while (type.IsGenericType) @@ -35,6 +33,7 @@ public static string GetFriendlyName(Type type) if (genericTypeDefinition == typeof(LegacyDataResponse<>) || genericTypeDefinition == typeof(Nullable<>)) { type = type.GetGenericArguments()[0]; + if (IsSystemType(type)) return null; continue; } @@ -59,8 +58,19 @@ public static string GetFriendlyName(Type type) return sb.ToString(); } - public static string GetFriendlyName(JsonTypeInfo type) + + public static string? GetFriendlyName(Type type) + { + if (IsSystemType(type)) return null; + + if (type.IsArray) return "CollectionOf" + (GetFriendlyName(type.GetElementType()!) ?? type.Name); + + if (type.IsGenericType) return GetFriendlyGenericTypeName(type); + + return type.Name; + } + public static string? GetFriendlyName(JsonTypeInfo jsonTypeInfo) { - return GetFriendlyName(type.Type); + return GetFriendlyName(jsonTypeInfo.Type); } } From 1bf98f085ce802a7a97eae5e9a31c9a03aee2c29 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Wed, 30 Apr 2025 01:15:24 +0200 Subject: [PATCH 17/19] Update OpenApiSchemaReferenceIdUtil.cs --- Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs b/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs index 270c0cc7..b848fa1f 100644 --- a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs +++ b/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs @@ -6,13 +6,17 @@ namespace OpenShock.Common.OpenApi; public static class OpenApiSchemaReferenceIdUtil { - private static bool IsCollection(Type genericDef) - => genericDef == typeof(IEnumerable<>) - || genericDef == typeof(IAsyncEnumerable<>) - || genericDef == typeof(IReadOnlyList<>) - || genericDef == typeof(IReadOnlyCollection<>) - || genericDef == typeof(IList<>) - || genericDef == typeof(List<>); + private static readonly HashSet CollectionTypes = + [ + typeof(List<>), + typeof(IList<>), + typeof(IReadOnlyList<>), + typeof(IEnumerable<>), + typeof(IAsyncEnumerable<>), + typeof(IReadOnlyCollection<>) + ]; + + private static bool IsCollection(Type genericDef) => CollectionTypes.Contains(genericDef); private static bool IsSystemType(Type type) { From 499fcf8cf7641b00c373111ced74134a9993076f Mon Sep 17 00:00:00 2001 From: hhvrc Date: Wed, 30 Apr 2025 13:09:29 +0200 Subject: [PATCH 18/19] Better ReferenceIds --- .../OpenApi/OpenApiSchemaReferenceIdUtil.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs b/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs index b848fa1f..4e3888b9 100644 --- a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs +++ b/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs @@ -1,6 +1,5 @@ -using System.Text; +using OpenShock.Common.Models; using System.Text.Json.Serialization.Metadata; -using OpenShock.Common.Models; namespace OpenShock.Common.OpenApi; @@ -28,10 +27,17 @@ private static bool IsSystemType(Type type) private static string? GetFriendlyGenericTypeName(Type type) { - StringBuilder sb = new(); - - while (type.IsGenericType) + string suffix = ""; + + while (type.IsGenericType || type.IsArray) { + if (type.IsArray) + { + suffix = "Array" + suffix; + type = type.GetElementType()!; + continue; + } + var genericTypeDefinition = type.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(LegacyDataResponse<>) || genericTypeDefinition == typeof(Nullable<>)) @@ -43,14 +49,14 @@ private static bool IsSystemType(Type type) if (IsCollection(genericTypeDefinition)) { - sb.Append("CollectionOf"); + suffix = "Array" + suffix; type = type.GetGenericArguments()[0]; continue; } if (genericTypeDefinition == typeof(Paginated<>)) { - sb.Append("Paginated"); + suffix = "Page" + suffix; type = type.GetGenericArguments()[0]; continue; } @@ -58,19 +64,16 @@ private static bool IsSystemType(Type type) throw new NotImplementedException(); } - sb.Append(type.Name); - - return sb.ToString(); + return type.Name + suffix; } public static string? GetFriendlyName(Type type) { if (IsSystemType(type)) return null; - if (type.IsArray) return "CollectionOf" + (GetFriendlyName(type.GetElementType()!) ?? type.Name); - - if (type.IsGenericType) return GetFriendlyGenericTypeName(type); + if (type.IsGenericType || type.IsArray) return GetFriendlyGenericTypeName(type); + return type.Name; } public static string? GetFriendlyName(JsonTypeInfo jsonTypeInfo) From aa17759051aa8da204fb5c7c9a7de763fd103ba1 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Wed, 30 Apr 2025 13:18:24 +0200 Subject: [PATCH 19/19] More refactoring --- ...aReferenceIdUtil.cs => OpenApiSchemaUtils.cs} | 16 +++++++++++----- Common/OpenShockServiceHelper.cs | 16 ++-------------- 2 files changed, 13 insertions(+), 19 deletions(-) rename Common/OpenApi/{OpenApiSchemaReferenceIdUtil.cs => OpenApiSchemaUtils.cs} (82%) diff --git a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs b/Common/OpenApi/OpenApiSchemaUtils.cs similarity index 82% rename from Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs rename to Common/OpenApi/OpenApiSchemaUtils.cs index 4e3888b9..ab9bb351 100644 --- a/Common/OpenApi/OpenApiSchemaReferenceIdUtil.cs +++ b/Common/OpenApi/OpenApiSchemaUtils.cs @@ -1,9 +1,10 @@ -using OpenShock.Common.Models; +using Microsoft.AspNetCore.OpenApi; +using OpenShock.Common.Models; using System.Text.Json.Serialization.Metadata; namespace OpenShock.Common.OpenApi; -public static class OpenApiSchemaReferenceIdUtil +public static class OpenApiSchemaUtils { private static readonly HashSet CollectionTypes = [ @@ -67,7 +68,7 @@ private static bool IsSystemType(Type type) return type.Name + suffix; } - public static string? GetFriendlyName(Type type) + private static string? GetFriendlyName(Type type) { if (IsSystemType(type)) return null; @@ -76,8 +77,13 @@ private static bool IsSystemType(Type type) return type.Name; } - public static string? GetFriendlyName(JsonTypeInfo jsonTypeInfo) + + + public static void ConfigureOptions(OpenApiOptions options) { - return GetFriendlyName(jsonTypeInfo.Type); + options.CreateSchemaReferenceId = (jsonTypeInfo) => GetFriendlyName(jsonTypeInfo.Type); + + options.AddDocumentTransformer(); + options.AddOperationTransformer(); } } diff --git a/Common/OpenShockServiceHelper.cs b/Common/OpenShockServiceHelper.cs index 56de6e1a..0caf8a66 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -119,20 +119,8 @@ public static IServiceCollection AddOpenShockServices(this IServiceCollection se x.JsonSerializerOptions.Converters.Add(new CustomJsonStringEnumConverter()); }); - services.AddOpenApi("1", options => - { - options.CreateSchemaReferenceId = OpenApiSchemaReferenceIdUtil.GetFriendlyName; - - options.AddDocumentTransformer(); - options.AddOperationTransformer(); - }); - services.AddOpenApi("2", options => - { - options.CreateSchemaReferenceId = OpenApiSchemaReferenceIdUtil.GetFriendlyName; - - options.AddDocumentTransformer(); - options.AddOperationTransformer(); - }); + services.AddOpenApi("1", OpenApiSchemaUtils.ConfigureOptions); + services.AddOpenApi("2", OpenApiSchemaUtils.ConfigureOptions); var apiVersioningBuilder = services.AddApiVersioning(options => {