diff --git a/ApiDto/Responses/ErrorResponse.cs b/ApiDto/Responses/ErrorResponse.cs
deleted file mode 100644
index f0dc33c..0000000
--- a/ApiDto/Responses/ErrorResponse.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-
-namespace Mirea.Api.Dto.Responses;
-
-///
-/// A class for providing information about an error
-///
-public class ErrorResponse
-{
- ///
- /// The text or translation code of the error. This field may not contain information in specific scenarios.
- /// For example, it might be empty for HTTP 204 responses where no content is returned or if the validation texts have not been configured.
- ///
- [Required]
- public required string Error { get; set; }
- ///
- /// In addition to returning the response code in the header, it is also duplicated in this field.
- /// Represents the HTTP response code.
- ///
- [Required]
- public required int Code { get; set; }
-}
\ No newline at end of file
diff --git a/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs b/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs
index 85f9ed8..b424733 100644
--- a/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs
+++ b/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs
@@ -1,9 +1,8 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Mirea.Api.Dto.Responses;
using System;
namespace Mirea.Api.Endpoint.Common.Attributes;
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
-public class BadRequestResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponse), StatusCodes.Status400BadRequest);
\ No newline at end of file
+public class BadRequestResponseAttribute() : ProducesResponseTypeAttribute(typeof(ProblemDetails), StatusCodes.Status400BadRequest);
\ No newline at end of file
diff --git a/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs b/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs
index 39527ea..7b5855f 100644
--- a/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs
+++ b/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs
@@ -1,9 +1,8 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Mirea.Api.Dto.Responses;
using System;
namespace Mirea.Api.Endpoint.Common.Attributes;
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
-public class NotFoundResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponse), StatusCodes.Status404NotFound);
\ No newline at end of file
+public class NotFoundResponseAttribute() : ProducesResponseTypeAttribute(typeof(ProblemDetails), StatusCodes.Status404NotFound);
\ No newline at end of file
diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs
index d5aff77..3c65dd8 100644
--- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs
+++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs
@@ -1,10 +1,13 @@
using FluentValidation;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
-using Mirea.Api.Dto.Responses;
using Mirea.Api.Endpoint.Common.Exceptions;
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
using System.Security;
using System.Text.Json;
using System.Threading.Tasks;
@@ -27,50 +30,55 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger()
+ {
+ { "traceId", traceId }
+ }
+ };
+
switch (exception)
{
case ValidationException validationException:
- code = StatusCodes.Status400BadRequest;
- result = JsonSerializer.Serialize(new ErrorResponse()
+ problemDetails.Status = StatusCodes.Status400BadRequest;
+ problemDetails.Type = "https://tools.ietf.org/html/rfc9110#section-15.5.1";
+ problemDetails.Title = "Validation errors occurred.";
+ problemDetails.Extensions = new Dictionary
{
- Error = validationException.Message,
- Code = code
- });
+ { "errors", validationException.Errors.Select(e => e.ErrorMessage).ToArray() },
+ { "traceId", traceId }
+ };
break;
case NotFoundException:
- code = StatusCodes.Status404NotFound;
+ problemDetails.Status = StatusCodes.Status404NotFound;
+ problemDetails.Type = "https://tools.ietf.org/html/rfc9110#section-15.5.4";
+ problemDetails.Title = "Resource not found.";
break;
case ControllerArgumentException:
- code = StatusCodes.Status400BadRequest;
+ problemDetails.Status = StatusCodes.Status400BadRequest;
+ problemDetails.Type = "https://tools.ietf.org/html/rfc9110#section-15.5.1";
+ problemDetails.Title = "Invalid arguments provided.";
break;
case SecurityException:
- code = StatusCodes.Status401Unauthorized;
+ problemDetails.Status = StatusCodes.Status401Unauthorized;
+ problemDetails.Type = "https://tools.ietf.org/html/rfc9110#section-15.5.2";
+ problemDetails.Title = "Unauthorized access.";
break;
}
- context.Response.ContentType = "application/json";
- context.Response.StatusCode = code;
-
- if (!string.IsNullOrEmpty(result))
- return context.Response.WriteAsync(result);
-
- string error;
- if (code == StatusCodes.Status500InternalServerError)
- {
- error = "Internal Server Error";
+ if (problemDetails.Status == StatusCodes.Status500InternalServerError)
logger.LogError(exception, "Internal server error when processing the request");
- }
- else
- error = exception.Message;
- result = JsonSerializer.Serialize(new ErrorResponse()
- {
- Error = error,
- Code = code
- });
+ context.Response.ContentType = "application/json";
+ context.Response.StatusCode = problemDetails.Status.Value;
- return context.Response.WriteAsync(result);
+ return context.Response.WriteAsync(JsonSerializer.Serialize(problemDetails));
}
}
\ No newline at end of file
diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs
index da000b1..2e10259 100644
--- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs
+++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs
@@ -4,9 +4,11 @@ using Microsoft.Extensions.Hosting;
using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Model;
using Serilog;
+using Serilog.Context;
using Serilog.Events;
using Serilog.Filters;
using Serilog.Formatting.Compact;
+using System.Diagnostics;
using System.IO;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
@@ -55,7 +57,14 @@ public static class LoggerConfiguration
public static IApplicationBuilder UseCustomSerilog(this IApplicationBuilder app)
{
- return app.UseSerilogRequestLogging(options =>
+ return app.Use(async (context, next) =>
+ {
+ var traceId = Activity.Current?.Id ?? context.TraceIdentifier;
+ using (LogContext.PushProperty("TraceId", traceId))
+ {
+ await next();
+ }
+ }).UseSerilogRequestLogging(options =>
{
options.MessageTemplate = "[{RequestMethod}] {RequestPath} [Client {RemoteIPAddress}] [{StatusCode}] in {Elapsed:0.0000} ms";
diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs
index e1780f2..5ab4671 100644
--- a/Endpoint/Controllers/Configuration/SetupController.cs
+++ b/Endpoint/Controllers/Configuration/SetupController.cs
@@ -30,6 +30,7 @@ using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Runtime.InteropServices;
+using System.Security;
using System.Security.Cryptography;
using PasswordPolicy = Mirea.Api.Dto.Common.PasswordPolicy;
@@ -88,7 +89,7 @@ public class SetupController(
}
if (!setupToken.MatchToken(tokenBase64))
- return Unauthorized("The token is not valid");
+ throw new SecurityException("The token is not valid");
Response.Cookies.Append(TokenAuthenticationAttribute.AuthToken, token, new CookieOptions
{
diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs
index 529b29a..3c26de0 100644
--- a/Endpoint/Controllers/V1/AuthController.cs
+++ b/Endpoint/Controllers/V1/AuthController.cs
@@ -15,6 +15,7 @@ using Mirea.Api.Security.Common.Domain;
using Mirea.Api.Security.Services;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider;
@@ -31,17 +32,17 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot";
+ "'},'*');}window.close();}, 15000);";
- return $"{title}
{title}
{message}
Это информационная страница. Вы можете закрыть её.
{script}";
+ return $"{title}
{title}
{message}
Это информационная страница. Вы можете закрыть её.
{script}";
}
[HttpGet("OAuth2")]
@@ -56,6 +57,7 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot user, IOptionsSnapshot user, IOptionsSnapshot>> GetDetails(string name)
{
if (string.IsNullOrEmpty(name) || name.Length < 4)
- return BadRequest($"The minimum number of characters is 4 (current: {name.Length}).");
+ throw new ControllerArgumentException($"The minimum number of characters is 4 (current: {name.Length}).");
var result = await mediator.Send(new GetProfessorInfoSearchQuery()
{
diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs
index 55a7f03..00baf6e 100644
--- a/Endpoint/Controllers/V1/ScheduleController.cs
+++ b/Endpoint/Controllers/V1/ScheduleController.cs
@@ -8,6 +8,7 @@ using Mirea.Api.Dto.Common;
using Mirea.Api.Dto.Requests;
using Mirea.Api.Dto.Responses;
using Mirea.Api.Endpoint.Common.Attributes;
+using Mirea.Api.Endpoint.Common.Exceptions;
using Mirea.Api.Endpoint.Common.MapperDto;
using Mirea.Api.Endpoint.Configuration.Model;
using System;
@@ -52,14 +53,10 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot generalConfig) :
}
catch (Exception ex)
{
- return BadRequest($"Failed to generate QR code: {ex.Message}");
+ throw new ControllerArgumentException($"Failed to generate QR code: {ex.Message}");
}
}