Compare commits

..

No commits in common. "2e48b0067f0dec2d1f7afbbf2e83596131ba0c7b" and "332e5a013b4fc1b99f0651aa7330113710417997" have entirely different histories.

42 changed files with 175 additions and 169 deletions

View File

@ -0,0 +1,23 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Mirea.Api.Dto.Responses;
/// <summary>
/// Provides a JWT and RT token.
/// </summary>
public class TokenResponse
{
/// <summary>
/// A JWT token for accessing protected resources.
/// </summary>
[Required]
public required string AccessToken { get; set; }
/// <summary>
/// The date and time when the JWT token expires.
/// </summary>
/// <remarks>After this date, a new JWT token must be requested.</remarks>
[Required]
public required DateTime ExpiresIn { get; set; }
}

View File

@ -1,4 +1,4 @@
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
using Mirea.Api.Endpoint.Configuration.General.Settings;
using System.Collections.Generic;
using System.Linq;

View File

@ -3,7 +3,7 @@ using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Mirea.Api.Endpoint.Configuration.Model;
namespace Mirea.Api.Endpoint.Common.Settings;
public class Admin : ISaveSettings
{

View File

@ -1,10 +1,10 @@
using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
using Mirea.Api.Endpoint.Configuration.General.Settings;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Mirea.Api.Endpoint.Configuration.Model;
namespace Mirea.Api.Endpoint.Common.Settings;
public class GeneralConfig : ISaveSettings
{

View File

@ -1,4 +1,4 @@
namespace Mirea.Api.Endpoint.Configuration;
namespace Mirea.Api.Endpoint.Common.Settings;
public interface ISaveSettings
{
void SaveSetting();

View File

@ -1,7 +1,8 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.DependencyInjection;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration;
public static class ApiVersioningConfiguration
{
@ -13,10 +14,14 @@ public static class ApiVersioningConfiguration
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
}).AddApiExplorer(options =>
});
services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
services.AddEndpointsApiExplorer();
}
}

View File

@ -1,9 +1,9 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
using Mirea.Api.Endpoint.Common.Settings;
using Mirea.Api.Endpoint.Configuration.General.Settings;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration;
public static class CacheConfiguration
{

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration;
public static class EnvironmentConfiguration
{

View File

@ -7,7 +7,7 @@ using Mirea.Api.Security.Common.Interfaces;
using System;
using System.Text;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration;
public static class JwtConfiguration
{

View File

@ -2,14 +2,14 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Common.Settings;
using Serilog;
using Serilog.Events;
using Serilog.Filters;
using Serilog.Formatting.Compact;
using System.IO;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration;
public static class LoggerConfiguration
{

View File

@ -1,12 +1,12 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Mirea.Api.Endpoint.Common.Services.Security;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
using Mirea.Api.Endpoint.Common.Settings;
using Mirea.Api.Endpoint.Configuration.General.Settings;
using Mirea.Api.Security;
using Mirea.Api.Security.Common.Interfaces;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration;
public static class SecureConfiguration
{

View File

@ -1,5 +1,5 @@
using Asp.Versioning.ApiExplorer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
@ -9,7 +9,7 @@ using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.IO;
namespace Mirea.Api.Endpoint.Configuration.Core.Startup;
namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration;
public static class SwaggerConfiguration
{

View File

@ -1,16 +0,0 @@
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace Mirea.Api.Endpoint.Configuration.Core.Middleware;
public class CookieAuthorizationMiddleware(RequestDelegate next)
{
public const string JwtAuthorizationName = "_ajwt";
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Cookies.ContainsKey(JwtAuthorizationName))
context.Request.Headers.Authorization = "Bearer " + context.Request.Cookies[JwtAuthorizationName];
await next(context);
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace Mirea.Api.Endpoint.Configuration.Validation.Attributes;
namespace Mirea.Api.Endpoint.Configuration.General.Attributes;
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class RequiredSettingsAttribute : Attribute;

View File

@ -0,0 +1,6 @@
namespace Mirea.Api.Endpoint.Configuration.General.Interfaces;
public interface IIsConfigured
{
bool IsConfigured();
}

View File

@ -1,7 +1,7 @@
using Mirea.Api.Endpoint.Configuration.Validation.Attributes;
using Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
using Mirea.Api.Endpoint.Configuration.General.Attributes;
using Mirea.Api.Endpoint.Configuration.General.Interfaces;
namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
namespace Mirea.Api.Endpoint.Configuration.General.Settings;
[RequiredSettings]
public class CacheSettings : IIsConfigured

View File

@ -1,10 +1,10 @@
using Mirea.Api.DataAccess.Persistence.Common;
using Mirea.Api.Endpoint.Configuration.Validation.Attributes;
using Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
using Mirea.Api.Endpoint.Configuration.General.Attributes;
using Mirea.Api.Endpoint.Configuration.General.Interfaces;
using System;
using System.Text.Json.Serialization;
namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
namespace Mirea.Api.Endpoint.Configuration.General.Settings;
[RequiredSettings]
public class DbSettings : IIsConfigured

View File

@ -1,6 +1,6 @@
using Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
using Mirea.Api.Endpoint.Configuration.General.Interfaces;
namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
namespace Mirea.Api.Endpoint.Configuration.General.Settings;
public class EmailSettings : IIsConfigured
{

View File

@ -1,7 +1,7 @@
using Mirea.Api.Endpoint.Configuration.Validation.Attributes;
using Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
using Mirea.Api.Endpoint.Configuration.General.Attributes;
using Mirea.Api.Endpoint.Configuration.General.Interfaces;
namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
namespace Mirea.Api.Endpoint.Configuration.General.Settings;
[RequiredSettings]
public class LogSettings : IIsConfigured

View File

@ -1,10 +1,10 @@
using Mirea.Api.Endpoint.Configuration.Validation.Attributes;
using Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
using Mirea.Api.Endpoint.Configuration.General.Attributes;
using Mirea.Api.Endpoint.Configuration.General.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
namespace Mirea.Api.Endpoint.Configuration.General.Settings;
[RequiredSettings]
public class ScheduleSettings : IIsConfigured

View File

@ -1,7 +1,7 @@
using Mirea.Api.Endpoint.Common.Interfaces;
using System;
namespace Mirea.Api.Endpoint.Configuration.Validation;
namespace Mirea.Api.Endpoint.Configuration.General;
public class SetupTokenService : ISetupToken
{

View File

@ -1,11 +1,11 @@
using Microsoft.Extensions.Options;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Configuration.Validation.Attributes;
using Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
using Mirea.Api.Endpoint.Common.Settings;
using Mirea.Api.Endpoint.Configuration.General.Attributes;
using Mirea.Api.Endpoint.Configuration.General.Interfaces;
using System;
using System.Reflection;
namespace Mirea.Api.Endpoint.Configuration.Validation.Validators;
namespace Mirea.Api.Endpoint.Configuration.General.Validators;
public class SettingsRequiredValidator
{

View File

@ -1,4 +1,4 @@
using Asp.Versioning.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;

View File

@ -1,6 +0,0 @@
namespace Mirea.Api.Endpoint.Configuration.Validation.Interfaces;
public interface IIsConfigured
{
bool IsConfigured();
}

View File

@ -1,5 +1,4 @@
using Asp.Versioning;
using Cronos;
using Cronos;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
@ -10,9 +9,9 @@ using Mirea.Api.Endpoint.Common.Attributes;
using Mirea.Api.Endpoint.Common.Exceptions;
using Mirea.Api.Endpoint.Common.Interfaces;
using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
using Mirea.Api.Endpoint.Configuration.Validation.Validators;
using Mirea.Api.Endpoint.Common.Settings;
using Mirea.Api.Endpoint.Configuration.General.Settings;
using Mirea.Api.Endpoint.Configuration.General.Validators;
using Mirea.Api.Security.Services;
using MySqlConnector;
using Npgsql;
@ -29,6 +28,7 @@ using System.Security.Cryptography;
namespace Mirea.Api.Endpoint.Controllers.Configuration;
[ApiVersion("1.0")]
[ApiController]
[MaintenanceModeIgnore]
[ApiExplorerSettings(IgnoreApi = true)]
public class SetupController(

View File

@ -1,16 +1,15 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
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.Services;
using Mirea.Api.Endpoint.Configuration.Core.Middleware;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Common.Settings;
using Mirea.Api.Security.Common.Dto.Requests;
using Mirea.Api.Security.Services;
using System;
@ -46,19 +45,13 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
private void SetRefreshToken(string value, DateTimeOffset? expires = null)
{
SetCookie("refresh_token", value, expires);
SetCookie("user_key", Fingerprint);
SetCookie("user_key", Fingerprint, expires);
}
private void SetFirstToken(string value, DateTimeOffset? expires = null)
{
SetCookie("authentication_token", value, expires);
SetCookie("user_key", Fingerprint);
}
private void SetAuthToken(string value, DateTimeOffset? expires = null)
{
SetCookie(CookieAuthorizationMiddleware.JwtAuthorizationName, value, expires);
SetCookie("user_key", Fingerprint);
SetCookie("user_key", Fingerprint, expires);
}
[ApiExplorerSettings(IgnoreApi = true)]
@ -82,17 +75,17 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
/// then generating and returning an authentication token if successful.
/// </summary>
/// <param name="request">The login request containing the username/email and password.</param>
/// <returns>User's AuthRoles.</returns>
/// <returns>A TokenResponse containing the access token and its expiry if successful, otherwise an Unauthorized response.</returns>
[HttpPost("Login")]
[BadRequestResponse]
public async Task<ActionResult<AuthRoles>> Login([FromBody] LoginRequest request)
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<TokenResponse>> Login([FromBody] LoginRequest request)
{
var userEntity = user.Value;
if (!userEntity.Username.Equals(request.Username, StringComparison.OrdinalIgnoreCase) &&
!userEntity.Email.Equals(request.Username, StringComparison.OrdinalIgnoreCase) ||
!passwordService.VerifyPassword(request.Password, userEntity.Salt, userEntity.PasswordHash))
return BadRequest("Invalid username/email or password");
return Unauthorized("Invalid username/email or password");
var token = await auth.GenerateAuthTokensAsync(new TokenRequest
{
@ -102,19 +95,21 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
}, "1");
SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn);
SetAuthToken(token.AccessToken, token.AccessExpiresIn);
return Ok(AuthRoles.Admin);
return Ok(new TokenResponse
{
AccessToken = token.AccessToken,
ExpiresIn = token.AccessExpiresIn
});
}
/// <summary>
/// Refreshes the authentication token using the existing refresh token.
/// </summary>
/// <returns>User's AuthRoles.</returns>
/// <returns>A TokenResponse containing the new access token and its expiry if successful, otherwise an Unauthorized response.</returns>
[HttpGet("ReLogin")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<AuthRoles>> ReLogin()
public async Task<ActionResult<TokenResponse>> ReLogin()
{
if (string.IsNullOrEmpty(RefreshToken))
return Unauthorized();
@ -132,13 +127,16 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
);
SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn);
SetAuthToken(token.AccessToken, token.AccessExpiresIn);
return Ok(AuthRoles.Admin);
return Ok(new TokenResponse
{
AccessToken = token.AccessToken,
ExpiresIn = token.AccessExpiresIn
});
}
catch (SecurityException)
{
return Forbid();
return Unauthorized();
}
}
@ -153,7 +151,6 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
{
SetRefreshToken("", DateTimeOffset.MinValue);
SetFirstToken("", DateTimeOffset.MinValue);
SetAuthToken("", DateTimeOffset.MinValue);
await auth.LogoutAsync(Fingerprint);
@ -167,6 +164,7 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
[HttpGet("GetRole")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize]
[CacheMaxAge(0, 0, 1)]
public ActionResult<AuthRoles> GetRole() => Ok(AuthRoles.Admin);
[HttpPost("RenewPassword")]

View File

@ -1,5 +1,5 @@
using Asp.Versioning;
using MediatR;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList;
using Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusDetails;
@ -20,6 +20,7 @@ public class CampusController(IMediator mediator) : BaseController
/// </summary>
/// <returns>Basic information about campuses.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<List<CampusBasicInfoResponse>>> Get()
{
var result = await mediator.Send(new GetCampusBasicInfoListQuery());
@ -40,6 +41,7 @@ public class CampusController(IMediator mediator) : BaseController
/// <param name="id">Campus ID.</param>
/// <returns>Details of the specified campus.</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<CampusDetailsResponse>> GetDetails(int id)

View File

@ -1,5 +1,5 @@
using Asp.Versioning;
using MediatR;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineDetails;
using Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineList;
@ -22,6 +22,7 @@ public class DisciplineController(IMediator mediator) : BaseController
/// <param name="pageSize">Number of items per page.</param>
/// <returns>Paginated list of disciplines.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
public async Task<ActionResult<List<DisciplineResponse>>> Get([FromQuery] int? page, [FromQuery] int? pageSize)
{
@ -46,6 +47,7 @@ public class DisciplineController(IMediator mediator) : BaseController
/// <param name="id">Discipline ID.</param>
/// <returns>Details of the specified discipline.</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<DisciplineResponse>> GetDetails(int id)

View File

@ -1,5 +1,5 @@
using Asp.Versioning;
using MediatR;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails;
using Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyList;
@ -22,6 +22,7 @@ public class FacultyController(IMediator mediator) : BaseController
/// <param name="pageSize">Number of items per page.</param>
/// <returns>Paginated list of faculties.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
public async Task<ActionResult<List<FacultyResponse>>> Get([FromQuery] int? page, [FromQuery] int? pageSize)
{
@ -47,6 +48,7 @@ public class FacultyController(IMediator mediator) : BaseController
/// <param name="id">Faculty ID.</param>
/// <returns>Details of the specified faculty.</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<FacultyDetailsResponse>> GetDetails(int id)

View File

@ -1,5 +1,5 @@
using Asp.Versioning;
using MediatR;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupDetails;
using Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupList;
@ -36,6 +36,7 @@ public class GroupController(IMediator mediator) : BaseController
/// <param name="pageSize">The page size for pagination (optional).</param>
/// <returns>A list of groups.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
public async Task<ActionResult<List<GroupResponse>>> Get([FromQuery] int? page, [FromQuery] int? pageSize)
{
@ -62,6 +63,7 @@ public class GroupController(IMediator mediator) : BaseController
/// <param name="id">The ID of the group to retrieve.</param>
/// <returns>Detailed information about the group.</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<GroupDetailsResponse>> GetDetails(int id)
@ -87,6 +89,7 @@ public class GroupController(IMediator mediator) : BaseController
/// <param name="id">The ID of the faculty.</param>
/// <returns>A list of groups belonging to the specified faculty.</returns>
[HttpGet("GetByFaculty/{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<List<GroupResponse>>> GetByFaculty(int id)

View File

@ -1,5 +1,5 @@
using Asp.Versioning;
using MediatR;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallDetails;
using Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallList;
@ -20,6 +20,7 @@ public class LectureHallController(IMediator mediator) : BaseController
/// </summary>
/// <returns>A list of lecture halls.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<List<LectureHallResponse>>> Get()
{
var result = await mediator.Send(new GetLectureHallListQuery());
@ -40,6 +41,7 @@ public class LectureHallController(IMediator mediator) : BaseController
/// <param name="id">The ID of the lecture hall to retrieve.</param>
/// <returns>The details of the specified lecture hall.</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<LectureHallDetailsResponse>> GetDetails(int id)
@ -65,6 +67,7 @@ public class LectureHallController(IMediator mediator) : BaseController
/// <param name="id">The ID of the campus.</param>
/// <returns>A list of lecture halls in the specified campus.</returns>
[HttpGet("GetByCampus/{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<List<LectureHallResponse>>> GetByCampus(int id)

View File

@ -1,5 +1,5 @@
using Asp.Versioning;
using MediatR;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails;
using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorList;
@ -22,6 +22,7 @@ public class ProfessorController(IMediator mediator) : BaseController
/// <param name="pageSize">The page size for pagination (optional).</param>
/// <returns>A list of professors.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
public async Task<ActionResult<List<ProfessorResponse>>> Get([FromQuery] int? page, [FromQuery] int? pageSize)
{
@ -47,6 +48,7 @@ public class ProfessorController(IMediator mediator) : BaseController
/// <param name="id">The ID of the professor to retrieve.</param>
/// <returns>Detailed information about the professor.</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<ProfessorResponse>> GetDetails(int id)

View File

@ -1,5 +1,4 @@
using Asp.Versioning;
using MediatR;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@ -9,7 +8,7 @@ using Mirea.Api.Dto.Requests;
using Mirea.Api.Dto.Responses;
using Mirea.Api.Endpoint.Common.Attributes;
using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Common.Settings;
using System;
using System.Collections.Generic;
using System.Linq;
@ -35,8 +34,10 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConf
/// <param name="request">The request object containing filter criteria.</param>
/// <returns>A list of schedules matching the filter criteria.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[BadRequestResponse]
[NotFoundResponse]
public async Task<ActionResult<List<ScheduleResponse>>> Get([FromBody] ScheduleRequest request)
{
if ((request.Groups == null || request.Groups.Length == 0) &&
@ -63,8 +64,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConf
ProfessorIds = request.Professors
})).Schedules;
if (result.Count == 0)
NoContent();
if (result.Count == 0) NoContent();
return Ok(result.Select(s => new ScheduleResponse
{
@ -98,6 +98,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConf
/// <param name="lectureHalls">An array of lecture hall IDs.</param>
/// <returns>A response containing schedules for the specified group.</returns>
[HttpGet("GetByGroup/{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[BadRequestResponse]
[NotFoundResponse]
@ -125,6 +126,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConf
/// <param name="lectureHalls">An array of lecture hall IDs.</param>
/// <returns>A response containing schedules for the specified professor.</returns>
[HttpGet("GetByProfessor/{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[BadRequestResponse]
[NotFoundResponse]
@ -152,6 +154,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConf
/// <param name="groups">An array of group IDs.</param>
/// <returns>A response containing schedules for the specified lecture hall.</returns>
[HttpGet("GetByLectureHall/{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[BadRequestResponse]
[NotFoundResponse]
@ -179,6 +182,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot<GeneralConf
/// <param name="lectureHalls">An array of lecture hall IDs.</param>
/// <returns>A response containing schedules for the specified discipline.</returns>
[HttpGet("GetByDiscipline/{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[BadRequestResponse]
[NotFoundResponse]

View File

@ -5,9 +5,9 @@
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<Company>Winsomnia</Company>
<Version>1.0.0-rc2</Version>
<AssemblyVersion>1.0.2.2</AssemblyVersion>
<FileVersion>1.0.2.2</FileVersion>
<Version>1.0.0-rc0</Version>
<AssemblyVersion>1.0.2.0</AssemblyVersion>
<FileVersion>1.0.2.0</FileVersion>
<AssemblyName>Mirea.Api.Endpoint</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
<OutputType>Exe</OutputType>
@ -22,38 +22,22 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageReference Include="Cronos" Version="0.8.4" />
<PackageReference Include="EPPlus" Version="7.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.11.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.1.2" />
<PackageReference Include="Mirea.Tools.Schedule.WebParser" Version="1.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" />
<PackageReference Include="System.CodeDom" Version="8.0.0" />
<PackageReference Include="System.Composition" Version="8.0.0" />
<PackageReference Include="System.Composition.TypedParts" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
<PackageReference Include="Z.EntityFramework.Extensions.EFCore" Version="8.103.5" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.7.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Versioning" Version="2.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.2" />
</ItemGroup>
<ItemGroup>

View File

@ -3,12 +3,12 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Mirea.Api.Endpoint.Common.Attributes;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Common.Settings;
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace Mirea.Api.Endpoint.Configuration.Core.Middleware;
namespace Mirea.Api.Endpoint.Middleware;
public class CacheMaxAgeMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{

View File

@ -7,7 +7,7 @@ using System;
using System.Text.Json;
using System.Threading.Tasks;
namespace Mirea.Api.Endpoint.Configuration.Core.Middleware;
namespace Mirea.Api.Endpoint.Middleware;
public class CustomExceptionHandlerMiddleware(RequestDelegate next)
{

View File

@ -2,7 +2,7 @@
using Mirea.Api.Security.Common.Interfaces;
using System.Threading.Tasks;
namespace Mirea.Api.Endpoint.Configuration.Core.Middleware;
namespace Mirea.Api.Endpoint.Middleware;
public class JwtRevocationMiddleware(RequestDelegate next)
{

View File

@ -3,7 +3,7 @@ using Mirea.Api.Endpoint.Common.Attributes;
using Mirea.Api.Endpoint.Common.Interfaces;
using System.Threading.Tasks;
namespace Mirea.Api.Endpoint.Configuration.Core.Middleware;
namespace Mirea.Api.Endpoint.Middleware;
public class MaintenanceModeMiddleware(RequestDelegate next, IMaintenanceModeService maintenanceModeService, IMaintenanceModeNotConfigureService maintenanceModeNotConfigureService)
{

View File

@ -10,11 +10,11 @@ using Mirea.Api.DataAccess.Persistence;
using Mirea.Api.DataAccess.Persistence.Common;
using Mirea.Api.Endpoint.Common.Interfaces;
using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Core.Middleware;
using Mirea.Api.Endpoint.Configuration.Core.Startup;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Configuration.Validation;
using Mirea.Api.Endpoint.Configuration.Validation.Validators;
using Mirea.Api.Endpoint.Common.Settings;
using Mirea.Api.Endpoint.Configuration.ApplicationConfiguration;
using Mirea.Api.Endpoint.Configuration.General;
using Mirea.Api.Endpoint.Configuration.General.Validators;
using Mirea.Api.Endpoint.Middleware;
using Mirea.Api.Security.Services;
using System;
using System.IO;
@ -64,9 +64,6 @@ public class Program
policy.AllowAnyMethod();
policy.AllowAnyHeader();
policy.AllowCredentials();
#if DEBUG
policy.WithOrigins("http://localhost:4200");
#endif
});
});
@ -84,7 +81,6 @@ public class Program
{
secretForward.SecretForwardToken = GeneratorKey.GenerateAlphaNumeric(16);
secretForward.SaveSetting();
Console.WriteLine($"For the reverse proxy server to work correctly, use the header: '{secretForward.SecretForwardToken}-X-Forwarded-For'");
}
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
@ -128,18 +124,16 @@ public class Program
}
app.UseCustomSwagger(app.Services);
app.UseHttpsRedirection();
app.UseMiddleware<CustomExceptionHandlerMiddleware>();
app.UseMiddleware<MaintenanceModeMiddleware>();
app.UseMiddleware<CookieAuthorizationMiddleware>();
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<JwtRevocationMiddleware>();
app.UseMiddleware<CacheMaxAgeMiddleware>();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -15,7 +15,7 @@
<ItemGroup>
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
</ItemGroup>
</Project>

View File

@ -5,18 +5,18 @@
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<Company>Winsomnia</Company>
<Version>1.0.1</Version>
<AssemblyVersion>1.0.3.1</AssemblyVersion>
<FileVersion>1.0.3.1</FileVersion>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.3.0</AssemblyVersion>
<FileVersion>1.0.3.0</FileVersion>
<AssemblyName>Mirea.Api.DataAccess.Application</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.10.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.10.0" />
<PackageReference Include="MediatR" Version="12.4.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="FluentValidation" Version="11.9.2" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageReference Include="MediatR" Version="12.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
</ItemGroup>
<ItemGroup>

View File

@ -13,9 +13,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>