From 4eecc19f4f8cb7084f3d6d12518b4e0107c749eb Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 7 Jan 2024 02:00:00 +0300 Subject: [PATCH 001/474] feat: add basic schedule data models --- Domain/Schedule/Campus.cs | 14 ++++++++++++++ Domain/Schedule/Day.cs | 15 +++++++++++++++ Domain/Schedule/Faculty.cs | 13 +++++++++++++ Domain/Schedule/Group.cs | 13 +++++++++++++ Domain/Schedule/LectureHall.cs | 13 +++++++++++++ Domain/Schedule/Lesson.cs | 14 ++++++++++++++ Domain/Schedule/LessonToTypeOfOccupation.cs | 11 +++++++++++ Domain/Schedule/Professor.cs | 12 ++++++++++++ Domain/Schedule/ProfessorToLesson.cs | 15 +++++++++++++++ Domain/Schedule/TypeOfOccupation.cs | 12 ++++++++++++ 10 files changed, 132 insertions(+) create mode 100644 Domain/Schedule/Campus.cs create mode 100644 Domain/Schedule/Day.cs create mode 100644 Domain/Schedule/Faculty.cs create mode 100644 Domain/Schedule/Group.cs create mode 100644 Domain/Schedule/LectureHall.cs create mode 100644 Domain/Schedule/Lesson.cs create mode 100644 Domain/Schedule/LessonToTypeOfOccupation.cs create mode 100644 Domain/Schedule/Professor.cs create mode 100644 Domain/Schedule/ProfessorToLesson.cs create mode 100644 Domain/Schedule/TypeOfOccupation.cs diff --git a/Domain/Schedule/Campus.cs b/Domain/Schedule/Campus.cs new file mode 100644 index 0000000..dde6d2d --- /dev/null +++ b/Domain/Schedule/Campus.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class Campus +{ + public int Id { get; set; } + public required string CodeName { get; set; } + public string? FullName { get; set; } + public string? Address { get; set; } + + public List? Faculties { get; set; } + public List? LectureHalls { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/Day.cs b/Domain/Schedule/Day.cs new file mode 100644 index 0000000..0da9657 --- /dev/null +++ b/Domain/Schedule/Day.cs @@ -0,0 +1,15 @@ +using System; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class Day +{ + public int Id { get; set; } + public DayOfWeek Index { get; set; } + public int PairNumber { get; set; } + + public int LessonId { get; set; } + public Lesson? Lesson { get; set; } + public int GroupId { get; set; } + public Group? Group { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/Faculty.cs b/Domain/Schedule/Faculty.cs new file mode 100644 index 0000000..12dbcbe --- /dev/null +++ b/Domain/Schedule/Faculty.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class Faculty +{ + public int Id { get; set; } + public required string Name { get; set; } + + public int? CampusId { get; set; } + public Campus? Campus { get; set; } + public List? Groups { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/Group.cs b/Domain/Schedule/Group.cs new file mode 100644 index 0000000..44c6b95 --- /dev/null +++ b/Domain/Schedule/Group.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class Group +{ + public int Id { get; set; } + public required string Name { get; set; } + + public int? FacultyId { get; set; } + public Faculty? Faculty { get; set; } + public List? Days { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/LectureHall.cs b/Domain/Schedule/LectureHall.cs new file mode 100644 index 0000000..70b7249 --- /dev/null +++ b/Domain/Schedule/LectureHall.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class LectureHall +{ + public int Id { get; set; } + public required string Name { get; set; } + + public List? ProfessorToLessons { get; set; } + public int CampusId { get; set; } + public Campus? Campus { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/Lesson.cs b/Domain/Schedule/Lesson.cs new file mode 100644 index 0000000..0851f0e --- /dev/null +++ b/Domain/Schedule/Lesson.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class Lesson +{ + public int Id { get; set; } + public bool IsEven { get; set; } + public required string Discipline { get; set; } + + public List? TypeOfOccupations { get; set; } + public List? ProfessorToLesson { get; set; } + public Day? Day { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/LessonToTypeOfOccupation.cs b/Domain/Schedule/LessonToTypeOfOccupation.cs new file mode 100644 index 0000000..c14e697 --- /dev/null +++ b/Domain/Schedule/LessonToTypeOfOccupation.cs @@ -0,0 +1,11 @@ +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class LessonToTypeOfOccupation +{ + public int Id { get; set; } + + public int LessonId { get; set; } + public Lesson? Lesson { get; set; } + public int TypeOfOccupationId { get; set; } + public TypeOfOccupation? TypeOfOccupation { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/Professor.cs b/Domain/Schedule/Professor.cs new file mode 100644 index 0000000..09309f8 --- /dev/null +++ b/Domain/Schedule/Professor.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class Professor +{ + public int Id { get; set; } + public required string Name { get; set; } + public string? AltName { get; set; } + + public List? ProfessorToLesson { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/ProfessorToLesson.cs b/Domain/Schedule/ProfessorToLesson.cs new file mode 100644 index 0000000..17b287e --- /dev/null +++ b/Domain/Schedule/ProfessorToLesson.cs @@ -0,0 +1,15 @@ +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class ProfessorToLesson +{ + public int Id { get; set; } + public string? LinkToMeet { get; set; } + + + public int LessonId { get; set; } + public Lesson? Lesson { get; set; } + public int? ProfessorId { get; set; } + public Professor? Professor { get; set; } + public int? LectureHallId { get; set; } + public LectureHall? LectureHall { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/TypeOfOccupation.cs b/Domain/Schedule/TypeOfOccupation.cs new file mode 100644 index 0000000..f7aeeb3 --- /dev/null +++ b/Domain/Schedule/TypeOfOccupation.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class TypeOfOccupation +{ + public int Id { get; set; } + public required string ShortName { get; set; } + public string? FullName { get; set; } + + public List? Lessons { get; set; } +} \ No newline at end of file -- 2.43.0 From f7998a17983f32bf413c09bc9c60569144ae4b08 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 14:42:52 +0300 Subject: [PATCH 002/474] feat: add project --- Application/Application.csproj | 15 +++++++++++++++ Backend.sln | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Application/Application.csproj diff --git a/Application/Application.csproj b/Application/Application.csproj new file mode 100644 index 0000000..76e4455 --- /dev/null +++ b/Application/Application.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + disable + enable + Winsomnia + 1.0.0-a0 + 1.0.0.0 + 1.0.0.0 + Mirea.Api.DataAccess.Application + $(AssemblyName) + + + \ No newline at end of file diff --git a/Backend.sln b/Backend.sln index 5c85ec6..f78e7d8 100644 --- a/Backend.sln +++ b/Backend.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{C27FB5CD-6A70-4FB2-847A-847B34806902}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "Domain\Domain.csproj", "{C27FB5CD-6A70-4FB2-847A-847B34806902}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Endpoint", "Endpoint\Endpoint.csproj", "{F3A1D12E-F5B2-4339-9966-DBF869E78357}" EndProject @@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Application\Application.csproj", "{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU -- 2.43.0 From 8028d400053ef02c7638fea7efc9a56c96f9adec Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 14:43:05 +0300 Subject: [PATCH 003/474] feat: add an interface for standard saving changes --- Application/Interfaces/DbContexts/IDbContextBase.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Application/Interfaces/DbContexts/IDbContextBase.cs diff --git a/Application/Interfaces/DbContexts/IDbContextBase.cs b/Application/Interfaces/DbContexts/IDbContextBase.cs new file mode 100644 index 0000000..2b650ff --- /dev/null +++ b/Application/Interfaces/DbContexts/IDbContextBase.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using System.Threading; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts; + +public interface IDbContextBase +{ + Task SaveChangesAsync(CancellationToken cancellationToken); +} \ No newline at end of file -- 2.43.0 From a389eb0a707c40a6e914d4f1aa5f76bbc98e774b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 14:50:21 +0300 Subject: [PATCH 004/474] feat: add an interface for working with db set --- Application/Application.csproj | 8 ++++++++ .../Interfaces/DbContexts/Schedule/ICampusDbContext.cs | 9 +++++++++ .../Interfaces/DbContexts/Schedule/IDayDbContext.cs | 9 +++++++++ .../Interfaces/DbContexts/Schedule/IFacultyDbContext.cs | 9 +++++++++ .../Interfaces/DbContexts/Schedule/IGroupDbContext.cs | 9 +++++++++ .../DbContexts/Schedule/ILectureHallDbContext.cs | 9 +++++++++ .../Interfaces/DbContexts/Schedule/ILessonDbContext.cs | 9 +++++++++ .../Schedule/ILessonToTypeOfOccupationDbContext.cs | 9 +++++++++ .../DbContexts/Schedule/IProfessorDbContext.cs | 9 +++++++++ .../DbContexts/Schedule/IProfessorToLessonDbContext.cs | 9 +++++++++ .../DbContexts/Schedule/ITypeOfOccupationDbContext.cs | 9 +++++++++ 11 files changed, 98 insertions(+) create mode 100644 Application/Interfaces/DbContexts/Schedule/ICampusDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/IDayDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/IGroupDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/ILessonDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/ILessonToTypeOfOccupationDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/IProfessorToLessonDbContext.cs create mode 100644 Application/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs diff --git a/Application/Application.csproj b/Application/Application.csproj index 76e4455..c03a6c1 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -12,4 +12,12 @@ $(AssemblyName) + + + + + + + + \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/ICampusDbContext.cs b/Application/Interfaces/DbContexts/Schedule/ICampusDbContext.cs new file mode 100644 index 0000000..40cd23b --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/ICampusDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface ICampusDbContext : IDbContextBase +{ + DbSet Campuses { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/IDayDbContext.cs b/Application/Interfaces/DbContexts/Schedule/IDayDbContext.cs new file mode 100644 index 0000000..f34d77a --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/IDayDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface IDayDbContext : IDbContextBase +{ + DbSet Days { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs b/Application/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs new file mode 100644 index 0000000..166c5cc --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface IFacultyDbContext : IDbContextBase +{ + DbSet Faculties { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/IGroupDbContext.cs b/Application/Interfaces/DbContexts/Schedule/IGroupDbContext.cs new file mode 100644 index 0000000..1fb85d7 --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/IGroupDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface IGroupDbContext : IDbContextBase +{ + DbSet Groups { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs b/Application/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs new file mode 100644 index 0000000..04f1a37 --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface ILectureHallDbContext : IDbContextBase +{ + DbSet LectureHalls { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/ILessonDbContext.cs b/Application/Interfaces/DbContexts/Schedule/ILessonDbContext.cs new file mode 100644 index 0000000..b29561a --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/ILessonDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface ILessonDbContext : IDbContextBase +{ + DbSet Lessons { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/ILessonToTypeOfOccupationDbContext.cs b/Application/Interfaces/DbContexts/Schedule/ILessonToTypeOfOccupationDbContext.cs new file mode 100644 index 0000000..0dacc7e --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/ILessonToTypeOfOccupationDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface ILessonToTypeOfOccupationDbContext : IDbContextBase +{ + DbSet LessonToTypeOfOccupations { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs b/Application/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs new file mode 100644 index 0000000..bb4b0d7 --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface IProfessorDbContext : IDbContextBase +{ + DbSet Professors { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/IProfessorToLessonDbContext.cs b/Application/Interfaces/DbContexts/Schedule/IProfessorToLessonDbContext.cs new file mode 100644 index 0000000..ba6aba5 --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/IProfessorToLessonDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface IProfessorToLessonDbContext : IDbContextBase +{ + DbSet ProfessorToLessons { get; set; } +} \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs b/Application/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs new file mode 100644 index 0000000..0c18dfa --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface ITypeOfOccupationDbContext : IDbContextBase +{ + DbSet TypeOfOccupations { get; set; } +} \ No newline at end of file -- 2.43.0 From 924f97332fadafdea3ee021e5012134767b50d4f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 14:50:52 +0300 Subject: [PATCH 005/474] style: sort reference --- Application/Interfaces/DbContexts/IDbContextBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Application/Interfaces/DbContexts/IDbContextBase.cs b/Application/Interfaces/DbContexts/IDbContextBase.cs index 2b650ff..c06bb69 100644 --- a/Application/Interfaces/DbContexts/IDbContextBase.cs +++ b/Application/Interfaces/DbContexts/IDbContextBase.cs @@ -1,5 +1,5 @@ -using System.Threading.Tasks; -using System.Threading; +using System.Threading; +using System.Threading.Tasks; namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts; -- 2.43.0 From 386272d493f1b549b2cd418d713d5834bdc428f8 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 15:02:03 +0300 Subject: [PATCH 006/474] feat: add validation behavior --- .../Common/Behaviors/ValidationBehavior.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Application/Common/Behaviors/ValidationBehavior.cs diff --git a/Application/Common/Behaviors/ValidationBehavior.cs b/Application/Common/Behaviors/ValidationBehavior.cs new file mode 100644 index 0000000..6cd5946 --- /dev/null +++ b/Application/Common/Behaviors/ValidationBehavior.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using MediatR; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Common.Behaviors; + +public class ValidationBehavior(IEnumerable> validators) + : IPipelineBehavior where TRequest : IRequest +{ + public Task Handle(TRequest request, + RequestHandlerDelegate next, CancellationToken cancellationToken) + { + var context = new ValidationContext(request); + var failures = validators + .Select(v => v.Validate(context)) + .SelectMany(result => result.Errors) + .Where(failure => failure != null) + .ToList(); + + if (failures.Count != 0) + { + throw new ValidationException(failures); + } + + return next(); + } +} \ No newline at end of file -- 2.43.0 From cfbd847d9abb773661ea9618614916d5b9f675ac Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 15:04:34 +0300 Subject: [PATCH 007/474] feat: add DI --- Application/DependencyInjection.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Application/DependencyInjection.cs diff --git a/Application/DependencyInjection.cs b/Application/DependencyInjection.cs new file mode 100644 index 0000000..2686e15 --- /dev/null +++ b/Application/DependencyInjection.cs @@ -0,0 +1,19 @@ +using MediatR; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; +using Mirea.Api.DataAccess.Application.Common.Behaviors; + +namespace Mirea.Api.DataAccess.Application; + +public static class DependencyInjection +{ + public static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); + services.AddValidatorsFromAssemblies(new[] { Assembly.GetExecutingAssembly() }); + services.AddTransient(typeof(IPipelineBehavior<,>), + typeof(ValidationBehavior<,>)); + return services; + } +} \ No newline at end of file -- 2.43.0 From 9e9d4e06fd85fc889a795e9272e3ccf52126718b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 16:11:01 +0300 Subject: [PATCH 008/474] feat: add mapping --- Application/Application.csproj | 4 +++ .../Common/Mappings/AssemblyMappingProfile.cs | 28 +++++++++++++++++++ Application/Common/Mappings/IMapWith.cs | 9 ++++++ Application/DependencyInjection.cs | 4 +-- 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 Application/Common/Mappings/AssemblyMappingProfile.cs create mode 100644 Application/Common/Mappings/IMapWith.cs diff --git a/Application/Application.csproj b/Application/Application.csproj index c03a6c1..2003517 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -13,6 +13,10 @@ + + + + diff --git a/Application/Common/Mappings/AssemblyMappingProfile.cs b/Application/Common/Mappings/AssemblyMappingProfile.cs new file mode 100644 index 0000000..68a9e8e --- /dev/null +++ b/Application/Common/Mappings/AssemblyMappingProfile.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using System; +using System.Linq; +using System.Reflection; + +namespace Mirea.Api.DataAccess.Application.Common.Mappings; + +public class AssemblyMappingProfile : Profile +{ + public AssemblyMappingProfile(Assembly assembly) => + ApplyMappingsFromAssembly(assembly); + + private void ApplyMappingsFromAssembly(Assembly assembly) + { + var types = assembly.GetExportedTypes() + .Where(type => type.GetInterfaces() + .Any(i => i.IsGenericType && + i.GetGenericTypeDefinition() == typeof(IMapWith<>))) + .ToList(); + + foreach (var type in types) + { + var instance = Activator.CreateInstance(type); + var methodInfo = type.GetMethod("Mapping"); + methodInfo?.Invoke(instance, new[] { this }); + } + } +} \ No newline at end of file diff --git a/Application/Common/Mappings/IMapWith.cs b/Application/Common/Mappings/IMapWith.cs new file mode 100644 index 0000000..390e4e0 --- /dev/null +++ b/Application/Common/Mappings/IMapWith.cs @@ -0,0 +1,9 @@ +using AutoMapper; + +namespace Mirea.Api.DataAccess.Application.Common.Mappings; + +public interface IMapWith +{ + void Mapping(Profile profile) => + profile.CreateMap(typeof(T), GetType()); +} \ No newline at end of file diff --git a/Application/DependencyInjection.cs b/Application/DependencyInjection.cs index 2686e15..61c3eab 100644 --- a/Application/DependencyInjection.cs +++ b/Application/DependencyInjection.cs @@ -1,8 +1,8 @@ -using MediatR; using FluentValidation; +using MediatR; using Microsoft.Extensions.DependencyInjection; -using System.Reflection; using Mirea.Api.DataAccess.Application.Common.Behaviors; +using System.Reflection; namespace Mirea.Api.DataAccess.Application; -- 2.43.0 From cb55567519496edb18398d555e1d7a9873ee91b3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 16:49:44 +0300 Subject: [PATCH 009/474] feat: add project --- Backend.sln | 18 +++++++++++++++++- Persistence/Persistence.csproj | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 Persistence/Persistence.csproj diff --git a/Backend.sln b/Backend.sln index f78e7d8..383bd42 100644 --- a/Backend.sln +++ b/Backend.sln @@ -17,7 +17,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Application\Application.csproj", "{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}" + ProjectSection(ProjectDependencies) = postProject + {C27FB5CD-6A70-4FB2-847A-847B34806902} = {C27FB5CD-6A70-4FB2-847A-847B34806902} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\Persistence.csproj", "{4C1E558F-633F-438E-AC3A-61CDDED917C5}" + ProjectSection(ProjectDependencies) = postProject + {E7F0A4D4-B032-4BB9-9526-1AF688F341A4} = {E7F0A4D4-B032-4BB9-9526-1AF688F341A4} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,6 +41,14 @@ Global {F3A1D12E-F5B2-4339-9966-DBF869E78357}.Debug|Any CPU.Build.0 = Debug|Any CPU {F3A1D12E-F5B2-4339-9966-DBF869E78357}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3A1D12E-F5B2-4339-9966-DBF869E78357}.Release|Any CPU.Build.0 = Release|Any CPU + {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Release|Any CPU.Build.0 = Release|Any CPU + {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Persistence/Persistence.csproj b/Persistence/Persistence.csproj new file mode 100644 index 0000000..9cdfc62 --- /dev/null +++ b/Persistence/Persistence.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + disable + enable + Winsomnia + 1.0.0-a0 + 1.0.0.0 + 1.0.0.0 + Mirea.Api.DataAccess.Persistence + $(AssemblyName) + + + + + + + + + + + + + + + \ No newline at end of file -- 2.43.0 From 40279ab2b8ea0606a844cf29294fd25247c46711 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 16:51:57 +0300 Subject: [PATCH 010/474] feat: add a data context --- .../Contexts/Schedule/CampusDbContext.cs | 17 +++++++++++++++++ Persistence/Contexts/Schedule/DayDbContext.cs | 17 +++++++++++++++++ .../Contexts/Schedule/FacultyDbContext.cs | 17 +++++++++++++++++ Persistence/Contexts/Schedule/GroupDbContext.cs | 17 +++++++++++++++++ .../Contexts/Schedule/LectureHallDbContext.cs | 17 +++++++++++++++++ .../Contexts/Schedule/LessonDbContext.cs | 17 +++++++++++++++++ .../LessonToTypeOfOccupationDbContext.cs | 17 +++++++++++++++++ .../Contexts/Schedule/ProfessorDbContext.cs | 17 +++++++++++++++++ .../Schedule/ProfessorToLessonDbContext.cs | 17 +++++++++++++++++ .../Schedule/TypeOfOccupationDbContext.cs | 17 +++++++++++++++++ 10 files changed, 170 insertions(+) create mode 100644 Persistence/Contexts/Schedule/CampusDbContext.cs create mode 100644 Persistence/Contexts/Schedule/DayDbContext.cs create mode 100644 Persistence/Contexts/Schedule/FacultyDbContext.cs create mode 100644 Persistence/Contexts/Schedule/GroupDbContext.cs create mode 100644 Persistence/Contexts/Schedule/LectureHallDbContext.cs create mode 100644 Persistence/Contexts/Schedule/LessonDbContext.cs create mode 100644 Persistence/Contexts/Schedule/LessonToTypeOfOccupationDbContext.cs create mode 100644 Persistence/Contexts/Schedule/ProfessorDbContext.cs create mode 100644 Persistence/Contexts/Schedule/ProfessorToLessonDbContext.cs create mode 100644 Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs diff --git a/Persistence/Contexts/Schedule/CampusDbContext.cs b/Persistence/Contexts/Schedule/CampusDbContext.cs new file mode 100644 index 0000000..30a9d56 --- /dev/null +++ b/Persistence/Contexts/Schedule/CampusDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class CampusDbContext(DbContextOptions options) : DbContext(options), ICampusDbContext +{ + public DbSet Campuses { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new CampusConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/DayDbContext.cs b/Persistence/Contexts/Schedule/DayDbContext.cs new file mode 100644 index 0000000..2edbb3a --- /dev/null +++ b/Persistence/Contexts/Schedule/DayDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class DayDbContext(DbContextOptions options) : DbContext(options), IDayDbContext +{ + public DbSet Days { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new DayConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/FacultyDbContext.cs b/Persistence/Contexts/Schedule/FacultyDbContext.cs new file mode 100644 index 0000000..ab6a45c --- /dev/null +++ b/Persistence/Contexts/Schedule/FacultyDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class FacultyDbContext(DbContextOptions options) : DbContext(options), IFacultyDbContext +{ + public DbSet Faculties { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new FacultyConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/GroupDbContext.cs b/Persistence/Contexts/Schedule/GroupDbContext.cs new file mode 100644 index 0000000..5d61c70 --- /dev/null +++ b/Persistence/Contexts/Schedule/GroupDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class GroupDbContext(DbContextOptions options) : DbContext(options), IGroupDbContext +{ + public DbSet Groups { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new GroupConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/LectureHallDbContext.cs b/Persistence/Contexts/Schedule/LectureHallDbContext.cs new file mode 100644 index 0000000..67841a1 --- /dev/null +++ b/Persistence/Contexts/Schedule/LectureHallDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class LectureHallDbContext(DbContextOptions options) : DbContext(options), ILectureHallDbContext +{ + public DbSet LectureHalls { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new LectureHallConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/LessonDbContext.cs b/Persistence/Contexts/Schedule/LessonDbContext.cs new file mode 100644 index 0000000..d2dfcae --- /dev/null +++ b/Persistence/Contexts/Schedule/LessonDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class LessonDbContext(DbContextOptions options) : DbContext(options), ILessonDbContext +{ + public DbSet Lessons { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new LessonConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/LessonToTypeOfOccupationDbContext.cs b/Persistence/Contexts/Schedule/LessonToTypeOfOccupationDbContext.cs new file mode 100644 index 0000000..1aada5e --- /dev/null +++ b/Persistence/Contexts/Schedule/LessonToTypeOfOccupationDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class LessonToTypeOfOccupationDbContext(DbContextOptions options) : DbContext(options), ILessonToTypeOfOccupationDbContext +{ + public DbSet LessonToTypeOfOccupations { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new LessonToTypeOfOccupationConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/ProfessorDbContext.cs b/Persistence/Contexts/Schedule/ProfessorDbContext.cs new file mode 100644 index 0000000..fcd51c6 --- /dev/null +++ b/Persistence/Contexts/Schedule/ProfessorDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class ProfessorDbContext(DbContextOptions options) : DbContext(options), IProfessorDbContext +{ + public DbSet Professors { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new ProfessorConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/ProfessorToLessonDbContext.cs b/Persistence/Contexts/Schedule/ProfessorToLessonDbContext.cs new file mode 100644 index 0000000..1673b68 --- /dev/null +++ b/Persistence/Contexts/Schedule/ProfessorToLessonDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class ProfessorToLessonDbContext(DbContextOptions options) : DbContext(options), IProfessorToLessonDbContext +{ + public DbSet ProfessorToLessons { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new ProfessorToLessonConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs b/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs new file mode 100644 index 0000000..b47ac61 --- /dev/null +++ b/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class TypeOfOccupationDbContext(DbContextOptions options) : DbContext(options), ITypeOfOccupationDbContext +{ + public DbSet TypeOfOccupations { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new TypeOfOccupationConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file -- 2.43.0 From 84214e38cccaafecaf693a4341cfab381a919d82 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 23:20:27 +0300 Subject: [PATCH 011/474] feat: add configuration files --- .../Schedule/CampusConfiguration.cs | 20 ++++++++++ .../Schedule/DayConfiguration.cs | 32 +++++++++++++++ .../Schedule/FacultyConfiguration.cs | 24 ++++++++++++ .../Schedule/GroupConfiguration.cs | 25 ++++++++++++ .../Schedule/LectureHallConfiguration.cs | 25 ++++++++++++ .../Schedule/LessonConfiguration.cs | 20 ++++++++++ .../LessonToTypeOfOccupationConfiguration.cs | 31 +++++++++++++++ .../Schedule/ProfessorConfiguration.cs | 19 +++++++++ .../ProfessorToLessonConfiguration.cs | 39 +++++++++++++++++++ .../Schedule/TypeOfOccupationConfiguration.cs | 19 +++++++++ 10 files changed, 254 insertions(+) create mode 100644 Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/DayConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/LessonToTypeOfOccupationConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/ProfessorToLessonConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs new file mode 100644 index 0000000..410d6c2 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class CampusConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Campus)); + builder.HasKey(c => c.Id); + builder.HasIndex(c => c.Id).IsUnique(); + builder.Property(c => c.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(c => c.Address).HasColumnType("TEXT").HasMaxLength(512); + builder.Property(c => c.CodeName).HasColumnType("TEXT").IsRequired().HasMaxLength(16); + builder.Property(c => c.FullName).HasColumnType("TEXT").HasMaxLength(256); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/DayConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/DayConfiguration.cs new file mode 100644 index 0000000..486013e --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/DayConfiguration.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class DayConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Day)); + builder.HasKey(d => d.Id); + builder.HasIndex(d => d.Id).IsUnique(); + builder.Property(d => d.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(d => d.PairNumber).HasColumnType("INTEGER").IsRequired(); + builder.Property(d => d.Index).HasColumnType("INTEGER").IsRequired(); + builder.Property(d => d.GroupId).HasColumnType("INTEGER").IsRequired(); + builder.Property(d => d.LessonId).HasColumnType("INTEGER").IsRequired(); + + builder + .HasOne(d => d.Group) + .WithMany(g => g.Days) + .HasForeignKey(d => d.GroupId) + .OnDelete(DeleteBehavior.Restrict); + builder + .HasOne(d => d.Lesson) + .WithOne(l => l.Day) + .HasForeignKey(d => d.LessonId) + .OnDelete(DeleteBehavior.Restrict); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs new file mode 100644 index 0000000..fccac7f --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class FacultyConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Faculty)); + builder.HasKey(f => f.Id); + builder.HasIndex(f => f.Id).IsUnique(); + builder.Property(f => f.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(f => f.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(256); + + builder + .HasOne(f => f.Campus) + .WithMany(c => c.Faculties) + .HasForeignKey(c => c.CampusId) + .OnDelete(DeleteBehavior.SetNull); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs new file mode 100644 index 0000000..0035390 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class GroupConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Group)); + builder.HasKey(g => g.Id); + builder.HasIndex(g => g.Id).IsUnique(); + builder.Property(g => g.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(g => g.FacultyId).HasColumnType("INTEGER").IsRequired(); + builder.Property(g => g.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(64); + + builder + .HasOne(g => g.Faculty) + .WithMany(u => u.Groups) + .HasForeignKey(d => d.FacultyId) + .OnDelete(DeleteBehavior.Restrict); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs new file mode 100644 index 0000000..592baa3 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class LectureHallConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(LectureHall)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.CampusId).HasColumnType("INTEGER").IsRequired(); + builder.Property(l => l.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(64); + + builder + .HasOne(l => l.Campus) + .WithMany(c => c.LectureHalls) + .HasForeignKey(d => d.CampusId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs new file mode 100644 index 0000000..82885d4 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class LessonConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Lesson)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.Discipline).HasColumnType("TEXT").IsRequired().HasMaxLength(256); + + builder.Property(l => l.IsEven).HasColumnType("BIT").IsRequired(); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonToTypeOfOccupationConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LessonToTypeOfOccupationConfiguration.cs new file mode 100644 index 0000000..dfe7010 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/LessonToTypeOfOccupationConfiguration.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class LessonToTypeOfOccupationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(LessonToTypeOfOccupation)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.LessonId).HasColumnType("INTEGER").IsRequired(); + builder.Property(l => l.TypeOfOccupationId).HasColumnType("INTEGER").IsRequired(); + + builder + .HasOne(l => l.Lesson) + .WithMany(l => l.TypeOfOccupations) + .HasForeignKey(l => l.LessonId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(l => l.TypeOfOccupation) + .WithMany(t => t.Lessons) + .HasForeignKey(l => l.TypeOfOccupationId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs new file mode 100644 index 0000000..7b95ab8 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class ProfessorConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Professor)); + builder.HasKey(p => p.Id); + builder.HasIndex(p => p.Id).IsUnique(); + builder.Property(p => p.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(p => p.Name).HasColumnType("TEXT").IsRequired(); + builder.Property(p => p.AltName).HasColumnType("TEXT"); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/ProfessorToLessonConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/ProfessorToLessonConfiguration.cs new file mode 100644 index 0000000..02ab493 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/ProfessorToLessonConfiguration.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class ProfessorToLessonConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(ProfessorToLesson)); + builder.HasKey(p => p.Id); + builder.HasIndex(p => p.Id).IsUnique(); + builder.Property(p => p.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.LinkToMeet).HasColumnType("TEXT").HasMaxLength(512); + builder.Property(p => p.LessonId).HasColumnType("INTEGER").IsRequired(); + builder.Property(p => p.ProfessorId).HasColumnType("INTEGER"); + builder.Property(l => l.LectureHallId).HasColumnType("INTEGER"); + + builder + .HasOne(p => p.LectureHall) + .WithMany(l => l.ProfessorToLessons) + .HasForeignKey(l => l.LectureHallId) + .OnDelete(DeleteBehavior.SetNull); + + builder + .HasOne(p => p.Lesson) + .WithMany(l => l.ProfessorToLesson) + .HasForeignKey(p => p.LessonId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(p => p.Professor) + .WithMany(p => p.ProfessorToLesson) + .HasForeignKey(p => p.ProfessorId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs new file mode 100644 index 0000000..24f3b55 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class TypeOfOccupationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(TypeOfOccupation)); + builder.HasKey(t => t.Id); + builder.HasIndex(t => t.Id).IsUnique(); + builder.Property(t => t.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(t => t.FullName).HasColumnType("TEXT").HasMaxLength(64); + builder.Property(t => t.ShortName).HasColumnType("TEXT").IsRequired().HasMaxLength(16); + } +} \ No newline at end of file -- 2.43.0 From f9a04ee84a958da09065a3318b1b8e489b0cd832 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 23:21:04 +0300 Subject: [PATCH 012/474] feat: add uber context for creating all context in one db --- Persistence/UberDbContext.cs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Persistence/UberDbContext.cs diff --git a/Persistence/UberDbContext.cs b/Persistence/UberDbContext.cs new file mode 100644 index 0000000..17a4c52 --- /dev/null +++ b/Persistence/UberDbContext.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence; + +public class UberDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Days { get; set; } = null!; + public DbSet Groups { get; set; } = null!; + public DbSet LectureHalls { get; set; } = null!; + public DbSet Lessons { get; set; } = null!; + public DbSet Professors { get; set; } = null!; + public DbSet TypeOfOccupations { get; set; } = null!; + public DbSet Faculties { get; set; } = null!; + public DbSet Campuses { get; set; } = null!; + public DbSet ProfessorToLessons { get; set; } = null!; + public DbSet LessonToTypeOfOccupations { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new DayConfiguration()); + modelBuilder.ApplyConfiguration(new GroupConfiguration()); + modelBuilder.ApplyConfiguration(new LectureHallConfiguration()); + modelBuilder.ApplyConfiguration(new LessonConfiguration()); + modelBuilder.ApplyConfiguration(new ProfessorConfiguration()); + modelBuilder.ApplyConfiguration(new TypeOfOccupationConfiguration()); + modelBuilder.ApplyConfiguration(new FacultyConfiguration()); + modelBuilder.ApplyConfiguration(new CampusConfiguration()); + modelBuilder.ApplyConfiguration(new ProfessorToLessonConfiguration()); + modelBuilder.ApplyConfiguration(new LessonToTypeOfOccupationConfiguration()); + + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file -- 2.43.0 From b4bbc413f261c6e12b6e1f4de78b5a9e4367721c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jan 2024 23:25:50 +0300 Subject: [PATCH 013/474] ci: add test pipeline --- .gitea/workflows/test.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .gitea/workflows/test.yaml diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml new file mode 100644 index 0000000..49484d4 --- /dev/null +++ b/.gitea/workflows/test.yaml @@ -0,0 +1,26 @@ +name: .NET Test Pipeline + +on: + pull_request: + branches: [master, 'release/*'] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build the solution + run: dotnet build --configuration Release + + - name: Run tests + run: dotnet test --configuration Release --no-build --no-restore --verbosity normal \ No newline at end of file -- 2.43.0 From 96a120f017c263bb1409bacca8f5d8df098baa3f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:36:13 +0300 Subject: [PATCH 014/474] refator: combine day and lesson --- Domain/Schedule/Day.cs | 15 --------------- Domain/Schedule/Lesson.cs | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 20 deletions(-) delete mode 100644 Domain/Schedule/Day.cs diff --git a/Domain/Schedule/Day.cs b/Domain/Schedule/Day.cs deleted file mode 100644 index 0da9657..0000000 --- a/Domain/Schedule/Day.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Mirea.Api.DataAccess.Domain.Schedule; - -public class Day -{ - public int Id { get; set; } - public DayOfWeek Index { get; set; } - public int PairNumber { get; set; } - - public int LessonId { get; set; } - public Lesson? Lesson { get; set; } - public int GroupId { get; set; } - public Group? Group { get; set; } -} \ No newline at end of file diff --git a/Domain/Schedule/Lesson.cs b/Domain/Schedule/Lesson.cs index 0851f0e..3ff5e52 100644 --- a/Domain/Schedule/Lesson.cs +++ b/Domain/Schedule/Lesson.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Mirea.Api.DataAccess.Domain.Schedule; @@ -6,9 +7,15 @@ public class Lesson { public int Id { get; set; } public bool IsEven { get; set; } - public required string Discipline { get; set; } + public DayOfWeek DayOfWeek { get; set; } + public int PairNumber { get; set; } - public List? TypeOfOccupations { get; set; } - public List? ProfessorToLesson { get; set; } - public Day? Day { get; set; } + public int GroupId { get; set; } + public Group? Group { get; set; } + public int TypeOfOccupationId { get; set; } + public TypeOfOccupation? TypeOfOccupation { get; set; } + public int DisciplineId { get; set; } + public Discipline? Discipline { get; set; } + + public List? LessonAssociations { get; set; } } \ No newline at end of file -- 2.43.0 From 03ccec2119a4f447a46946bf146cdebbd8652fcf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:37:28 +0300 Subject: [PATCH 015/474] refactor: create a discipline --- Domain/Schedule/Discipline.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Domain/Schedule/Discipline.cs diff --git a/Domain/Schedule/Discipline.cs b/Domain/Schedule/Discipline.cs new file mode 100644 index 0000000..1cdfec4 --- /dev/null +++ b/Domain/Schedule/Discipline.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class Discipline +{ + public int Id { get; set; } + public required string Name { get; set; } + + public List? Lessons { get; set; } +} \ No newline at end of file -- 2.43.0 From 9e4320f2d363889ef20182c3e6ba6a4145da5a39 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:38:26 +0300 Subject: [PATCH 016/474] refactor: give the exact name --- Domain/Schedule/{ProfessorToLesson.cs => LessonAssociation.cs} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename Domain/Schedule/{ProfessorToLesson.cs => LessonAssociation.cs} (92%) diff --git a/Domain/Schedule/ProfessorToLesson.cs b/Domain/Schedule/LessonAssociation.cs similarity index 92% rename from Domain/Schedule/ProfessorToLesson.cs rename to Domain/Schedule/LessonAssociation.cs index 17b287e..89b5651 100644 --- a/Domain/Schedule/ProfessorToLesson.cs +++ b/Domain/Schedule/LessonAssociation.cs @@ -1,11 +1,10 @@ namespace Mirea.Api.DataAccess.Domain.Schedule; -public class ProfessorToLesson +public class LessonAssociation { public int Id { get; set; } public string? LinkToMeet { get; set; } - public int LessonId { get; set; } public Lesson? Lesson { get; set; } public int? ProfessorId { get; set; } -- 2.43.0 From 28e862c670e6825cb417f0e471539270279862eb Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:38:55 +0300 Subject: [PATCH 017/474] refactor: delete due to merging with lesson --- Domain/Schedule/LessonToTypeOfOccupation.cs | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 Domain/Schedule/LessonToTypeOfOccupation.cs diff --git a/Domain/Schedule/LessonToTypeOfOccupation.cs b/Domain/Schedule/LessonToTypeOfOccupation.cs deleted file mode 100644 index c14e697..0000000 --- a/Domain/Schedule/LessonToTypeOfOccupation.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Mirea.Api.DataAccess.Domain.Schedule; - -public class LessonToTypeOfOccupation -{ - public int Id { get; set; } - - public int LessonId { get; set; } - public Lesson? Lesson { get; set; } - public int TypeOfOccupationId { get; set; } - public TypeOfOccupation? TypeOfOccupation { get; set; } -} \ No newline at end of file -- 2.43.0 From bd3a11486d478087fb770b46596afb28babef1a7 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:39:47 +0300 Subject: [PATCH 018/474] refactor: correct new reference --- Domain/Schedule/Faculty.cs | 1 + Domain/Schedule/Group.cs | 3 ++- Domain/Schedule/LectureHall.cs | 3 ++- Domain/Schedule/Professor.cs | 2 +- Domain/Schedule/TypeOfOccupation.cs | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Domain/Schedule/Faculty.cs b/Domain/Schedule/Faculty.cs index 12dbcbe..cf87fa7 100644 --- a/Domain/Schedule/Faculty.cs +++ b/Domain/Schedule/Faculty.cs @@ -9,5 +9,6 @@ public class Faculty public int? CampusId { get; set; } public Campus? Campus { get; set; } + public List? Groups { get; set; } } \ No newline at end of file diff --git a/Domain/Schedule/Group.cs b/Domain/Schedule/Group.cs index 44c6b95..a27a819 100644 --- a/Domain/Schedule/Group.cs +++ b/Domain/Schedule/Group.cs @@ -9,5 +9,6 @@ public class Group public int? FacultyId { get; set; } public Faculty? Faculty { get; set; } - public List? Days { get; set; } + + public List? Lessons { get; set; } } \ No newline at end of file diff --git a/Domain/Schedule/LectureHall.cs b/Domain/Schedule/LectureHall.cs index 70b7249..855dc8c 100644 --- a/Domain/Schedule/LectureHall.cs +++ b/Domain/Schedule/LectureHall.cs @@ -7,7 +7,8 @@ public class LectureHall public int Id { get; set; } public required string Name { get; set; } - public List? ProfessorToLessons { get; set; } public int CampusId { get; set; } public Campus? Campus { get; set; } + + public List? LessonAssociations { get; set; } } \ No newline at end of file diff --git a/Domain/Schedule/Professor.cs b/Domain/Schedule/Professor.cs index 09309f8..0b0d710 100644 --- a/Domain/Schedule/Professor.cs +++ b/Domain/Schedule/Professor.cs @@ -8,5 +8,5 @@ public class Professor public required string Name { get; set; } public string? AltName { get; set; } - public List? ProfessorToLesson { get; set; } + public List? LessonAssociations { get; set; } } \ No newline at end of file diff --git a/Domain/Schedule/TypeOfOccupation.cs b/Domain/Schedule/TypeOfOccupation.cs index f7aeeb3..a2152a8 100644 --- a/Domain/Schedule/TypeOfOccupation.cs +++ b/Domain/Schedule/TypeOfOccupation.cs @@ -8,5 +8,5 @@ public class TypeOfOccupation public required string ShortName { get; set; } public string? FullName { get; set; } - public List? Lessons { get; set; } + public List? Lessons { get; set; } } \ No newline at end of file -- 2.43.0 From e7c05b4a68753608f541cce86802b4aa7bb9ee55 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:43:27 +0300 Subject: [PATCH 019/474] refactor: fix database contexts --- ...DayDbContext.cs => IDisciplineDbContext.cs} | 4 ++-- ...ntext.cs => ILessonAssociationDbContext.cs} | 4 ++-- .../ILessonToTypeOfOccupationDbContext.cs | 9 --------- ...{DayDbContext.cs => DisciplineDbContext.cs} | 6 +++--- ...ontext.cs => LessonAssociationDbContext.cs} | 6 +++--- .../LessonToTypeOfOccupationDbContext.cs | 17 ----------------- Persistence/UberDbContext.cs | 18 ++++++++---------- 7 files changed, 18 insertions(+), 46 deletions(-) rename Application/Interfaces/DbContexts/Schedule/{IDayDbContext.cs => IDisciplineDbContext.cs} (61%) rename Application/Interfaces/DbContexts/Schedule/{IProfessorToLessonDbContext.cs => ILessonAssociationDbContext.cs} (56%) delete mode 100644 Application/Interfaces/DbContexts/Schedule/ILessonToTypeOfOccupationDbContext.cs rename Persistence/Contexts/Schedule/{DayDbContext.cs => DisciplineDbContext.cs} (61%) rename Persistence/Contexts/Schedule/{ProfessorToLessonDbContext.cs => LessonAssociationDbContext.cs} (62%) delete mode 100644 Persistence/Contexts/Schedule/LessonToTypeOfOccupationDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/IDayDbContext.cs b/Application/Interfaces/DbContexts/Schedule/IDisciplineDbContext.cs similarity index 61% rename from Application/Interfaces/DbContexts/Schedule/IDayDbContext.cs rename to Application/Interfaces/DbContexts/Schedule/IDisciplineDbContext.cs index f34d77a..6b30d92 100644 --- a/Application/Interfaces/DbContexts/Schedule/IDayDbContext.cs +++ b/Application/Interfaces/DbContexts/Schedule/IDisciplineDbContext.cs @@ -3,7 +3,7 @@ using Mirea.Api.DataAccess.Domain.Schedule; namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; -public interface IDayDbContext : IDbContextBase +public interface IDisciplineDbContext : IDbContextBase { - DbSet Days { get; set; } + DbSet Disciplines { get; set; } } \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/IProfessorToLessonDbContext.cs b/Application/Interfaces/DbContexts/Schedule/ILessonAssociationDbContext.cs similarity index 56% rename from Application/Interfaces/DbContexts/Schedule/IProfessorToLessonDbContext.cs rename to Application/Interfaces/DbContexts/Schedule/ILessonAssociationDbContext.cs index ba6aba5..2ec1af2 100644 --- a/Application/Interfaces/DbContexts/Schedule/IProfessorToLessonDbContext.cs +++ b/Application/Interfaces/DbContexts/Schedule/ILessonAssociationDbContext.cs @@ -3,7 +3,7 @@ using Mirea.Api.DataAccess.Domain.Schedule; namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; -public interface IProfessorToLessonDbContext : IDbContextBase +public interface ILessonAssociationDbContext : IDbContextBase { - DbSet ProfessorToLessons { get; set; } + DbSet LessonAssociations { get; set; } } \ No newline at end of file diff --git a/Application/Interfaces/DbContexts/Schedule/ILessonToTypeOfOccupationDbContext.cs b/Application/Interfaces/DbContexts/Schedule/ILessonToTypeOfOccupationDbContext.cs deleted file mode 100644 index 0dacc7e..0000000 --- a/Application/Interfaces/DbContexts/Schedule/ILessonToTypeOfOccupationDbContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Mirea.Api.DataAccess.Domain.Schedule; - -namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; - -public interface ILessonToTypeOfOccupationDbContext : IDbContextBase -{ - DbSet LessonToTypeOfOccupations { get; set; } -} \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/DayDbContext.cs b/Persistence/Contexts/Schedule/DisciplineDbContext.cs similarity index 61% rename from Persistence/Contexts/Schedule/DayDbContext.cs rename to Persistence/Contexts/Schedule/DisciplineDbContext.cs index 2edbb3a..d0a5612 100644 --- a/Persistence/Contexts/Schedule/DayDbContext.cs +++ b/Persistence/Contexts/Schedule/DisciplineDbContext.cs @@ -5,13 +5,13 @@ using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class DayDbContext(DbContextOptions options) : DbContext(options), IDayDbContext +public class DisciplineDbContext(DbContextOptions options) : DbContext(options), IDisciplineDbContext { - public DbSet Days { get; set; } = null!; + public DbSet Disciplines { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new DayConfiguration()); + modelBuilder.ApplyConfiguration(new DisciplineConfiguration()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/ProfessorToLessonDbContext.cs b/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs similarity index 62% rename from Persistence/Contexts/Schedule/ProfessorToLessonDbContext.cs rename to Persistence/Contexts/Schedule/LessonAssociationDbContext.cs index 1673b68..773bc8a 100644 --- a/Persistence/Contexts/Schedule/ProfessorToLessonDbContext.cs +++ b/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs @@ -5,13 +5,13 @@ using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class ProfessorToLessonDbContext(DbContextOptions options) : DbContext(options), IProfessorToLessonDbContext +public class LessonAssociationDbContext(DbContextOptions options) : DbContext(options), ILessonAssociationDbContext { - public DbSet ProfessorToLessons { get; set; } = null!; + public DbSet LessonAssociations { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new ProfessorToLessonConfiguration()); + modelBuilder.ApplyConfiguration(new LessonAssociationConfiguration()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/Persistence/Contexts/Schedule/LessonToTypeOfOccupationDbContext.cs b/Persistence/Contexts/Schedule/LessonToTypeOfOccupationDbContext.cs deleted file mode 100644 index 1aada5e..0000000 --- a/Persistence/Contexts/Schedule/LessonToTypeOfOccupationDbContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; -using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; - -namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; - -public class LessonToTypeOfOccupationDbContext(DbContextOptions options) : DbContext(options), ILessonToTypeOfOccupationDbContext -{ - public DbSet LessonToTypeOfOccupations { get; set; } = null!; - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.ApplyConfiguration(new LessonToTypeOfOccupationConfiguration()); - base.OnModelCreating(modelBuilder); - } -} \ No newline at end of file diff --git a/Persistence/UberDbContext.cs b/Persistence/UberDbContext.cs index 17a4c52..699cc49 100644 --- a/Persistence/UberDbContext.cs +++ b/Persistence/UberDbContext.cs @@ -6,29 +6,27 @@ namespace Mirea.Api.DataAccess.Persistence; public class UberDbContext(DbContextOptions options) : DbContext(options) { - public DbSet Days { get; set; } = null!; + public DbSet Campuses { get; set; } = null!; + public DbSet Disciplines { get; set; } = null!; + public DbSet Faculties { get; set; } = null!; public DbSet Groups { get; set; } = null!; public DbSet LectureHalls { get; set; } = null!; + public DbSet LessonAssociations { get; set; } = null!; public DbSet Lessons { get; set; } = null!; public DbSet Professors { get; set; } = null!; public DbSet TypeOfOccupations { get; set; } = null!; - public DbSet Faculties { get; set; } = null!; - public DbSet Campuses { get; set; } = null!; - public DbSet ProfessorToLessons { get; set; } = null!; - public DbSet LessonToTypeOfOccupations { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new DayConfiguration()); + modelBuilder.ApplyConfiguration(new CampusConfiguration()); + modelBuilder.ApplyConfiguration(new DisciplineConfiguration()); + modelBuilder.ApplyConfiguration(new FacultyConfiguration()); modelBuilder.ApplyConfiguration(new GroupConfiguration()); modelBuilder.ApplyConfiguration(new LectureHallConfiguration()); modelBuilder.ApplyConfiguration(new LessonConfiguration()); modelBuilder.ApplyConfiguration(new ProfessorConfiguration()); + modelBuilder.ApplyConfiguration(new LessonAssociationConfiguration()); modelBuilder.ApplyConfiguration(new TypeOfOccupationConfiguration()); - modelBuilder.ApplyConfiguration(new FacultyConfiguration()); - modelBuilder.ApplyConfiguration(new CampusConfiguration()); - modelBuilder.ApplyConfiguration(new ProfessorToLessonConfiguration()); - modelBuilder.ApplyConfiguration(new LessonToTypeOfOccupationConfiguration()); base.OnModelCreating(modelBuilder); } -- 2.43.0 From 194dd1b7295d5383bb76451c8330aed2828e4bea Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:44:25 +0300 Subject: [PATCH 020/474] fix: data deletion --- .../EntityTypeConfigurations/Schedule/GroupConfiguration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs index 0035390..5938bb3 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs @@ -13,13 +13,13 @@ public class GroupConfiguration : IEntityTypeConfiguration builder.HasIndex(g => g.Id).IsUnique(); builder.Property(g => g.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); - builder.Property(g => g.FacultyId).HasColumnType("INTEGER").IsRequired(); + builder.Property(g => g.FacultyId).HasColumnType("INTEGER"); builder.Property(g => g.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(64); builder .HasOne(g => g.Faculty) .WithMany(u => u.Groups) .HasForeignKey(d => d.FacultyId) - .OnDelete(DeleteBehavior.Restrict); + .OnDelete(DeleteBehavior.SetNull); } } \ No newline at end of file -- 2.43.0 From 9493d4340fe8865fa8ac4a3858653822467812f0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:45:46 +0300 Subject: [PATCH 021/474] fix: do not delete lecture hall --- .../Schedule/LectureHallConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs index 592baa3..2d10805 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs @@ -20,6 +20,6 @@ public class LectureHallConfiguration : IEntityTypeConfiguration .HasOne(l => l.Campus) .WithMany(c => c.LectureHalls) .HasForeignKey(d => d.CampusId) - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Restrict); } } \ No newline at end of file -- 2.43.0 From 66dc5e3e3828e9759482338fa3ce1b880037fb45 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:46:23 +0300 Subject: [PATCH 022/474] refactor: rewrite the configuration for new tables --- .../Schedule/DayConfiguration.cs | 32 --------------- .../Schedule/DisciplineConfiguration.cs | 18 +++++++++ .../Schedule/FacultyConfiguration.cs | 2 + .../LessonAssociationConfiguration.cs | 40 +++++++++++++++++++ .../Schedule/LessonConfiguration.cs | 27 ++++++++++++- .../LessonToTypeOfOccupationConfiguration.cs | 31 -------------- .../ProfessorToLessonConfiguration.cs | 39 ------------------ .../Schedule/TypeOfOccupationConfiguration.cs | 2 +- 8 files changed, 86 insertions(+), 105 deletions(-) delete mode 100644 Persistence/EntityTypeConfigurations/Schedule/DayConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs delete mode 100644 Persistence/EntityTypeConfigurations/Schedule/LessonToTypeOfOccupationConfiguration.cs delete mode 100644 Persistence/EntityTypeConfigurations/Schedule/ProfessorToLessonConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/DayConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/DayConfiguration.cs deleted file mode 100644 index 486013e..0000000 --- a/Persistence/EntityTypeConfigurations/Schedule/DayConfiguration.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Mirea.Api.DataAccess.Domain.Schedule; - -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; - -public class DayConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable(nameof(Day)); - builder.HasKey(d => d.Id); - builder.HasIndex(d => d.Id).IsUnique(); - builder.Property(d => d.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); - - builder.Property(d => d.PairNumber).HasColumnType("INTEGER").IsRequired(); - builder.Property(d => d.Index).HasColumnType("INTEGER").IsRequired(); - builder.Property(d => d.GroupId).HasColumnType("INTEGER").IsRequired(); - builder.Property(d => d.LessonId).HasColumnType("INTEGER").IsRequired(); - - builder - .HasOne(d => d.Group) - .WithMany(g => g.Days) - .HasForeignKey(d => d.GroupId) - .OnDelete(DeleteBehavior.Restrict); - builder - .HasOne(d => d.Lesson) - .WithOne(l => l.Day) - .HasForeignKey(d => d.LessonId) - .OnDelete(DeleteBehavior.Restrict); - } -} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs new file mode 100644 index 0000000..f92ac02 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class DisciplineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Discipline)); + builder.HasKey(d => d.Id); + builder.HasIndex(d => d.Id).IsUnique(); + builder.Property(d => d.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(d => d.Name).HasColumnType("TEXT").HasMaxLength(256).IsRequired(); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs index fccac7f..d61fce5 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs @@ -15,6 +15,8 @@ public class FacultyConfiguration : IEntityTypeConfiguration builder.Property(f => f.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(256); + builder.Property(f => f.CampusId).HasColumnType("INTEGER"); + builder .HasOne(f => f.Campus) .WithMany(c => c.Faculties) diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs new file mode 100644 index 0000000..44152dd --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class LessonAssociationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(LessonAssociation)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.LinkToMeet).HasColumnType("TEXT").HasMaxLength(512); + + builder.Property(l => l.LessonId).HasColumnType("INTEGER").IsRequired(); + builder.Property(l => l.ProfessorId).HasColumnType("INTEGER"); + builder.Property(l => l.LectureHallId).HasColumnType("INTEGER"); + + builder + .HasOne(l => l.Lesson) + .WithMany(d => d.LessonAssociations) + .HasForeignKey(l => l.LessonId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(l => l.Professor) + .WithMany(p => p.LessonAssociations) + .HasForeignKey(l => l.ProfessorId) + .OnDelete(DeleteBehavior.SetNull); + + builder + .HasOne(l => l.LectureHall) + .WithMany(l => l.LessonAssociations) + .HasForeignKey(l => l.LectureHallId) + .OnDelete(DeleteBehavior.SetNull); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs index 82885d4..a3e878a 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs @@ -13,8 +13,31 @@ public class LessonConfiguration : IEntityTypeConfiguration builder.HasIndex(l => l.Id).IsUnique(); builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); - builder.Property(l => l.Discipline).HasColumnType("TEXT").IsRequired().HasMaxLength(256); - builder.Property(l => l.IsEven).HasColumnType("BIT").IsRequired(); + builder.Property(l => l.DayOfWeek).HasColumnType("INTEGER").IsRequired(); + builder.Property(l => l.PairNumber).HasColumnType("INTEGER").IsRequired(); + + builder.Property(l => l.GroupId).HasColumnType("INTEGER").IsRequired(); + builder.Property(l => l.TypeOfOccupationId).HasColumnType("INTEGER").IsRequired(); + builder.Property(l => l.DisciplineId).HasColumnType("INTEGER").IsRequired(); + + + builder + .HasOne(l => l.Group) + .WithMany(g => g.Lessons) + .HasForeignKey(d => d.GroupId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(l => l.TypeOfOccupation) + .WithMany(t => t.Lessons) + .HasForeignKey(d => d.TypeOfOccupationId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(l => l.Discipline) + .WithMany(d => d.Lessons) + .HasForeignKey(l => l.DisciplineId) + .OnDelete(DeleteBehavior.Cascade); } } \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonToTypeOfOccupationConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LessonToTypeOfOccupationConfiguration.cs deleted file mode 100644 index dfe7010..0000000 --- a/Persistence/EntityTypeConfigurations/Schedule/LessonToTypeOfOccupationConfiguration.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Mirea.Api.DataAccess.Domain.Schedule; - -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; - -public class LessonToTypeOfOccupationConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable(nameof(LessonToTypeOfOccupation)); - builder.HasKey(l => l.Id); - builder.HasIndex(l => l.Id).IsUnique(); - builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); - - builder.Property(l => l.LessonId).HasColumnType("INTEGER").IsRequired(); - builder.Property(l => l.TypeOfOccupationId).HasColumnType("INTEGER").IsRequired(); - - builder - .HasOne(l => l.Lesson) - .WithMany(l => l.TypeOfOccupations) - .HasForeignKey(l => l.LessonId) - .OnDelete(DeleteBehavior.Cascade); - - builder - .HasOne(l => l.TypeOfOccupation) - .WithMany(t => t.Lessons) - .HasForeignKey(l => l.TypeOfOccupationId) - .OnDelete(DeleteBehavior.Cascade); - } -} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/ProfessorToLessonConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/ProfessorToLessonConfiguration.cs deleted file mode 100644 index 02ab493..0000000 --- a/Persistence/EntityTypeConfigurations/Schedule/ProfessorToLessonConfiguration.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Mirea.Api.DataAccess.Domain.Schedule; - -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; - -public class ProfessorToLessonConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable(nameof(ProfessorToLesson)); - builder.HasKey(p => p.Id); - builder.HasIndex(p => p.Id).IsUnique(); - builder.Property(p => p.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); - - builder.Property(l => l.LinkToMeet).HasColumnType("TEXT").HasMaxLength(512); - builder.Property(p => p.LessonId).HasColumnType("INTEGER").IsRequired(); - builder.Property(p => p.ProfessorId).HasColumnType("INTEGER"); - builder.Property(l => l.LectureHallId).HasColumnType("INTEGER"); - - builder - .HasOne(p => p.LectureHall) - .WithMany(l => l.ProfessorToLessons) - .HasForeignKey(l => l.LectureHallId) - .OnDelete(DeleteBehavior.SetNull); - - builder - .HasOne(p => p.Lesson) - .WithMany(l => l.ProfessorToLesson) - .HasForeignKey(p => p.LessonId) - .OnDelete(DeleteBehavior.Cascade); - - builder - .HasOne(p => p.Professor) - .WithMany(p => p.ProfessorToLesson) - .HasForeignKey(p => p.ProfessorId) - .OnDelete(DeleteBehavior.Cascade); - } -} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs index 24f3b55..82c0b61 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs @@ -13,7 +13,7 @@ public class TypeOfOccupationConfiguration : IEntityTypeConfiguration t.Id).IsUnique(); builder.Property(t => t.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); - builder.Property(t => t.FullName).HasColumnType("TEXT").HasMaxLength(64); builder.Property(t => t.ShortName).HasColumnType("TEXT").IsRequired().HasMaxLength(16); + builder.Property(t => t.FullName).HasColumnType("TEXT").HasMaxLength(64); } } \ No newline at end of file -- 2.43.0 From cea8a14f8b4038d0525659749bf7b1abc2dd6232 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 07:58:05 +0300 Subject: [PATCH 023/474] refactor: remove unnecessary classes --- .../Controllers/WeatherForecastController.cs | 36 ------------------- Endpoint/WeatherForecast.cs | 14 -------- 2 files changed, 50 deletions(-) delete mode 100644 Endpoint/Controllers/WeatherForecastController.cs delete mode 100644 Endpoint/WeatherForecast.cs diff --git a/Endpoint/Controllers/WeatherForecastController.cs b/Endpoint/Controllers/WeatherForecastController.cs deleted file mode 100644 index 80c457d..0000000 --- a/Endpoint/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Mirea.Api.Endpoint.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} \ No newline at end of file diff --git a/Endpoint/WeatherForecast.cs b/Endpoint/WeatherForecast.cs deleted file mode 100644 index 794bf3b..0000000 --- a/Endpoint/WeatherForecast.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Mirea.Api.Endpoint; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} \ No newline at end of file -- 2.43.0 From e6c62eae0954808bdcfd418c3c2175b3eb652047 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 08:44:48 +0300 Subject: [PATCH 024/474] feat: add swagger configuration --- Endpoint/ConfigureSwaggerOptions.cs | 36 +++++++++++++++++++ Endpoint/SwaggerDefaultValues.cs | 55 +++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 Endpoint/ConfigureSwaggerOptions.cs create mode 100644 Endpoint/SwaggerDefaultValues.cs diff --git a/Endpoint/ConfigureSwaggerOptions.cs b/Endpoint/ConfigureSwaggerOptions.cs new file mode 100644 index 0000000..9b53140 --- /dev/null +++ b/Endpoint/ConfigureSwaggerOptions.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; + +namespace Mirea.Api.Endpoint; + +public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions +{ + public void Configure(SwaggerGenOptions options) + { + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); + } + } + + private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) + { + var info = new OpenApiInfo() + { + Title = "MIREA Schedule Web API", + Version = description.ApiVersion.ToString(), + Description = "This API provides a convenient interface for retrieving data stored in the database. Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.", + Contact = new OpenApiContact { Name = "Author name", Email = "support@winsomnia.net" }, + License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") } + }; + + if (description.IsDeprecated) + info.Description += " This API version has been deprecated."; + + return info; + } +} \ No newline at end of file diff --git a/Endpoint/SwaggerDefaultValues.cs b/Endpoint/SwaggerDefaultValues.cs new file mode 100644 index 0000000..d47c591 --- /dev/null +++ b/Endpoint/SwaggerDefaultValues.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Linq; +using System.Text.Json; + +namespace Mirea.Api.Endpoint; + +public class SwaggerDefaultValues : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var apiDescription = context.ApiDescription; + operation.Deprecated |= apiDescription.IsDeprecated(); + + foreach (var responseType in context.ApiDescription.SupportedResponseTypes) + { + var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); + var response = operation.Responses[responseKey]; + + foreach (var contentType in response.Content.Keys) + { + if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType)) + { + response.Content.Remove(contentType); + } + } + } + + if (operation.Parameters == null) + { + return; + } + + foreach (var parameter in operation.Parameters) + { + var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + parameter.Description ??= description.ModelMetadata?.Description; + + if (parameter.Schema.Default == null && + description.DefaultValue != null && + description.DefaultValue is not DBNull && + description.ModelMetadata is ModelMetadata modelMetadata) + { + var json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType); + parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json); + } + + parameter.Required |= description.IsRequired; + } + } +} \ No newline at end of file -- 2.43.0 From e1c3165ad3922b3467c78392234be7ffdf4902c1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 08:50:10 +0300 Subject: [PATCH 025/474] build: add a dependency on versioning --- Endpoint/Endpoint.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 077a905..4b68f1c 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -20,6 +20,7 @@ + \ No newline at end of file -- 2.43.0 From 3c3bdc615554bb838632b27eda6519658f0678f5 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 09:00:14 +0300 Subject: [PATCH 026/474] feat: add the pre-needed server settings --- Endpoint/Properties/Settings.cs | 36 ++++++++++++++++++++++++++++ Persistence/Properties/DbSettings.cs | 15 ++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 Endpoint/Properties/Settings.cs create mode 100644 Persistence/Properties/DbSettings.cs diff --git a/Endpoint/Properties/Settings.cs b/Endpoint/Properties/Settings.cs new file mode 100644 index 0000000..1bee892 --- /dev/null +++ b/Endpoint/Properties/Settings.cs @@ -0,0 +1,36 @@ +using Mirea.Api.DataAccess.Persistence.Properties; + +namespace Mirea.Api.Endpoint.Properties; + +public class EmailSettings +{ + public string? Server { get; set; } + public string? User { get; set; } + public string? Password { get; set; } + public string? From { get; set; } + public int? Port { get; set; } + public bool? Ssl { get; set; } +} + +public class LogSettings +{ + public bool EnableLogToFile { get; set; } + public string? LogFilePath { get; set; } + public string? LogFileName { get; set; } +} + +public class ScheduleSettings +{ + // Every 6 hours + public string CronUpdateSchedule { get; set; } = "0 0 0/6 * * *"; +} + +public class Settings +{ + public const string FilePath = "Settings.json"; + + public EmailSettings? EmailSettings { get; set; } + public LogSettings? LogSettings { get; set; } + public DbSettings? DbSettings { get; set; } + public ScheduleSettings? ScheduleSettings { get; set; } +} \ No newline at end of file diff --git a/Persistence/Properties/DbSettings.cs b/Persistence/Properties/DbSettings.cs new file mode 100644 index 0000000..7ee06d9 --- /dev/null +++ b/Persistence/Properties/DbSettings.cs @@ -0,0 +1,15 @@ +namespace Mirea.Api.DataAccess.Persistence.Properties; + +public enum DatabaseEnum +{ + Mysql, + Sqlite, + PostgresSql +} +public class DbSettings +{ + public bool IsDoneConfiguration { get; set; } + public DatabaseEnum TypeDatabase { get; set; } + public required string ConnectionStringSql { get; set; } + public DatabaseEnum? MigrateTo { get; set; } +} \ No newline at end of file -- 2.43.0 From 8f334ae5c2a2fb4a50023fdd94cef5efb499d8ca Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:29:08 +0300 Subject: [PATCH 027/474] feat: add reading from .env file --- Endpoint/EnvironmentManager.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Endpoint/EnvironmentManager.cs diff --git a/Endpoint/EnvironmentManager.cs b/Endpoint/EnvironmentManager.cs new file mode 100644 index 0000000..3cbe859 --- /dev/null +++ b/Endpoint/EnvironmentManager.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; + +namespace Mirea.Api.Endpoint; + +internal static class EnvironmentManager +{ + public static void LoadEnvironment(string filePath) + { + if (!File.Exists(filePath)) return; + + foreach (var line in File.ReadAllLines(filePath)) + { + var parts = line.Split( + '=', + StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length != 2) + continue; + + Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1][..(parts[1].Contains('#') ? parts[1].IndexOf('#') : parts[1].Length)].Trim()); + } + } +} \ No newline at end of file -- 2.43.0 From e6cc9437d587cd2e43f785066961123669e01b1e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:30:41 +0300 Subject: [PATCH 028/474] feat: add additional configuration from files --- Endpoint/Program.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index c864599..4e5d4c6 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -6,9 +6,26 @@ namespace Mirea.Api.Endpoint; public class Program { + private static IConfigurationRoot ConfigureEnvironment() + { + EnvironmentManager.LoadEnvironment(".env"); + var environmentVariables = Environment.GetEnvironmentVariables() + .OfType() + .ToDictionary( + entry => entry.Key.ToString() ?? string.Empty, + entry => entry.Value?.ToString() ?? string.Empty + ); + + var result = new ConfigurationBuilder().AddInMemoryCollection(environmentVariables!); + + return result.Build(); + } + public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); + builder.Configuration.AddConfiguration(ConfigureEnvironment()); + builder.Configuration.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true); // Add services to the container. -- 2.43.0 From 788103c8a55ac756fc3048584b25eca2d0c30ac9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:31:28 +0300 Subject: [PATCH 029/474] fix: specify the current directory as a startup directory --- Endpoint/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 4e5d4c6..b0b9817 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -23,6 +23,8 @@ public class Program public static void Main(string[] args) { + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddConfiguration(ConfigureEnvironment()); builder.Configuration.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true); -- 2.43.0 From d5b3859e8677d44a8d4719abcbd8d4e7e08fc279 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:32:10 +0300 Subject: [PATCH 030/474] feat: add a CORS configuration --- Endpoint/Program.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index b0b9817..a9156f9 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -30,9 +30,17 @@ public class Program builder.Configuration.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true); // Add services to the container. - builder.Services.AddControllers(); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + + builder.Services.AddCors(options => + { + options.AddPolicy("AllowAll", policy => + { + policy.AllowAnyHeader(); + policy.AllowAnyMethod(); + policy.AllowAnyOrigin(); + }); + }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -- 2.43.0 From f1ed45af96c7c92fea699a4e17e44cc1cc411bc4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:33:25 +0300 Subject: [PATCH 031/474] feat: add API versioning --- Endpoint/Program.cs | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index a9156f9..334eccc 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -41,8 +41,33 @@ public class Program policy.AllowAnyOrigin(); }); }); + + builder.Services.AddApiVersioning(options => + { + options.DefaultApiVersion = new ApiVersion(1, 0); + options.AssumeDefaultVersionWhenUnspecified = true; + options.ReportApiVersions = true; + options.ApiVersionReader = new UrlSegmentApiVersionReader(); + }); + + builder.Services.AddVersionedApiExplorer(options => + { + options.GroupNameFormat = "'v'VVV"; + options.SubstituteApiVersionInUrl = true; + }); + builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); + + builder.Services.AddSwaggerGen(options => + { + options.OperationFilter(); + var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory); + + var xmlPath = Path.Combine(basePath, "docs.xml"); + options.IncludeXmlComments(xmlPath); + }); + + builder.Services.AddTransient, ConfigureSwaggerOptions>(); var app = builder.Build(); @@ -50,7 +75,17 @@ public class Program if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(options => + { + var provider = app.Services.GetService(); + + foreach (var description in provider.ApiVersionDescriptions) + { + var url = $"/swagger/{description.GroupName}/swagger.json"; + var name = description.GroupName.ToUpperInvariant(); + options.SwaggerEndpoint(url, name); + } + }); } app.UseHttpsRedirection(); -- 2.43.0 From 7b8bff8e54b11e4457ee4e2d42c1cc9347575468 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:34:42 +0300 Subject: [PATCH 032/474] build: add dependencies to the project --- Endpoint/Endpoint.csproj | 10 +++++++++- Endpoint/Program.cs | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 4b68f1c..25a94b7 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -11,10 +11,13 @@ Mirea.Api.Endpoint $(AssemblyName) Exe - true + false 65cea060-88bf-4e35-9cfb-18fc996a8f05 Linux . + False + True + docs.xml @@ -23,4 +26,9 @@ + + + + + \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 334eccc..6499332 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -1,6 +1,19 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Mirea.Api.DataAccess.Application; +using Mirea.Api.DataAccess.Persistence; +using Mirea.Api.Endpoint.Properties; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections; +using System.IO; +using System.Linq; namespace Mirea.Api.Endpoint; -- 2.43.0 From 98ebe5ffdb00b9c4ac021f32e43837c9fed43d65 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:35:45 +0300 Subject: [PATCH 033/474] feat: add configuration output to the console during debugging --- Endpoint/Program.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 6499332..34e352a 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -84,6 +84,11 @@ public class Program var app = builder.Build(); +#if DEBUG + // Write configurations + foreach (var item in app.Configuration.AsEnumerable()) + Console.WriteLine($"{item.Key}:{item.Value}"); +#endif // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { -- 2.43.0 From 2b02cfb6160b39246983f0855bd61dd15617ad4b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:36:18 +0300 Subject: [PATCH 034/474] feat: add and initialize work with the database --- Endpoint/Program.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 34e352a..f649536 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -42,7 +42,8 @@ public class Program builder.Configuration.AddConfiguration(ConfigureEnvironment()); builder.Configuration.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true); - // Add services to the container. + builder.Services.AddApplication(); + builder.Services.AddPersistence(builder.Configuration); builder.Services.AddControllers(); builder.Services.AddCors(options => @@ -89,6 +90,10 @@ public class Program foreach (var item in app.Configuration.AsEnumerable()) Console.WriteLine($"{item.Key}:{item.Value}"); #endif + + var uber = app.Services.CreateScope().ServiceProvider.GetService(); + DbInitializer.Initialize(uber!); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { -- 2.43.0 From 459d11dd6b35b2accca474ca97619dd254686545 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:37:42 +0300 Subject: [PATCH 035/474] feat: add a database creation class (initial) --- Persistence/DbInitializer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Persistence/DbInitializer.cs diff --git a/Persistence/DbInitializer.cs b/Persistence/DbInitializer.cs new file mode 100644 index 0000000..87f3a9b --- /dev/null +++ b/Persistence/DbInitializer.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; + +namespace Mirea.Api.DataAccess.Persistence; + +public static class DbInitializer +{ + public static void Initialize(DbContext dbContext) + { + dbContext.Database.EnsureCreated(); + } +} \ No newline at end of file -- 2.43.0 From 3294325ad0eb99bda1290ebec8544ccb635b99e1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:39:01 +0300 Subject: [PATCH 036/474] build: add the necessary packages --- Persistence/Persistence.csproj | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Persistence/Persistence.csproj b/Persistence/Persistence.csproj index 9cdfc62..37f0a7f 100644 --- a/Persistence/Persistence.csproj +++ b/Persistence/Persistence.csproj @@ -13,8 +13,9 @@ - - + + + @@ -24,4 +25,8 @@ + + + + \ No newline at end of file -- 2.43.0 From 806c3eeb17d403fa27970fa68b10996a4930af98 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 19:39:59 +0300 Subject: [PATCH 037/474] feat: add an initial DI container for working with Persistence --- Persistence/DependencyInjection.cs | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Persistence/DependencyInjection.cs diff --git a/Persistence/DependencyInjection.cs b/Persistence/DependencyInjection.cs new file mode 100644 index 0000000..9ec71f1 --- /dev/null +++ b/Persistence/DependencyInjection.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Persistence.Contexts.Schedule; +using Mirea.Api.DataAccess.Persistence.Properties; +using System; +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Persistence; + +public static class DependencyInjection +{ + public static IServiceCollection AddPersistence(this IServiceCollection services, IConfiguration configuration) + { + var settings = configuration.GetSection(nameof(DbSettings)).Get(); + var connection = settings?.ConnectionStringSql; + + Dictionary> dbConfigurations = new() + { + { + DatabaseEnum.Mysql, + options => options.UseMySql(connection, ServerVersion.AutoDetect(connection)) + }, + { + DatabaseEnum.Sqlite, + options => options.UseSqlite(connection) + }, + { + DatabaseEnum.PostgresSql, + options => options.UseNpgsql(connection) + } + }; + + if (dbConfigurations.TryGetValue((DatabaseEnum)settings?.TypeDatabase!, out var dbConfig)) + { + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + + services.AddDbContext(dbConfig); + } + else + throw new NotSupportedException("Unsupported database type"); + + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + + return services; + } +} \ No newline at end of file -- 2.43.0 From c5c4a2a8dace0ad23bc73795ba95404fb5ad674b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 20:38:13 +0300 Subject: [PATCH 038/474] fix: add ignoring documentation warning --- Endpoint/Endpoint.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 25a94b7..84dcfc2 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -18,6 +18,7 @@ False True docs.xml + $(NoWarn);1591 -- 2.43.0 From 92504295f076f01fd8e7f6bc1225114c9c423642 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 26 Jan 2024 20:38:49 +0300 Subject: [PATCH 039/474] fix: add null ignoring --- Endpoint/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index f649536..920ce15 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -102,7 +102,7 @@ public class Program { var provider = app.Services.GetService(); - foreach (var description in provider.ApiVersionDescriptions) + foreach (var description in provider!.ApiVersionDescriptions) { var url = $"/swagger/{description.GroupName}/swagger.json"; var name = description.GroupName.ToUpperInvariant(); -- 2.43.0 From f25c815506e9af00b5ecc2ff031e929b7f4957d3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:30:25 +0300 Subject: [PATCH 040/474] feat: add a not found exception --- Application/Common/Exceptions/NotFoundException.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Application/Common/Exceptions/NotFoundException.cs diff --git a/Application/Common/Exceptions/NotFoundException.cs b/Application/Common/Exceptions/NotFoundException.cs new file mode 100644 index 0000000..7cacf74 --- /dev/null +++ b/Application/Common/Exceptions/NotFoundException.cs @@ -0,0 +1,10 @@ +using System.Reflection; +using System; + +namespace Mirea.Api.DataAccess.Application.Common.Exceptions; + +public class NotFoundException : Exception +{ + public NotFoundException(MemberInfo entity, string name, object id) : base($"The entity \"{entity.Name}\" property \"{name}\" was not found by id \"{id}\".") { } + public NotFoundException(MemberInfo entity, object id) : base($"The entity \"{entity.Name}\" was not found by id \"{id}\".") { } +} \ No newline at end of file -- 2.43.0 From bc99007d9487a614a97d5c99d52fa3d76f1437b8 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:31:18 +0300 Subject: [PATCH 041/474] feat: add queries for the campus --- .../CampusBasicInfoDto.cs | 22 +++++++++++++++ .../CampusBasicInfoVm.cs | 14 ++++++++++ .../GetCampusBasicInfoListQuery.cs | 5 ++++ .../GetCampusBasicInfoListQueryHandler.cs | 26 ++++++++++++++++++ .../GetCampusDetails/CampusDetailsVm.cs | 27 +++++++++++++++++++ .../GetCampusDetails/GetCampusDetailsQuery.cs | 9 +++++++ .../GetCampusDetailsQueryHandler.cs | 27 +++++++++++++++++++ 7 files changed, 130 insertions(+) create mode 100644 Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs create mode 100644 Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs create mode 100644 Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs create mode 100644 Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs create mode 100644 Application/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs create mode 100644 Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs create mode 100644 Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs diff --git a/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs b/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs new file mode 100644 index 0000000..e0c9918 --- /dev/null +++ b/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; + +/// +/// Represents basic information about a campus. +/// +public class CampusBasicInfoDto +{ + /// + /// The unique identifier for the campus. + /// + public int Id { get; set; } + + /// + /// The code name associated with the campus. + /// + public required string CodeName { get; set; } + + /// + /// The full name of the campus. + /// + public string? FullName { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs b/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs new file mode 100644 index 0000000..a3a5f2d --- /dev/null +++ b/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; + +/// +/// Represents a view model containing basic information about multiple campuses. +/// +public class CampusBasicInfoVm +{ + /// + /// The list of campus basic information. + /// + public IList Campuses { get; set; } = new List(); +} \ No newline at end of file diff --git a/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs b/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs new file mode 100644 index 0000000..2059735 --- /dev/null +++ b/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; + +public class GetCampusBasicInfoListQuery : IRequest; \ No newline at end of file diff --git a/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs b/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs new file mode 100644 index 0000000..3cd6259 --- /dev/null +++ b/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs @@ -0,0 +1,26 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; + +public class GetCampusBasicInfoListQueryHandler(ICampusDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetCampusBasicInfoListQuery request, CancellationToken cancellationToken) + { + var dtos = await dbContext.Campuses.Select(c => new CampusBasicInfoDto() + { + Id = c.Id, + CodeName = c.CodeName, + FullName = c.FullName + }).ToListAsync(cancellationToken); + + return new CampusBasicInfoVm + { + Campuses = dtos + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs b/Application/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs new file mode 100644 index 0000000..35246c1 --- /dev/null +++ b/Application/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs @@ -0,0 +1,27 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusDetails; + +/// +/// Represents a view model containing detailed information about a campus. +/// +public class CampusDetailsVm +{ + /// + /// The unique identifier for the campus. + /// + public int Id { get; set; } + + /// + /// The code name associated with the campus. + /// + public required string CodeName { get; set; } + + /// + /// The full name of the campus. + /// + public string? FullName { get; set; } + + /// + /// The address of the campus. + /// + public string? Address { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs b/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs new file mode 100644 index 0000000..f64bf8b --- /dev/null +++ b/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs @@ -0,0 +1,9 @@ +using MediatR; +using Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusDetails; + +public class GetCampusDetailsQuery : IRequest +{ + public required int Id { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs b/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs new file mode 100644 index 0000000..2cb791c --- /dev/null +++ b/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs @@ -0,0 +1,27 @@ +using System.Globalization; +using System.Linq; +using MediatR; +using Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Common.Exceptions; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusDetails; + +public class GetCampusDetailsQueryHandler(ICampusDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetCampusDetailsQuery request, CancellationToken cancellationToken) + { + var campus = await dbContext.Campuses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Campus), request.Id); + + return new CampusDetailsVm() + { + Id = campus.Id, + CodeName = campus.CodeName, + FullName = campus.FullName, + Address = campus.Address + }; + } +} \ No newline at end of file -- 2.43.0 From 77136cc7ec44bae019f44a5d44da51002ee5489f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:33:53 +0300 Subject: [PATCH 042/474] feat: add an extension to simplify attributes --- .../Extensions/BadRequestResponseAttribute.cs | 8 ++++++++ Endpoint/Common/Extensions/ErrorResponseVm.cs | 18 ++++++++++++++++++ .../Extensions/NotFoundResponseAttribute.cs | 8 ++++++++ 3 files changed, 34 insertions(+) create mode 100644 Endpoint/Common/Extensions/BadRequestResponseAttribute.cs create mode 100644 Endpoint/Common/Extensions/ErrorResponseVm.cs create mode 100644 Endpoint/Common/Extensions/NotFoundResponseAttribute.cs diff --git a/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs b/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs new file mode 100644 index 0000000..171da23 --- /dev/null +++ b/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; +using System; +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Common.Extensions; + +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] +public class BadRequestResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponseVm), StatusCodes.Status400BadRequest); \ No newline at end of file diff --git a/Endpoint/Common/Extensions/ErrorResponseVm.cs b/Endpoint/Common/Extensions/ErrorResponseVm.cs new file mode 100644 index 0000000..7c4a099 --- /dev/null +++ b/Endpoint/Common/Extensions/ErrorResponseVm.cs @@ -0,0 +1,18 @@ +namespace Mirea.Api.Endpoint.Common.Extensions; + +/// +/// A class for providing information about an error +/// +public class ErrorResponseVm +{ + /// + /// 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. + /// + 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. + /// + public required int Code { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs b/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs new file mode 100644 index 0000000..15db1cc --- /dev/null +++ b/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; +using System; +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Common.Extensions; + +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] +public class NotFoundResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponseVm), StatusCodes.Status404NotFound); \ No newline at end of file -- 2.43.0 From 7b584b2dc5ebc02e2e68124e77bf5de780e7cbe7 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:34:33 +0300 Subject: [PATCH 043/474] feat: add a basic controller --- Endpoint/Controllers/BaseController.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Endpoint/Controllers/BaseController.cs diff --git a/Endpoint/Controllers/BaseController.cs b/Endpoint/Controllers/BaseController.cs new file mode 100644 index 0000000..82c4c7a --- /dev/null +++ b/Endpoint/Controllers/BaseController.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Controllers; + +[ApiController] +[Route("api/[controller]/[action]")] +public class BaseController : ControllerBase; \ No newline at end of file -- 2.43.0 From 335298fd910359fb960b55eb1f309a7059c8ba83 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:34:56 +0300 Subject: [PATCH 044/474] feat: add a basic controller for API version 1 --- Endpoint/Controllers/V1/BaseControllerV1.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Endpoint/Controllers/V1/BaseControllerV1.cs diff --git a/Endpoint/Controllers/V1/BaseControllerV1.cs b/Endpoint/Controllers/V1/BaseControllerV1.cs new file mode 100644 index 0000000..6ebe1f3 --- /dev/null +++ b/Endpoint/Controllers/V1/BaseControllerV1.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +[Produces("application/json")] +[Route("api/v{version:apiVersion}/[controller]/[action]")] +public class BaseControllerV1 : BaseController; \ No newline at end of file -- 2.43.0 From a4dfccbc229737ae4df526322342ed1ea84d768c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:33:53 +0300 Subject: [PATCH 045/474] feat: add an extension to simplify attributes --- .../Extensions/BadRequestResponseAttribute.cs | 8 ++++++++ Endpoint/Common/Extensions/ErrorResponseVm.cs | 18 ++++++++++++++++++ .../Extensions/NotFoundResponseAttribute.cs | 8 ++++++++ 3 files changed, 34 insertions(+) create mode 100644 Endpoint/Common/Extensions/BadRequestResponseAttribute.cs create mode 100644 Endpoint/Common/Extensions/ErrorResponseVm.cs create mode 100644 Endpoint/Common/Extensions/NotFoundResponseAttribute.cs diff --git a/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs b/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs new file mode 100644 index 0000000..171da23 --- /dev/null +++ b/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; +using System; +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Common.Extensions; + +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] +public class BadRequestResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponseVm), StatusCodes.Status400BadRequest); \ No newline at end of file diff --git a/Endpoint/Common/Extensions/ErrorResponseVm.cs b/Endpoint/Common/Extensions/ErrorResponseVm.cs new file mode 100644 index 0000000..7c4a099 --- /dev/null +++ b/Endpoint/Common/Extensions/ErrorResponseVm.cs @@ -0,0 +1,18 @@ +namespace Mirea.Api.Endpoint.Common.Extensions; + +/// +/// A class for providing information about an error +/// +public class ErrorResponseVm +{ + /// + /// 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. + /// + 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. + /// + public required int Code { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs b/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs new file mode 100644 index 0000000..15db1cc --- /dev/null +++ b/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; +using System; +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Common.Extensions; + +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] +public class NotFoundResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponseVm), StatusCodes.Status404NotFound); \ No newline at end of file -- 2.43.0 From fb6ca5df7715b5e95fb8c226606a2b738d7a5922 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:34:33 +0300 Subject: [PATCH 046/474] feat: add a basic controller --- Endpoint/Controllers/BaseController.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Endpoint/Controllers/BaseController.cs diff --git a/Endpoint/Controllers/BaseController.cs b/Endpoint/Controllers/BaseController.cs new file mode 100644 index 0000000..82c4c7a --- /dev/null +++ b/Endpoint/Controllers/BaseController.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Controllers; + +[ApiController] +[Route("api/[controller]/[action]")] +public class BaseController : ControllerBase; \ No newline at end of file -- 2.43.0 From 8852351ca63044f3a377acd84d4b6b5ea3d7fa96 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:34:56 +0300 Subject: [PATCH 047/474] feat: add a basic controller for API version 1 --- Endpoint/Controllers/V1/BaseControllerV1.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Endpoint/Controllers/V1/BaseControllerV1.cs diff --git a/Endpoint/Controllers/V1/BaseControllerV1.cs b/Endpoint/Controllers/V1/BaseControllerV1.cs new file mode 100644 index 0000000..6ebe1f3 --- /dev/null +++ b/Endpoint/Controllers/V1/BaseControllerV1.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +[Produces("application/json")] +[Route("api/v{version:apiVersion}/[controller]/[action]")] +public class BaseControllerV1 : BaseController; \ No newline at end of file -- 2.43.0 From a1a83ce2e6cf398922891e700c0df72312454e10 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 05:59:48 +0300 Subject: [PATCH 048/474] feat: add queries for the discipline --- .../GetDisciplineDetails/DisciplineInfoVm.cs | 17 +++++++++++ .../GetDisciplineInfoQuery.cs | 8 +++++ .../GetDisciplineInfoQueryHandler.cs | 22 ++++++++++++++ .../GetDisciplineList/DisciplineListVm.cs | 14 +++++++++ .../GetDisciplineList/DisciplineLookupDto.cs | 17 +++++++++++ .../GetDisciplineListQuery.cs | 9 ++++++ .../GetDisciplineListQueryHandler.cs | 30 +++++++++++++++++++ 7 files changed, 117 insertions(+) create mode 100644 Application/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs create mode 100644 Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs create mode 100644 Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs create mode 100644 Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs create mode 100644 Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs create mode 100644 Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs create mode 100644 Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs b/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs new file mode 100644 index 0000000..c6ac1b1 --- /dev/null +++ b/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs @@ -0,0 +1,17 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineDetails; + +/// +/// Represents disciplines. +/// +public class DisciplineInfoVm +{ + /// + /// The unique identifier for the discipline. + /// + public int Id { get; set; } + + /// + /// The name of the discipline. + /// + public required string Name { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs b/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs new file mode 100644 index 0000000..b6c0621 --- /dev/null +++ b/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineDetails; + +public class GetDisciplineInfoQuery : IRequest +{ + public required int Id { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs b/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs new file mode 100644 index 0000000..007e74f --- /dev/null +++ b/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs @@ -0,0 +1,22 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Common.Exceptions; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineDetails; + +public class GetDisciplineInfoQueryHandler(IDisciplineDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetDisciplineInfoQuery request, CancellationToken cancellationToken) + { + var discipline = await dbContext.Disciplines.FirstOrDefaultAsync(d => d.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Discipline), request.Id); + + return new DisciplineInfoVm() + { + Id = discipline.Id, + Name = discipline.Name, + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs b/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs new file mode 100644 index 0000000..3f5c835 --- /dev/null +++ b/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineList; + +/// +/// Represents a view model containing multiple disciplines name. +/// +public class DisciplineListVm +{ + /// + /// The list of disciplines. + /// + public IList Disciplines { get; set; } = new List(); +} \ No newline at end of file diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs b/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs new file mode 100644 index 0000000..0a090df --- /dev/null +++ b/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs @@ -0,0 +1,17 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineList; + +/// +/// Represents disciplines. +/// +public class DisciplineLookupDto +{ + /// + /// The unique identifier for the discipline. + /// + public int Id { get; set; } + + /// + /// The name of the discipline. + /// + public required string Name { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs b/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs new file mode 100644 index 0000000..f3b770b --- /dev/null +++ b/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineList; + +public class GetDisciplineListQuery : IRequest +{ + public int? Page { get; set; } + public int? PageSize { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs b/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs new file mode 100644 index 0000000..c76ead2 --- /dev/null +++ b/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs @@ -0,0 +1,30 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineList; + +public class GetDisciplineListQueryHandler(IDisciplineDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetDisciplineListQuery request, CancellationToken cancellationToken) + { + var dtos = dbContext.Disciplines.OrderBy(d => d.Id).Select(d => new DisciplineLookupDto() + { + Id = d.Id, + Name = d.Name, + }); + + if (request is { PageSize: not null, Page: not null }) + dtos = dtos.Skip(request.Page.Value * request.PageSize.Value).Take(request.PageSize.Value); + + var result = await dtos.ToListAsync(cancellationToken); + + return new DisciplineListVm + { + Disciplines = result + }; + } +} \ No newline at end of file -- 2.43.0 From 96a820deaddebd3a147b58ff4c505193205c067c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:33:53 +0300 Subject: [PATCH 049/474] feat: add an extension to simplify attributes --- .../Extensions/BadRequestResponseAttribute.cs | 8 ++++++++ Endpoint/Common/Extensions/ErrorResponseVm.cs | 18 ++++++++++++++++++ .../Extensions/NotFoundResponseAttribute.cs | 8 ++++++++ 3 files changed, 34 insertions(+) create mode 100644 Endpoint/Common/Extensions/BadRequestResponseAttribute.cs create mode 100644 Endpoint/Common/Extensions/ErrorResponseVm.cs create mode 100644 Endpoint/Common/Extensions/NotFoundResponseAttribute.cs diff --git a/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs b/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs new file mode 100644 index 0000000..171da23 --- /dev/null +++ b/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; +using System; +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Common.Extensions; + +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] +public class BadRequestResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponseVm), StatusCodes.Status400BadRequest); \ No newline at end of file diff --git a/Endpoint/Common/Extensions/ErrorResponseVm.cs b/Endpoint/Common/Extensions/ErrorResponseVm.cs new file mode 100644 index 0000000..7c4a099 --- /dev/null +++ b/Endpoint/Common/Extensions/ErrorResponseVm.cs @@ -0,0 +1,18 @@ +namespace Mirea.Api.Endpoint.Common.Extensions; + +/// +/// A class for providing information about an error +/// +public class ErrorResponseVm +{ + /// + /// 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. + /// + 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. + /// + public required int Code { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs b/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs new file mode 100644 index 0000000..15db1cc --- /dev/null +++ b/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; +using System; +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Common.Extensions; + +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] +public class NotFoundResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponseVm), StatusCodes.Status404NotFound); \ No newline at end of file -- 2.43.0 From 07e04b99c571755d5ac408a504dea425b46c2b20 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:34:33 +0300 Subject: [PATCH 050/474] feat: add a basic controller --- Endpoint/Controllers/BaseController.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Endpoint/Controllers/BaseController.cs diff --git a/Endpoint/Controllers/BaseController.cs b/Endpoint/Controllers/BaseController.cs new file mode 100644 index 0000000..82c4c7a --- /dev/null +++ b/Endpoint/Controllers/BaseController.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Controllers; + +[ApiController] +[Route("api/[controller]/[action]")] +public class BaseController : ControllerBase; \ No newline at end of file -- 2.43.0 From 2403cb35eca32fb006c0aebc11435427cafef5f9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 04:34:56 +0300 Subject: [PATCH 051/474] feat: add a basic controller for API version 1 --- Endpoint/Controllers/V1/BaseControllerV1.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Endpoint/Controllers/V1/BaseControllerV1.cs diff --git a/Endpoint/Controllers/V1/BaseControllerV1.cs b/Endpoint/Controllers/V1/BaseControllerV1.cs new file mode 100644 index 0000000..6ebe1f3 --- /dev/null +++ b/Endpoint/Controllers/V1/BaseControllerV1.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +[Produces("application/json")] +[Route("api/v{version:apiVersion}/[controller]/[action]")] +public class BaseControllerV1 : BaseController; \ No newline at end of file -- 2.43.0 From 88b4eb594ec7ef53e25547c0d6b7aa0c087d7469 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 06:05:27 +0300 Subject: [PATCH 052/474] feat: add a controller for discipline --- .../Controllers/V1/DisciplineController.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 Endpoint/Controllers/V1/DisciplineController.cs diff --git a/Endpoint/Controllers/V1/DisciplineController.cs b/Endpoint/Controllers/V1/DisciplineController.cs new file mode 100644 index 0000000..6589d31 --- /dev/null +++ b/Endpoint/Controllers/V1/DisciplineController.cs @@ -0,0 +1,53 @@ +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; +using Mirea.Api.Endpoint.Common.Extensions; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1 +{ + public class DisciplineController(IMediator mediator) : BaseControllerV1 + { + /// + /// Gets a paginated list of disciplines. + /// + /// Page number. Start from 0. + /// Number of items per page. + /// Paginated list of disciplines. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + public async Task> Get([FromQuery] int? page, [FromQuery] int? pageSize) + { + var result = await mediator.Send(new GetDisciplineListQuery() + { + Page = page, + PageSize = pageSize + }); + + return Ok(result); + } + + /// + /// Gets details of a specific discipline by ID. + /// + /// Discipline ID. + /// Details of the specified discipline. + /// + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetDisciplineInfoQuery() + { + Id = id + }); + + return Ok(result); + } + } +} -- 2.43.0 From dfc9caf921599d6bf074f47388221c2a26fba51d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 06:05:54 +0300 Subject: [PATCH 053/474] feat: add a controller for campus --- Endpoint/Controllers/V1/CampusController.cs | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Endpoint/Controllers/V1/CampusController.cs diff --git a/Endpoint/Controllers/V1/CampusController.cs b/Endpoint/Controllers/V1/CampusController.cs new file mode 100644 index 0000000..c383608 --- /dev/null +++ b/Endpoint/Controllers/V1/CampusController.cs @@ -0,0 +1,45 @@ +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; +using Mirea.Api.Endpoint.Common.Extensions; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1 +{ + public class CampusController(IMediator mediator) : BaseControllerV1 + { + /// + /// Gets basic information about campuses. + /// + /// Basic information about campuses. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> GetBasicInfo() + { + var result = await mediator.Send(new GetCampusBasicInfoListQuery()); + + return Ok(result); + } + + /// + /// Gets details of a specific campus by ID. + /// + /// Campus ID. + /// Details of the specified campus. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetCampusDetailsQuery() + { + Id = id + }); + + return Ok(result); + } + } +} -- 2.43.0 From 810c8ec5cf5b35523453bc091e407ca785e7cfd2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 06:28:14 +0300 Subject: [PATCH 054/474] refactor: move files --- .../{Extensions => Attributes}/BadRequestResponseAttribute.cs | 3 ++- .../{Extensions => Attributes}/NotFoundResponseAttribute.cs | 3 ++- Endpoint/{ => Configuration}/ConfigureSwaggerOptions.cs | 2 +- Endpoint/{ => Configuration}/EnvironmentManager.cs | 2 +- Endpoint/{Common/Extensions => Models}/ErrorResponseVm.cs | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) rename Endpoint/Common/{Extensions => Attributes}/BadRequestResponseAttribute.cs (90%) rename Endpoint/Common/{Extensions => Attributes}/NotFoundResponseAttribute.cs (90%) rename Endpoint/{ => Configuration}/ConfigureSwaggerOptions.cs (96%) rename Endpoint/{ => Configuration}/EnvironmentManager.cs (93%) rename Endpoint/{Common/Extensions => Models}/ErrorResponseVm.cs (92%) diff --git a/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs b/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs similarity index 90% rename from Endpoint/Common/Extensions/BadRequestResponseAttribute.cs rename to Endpoint/Common/Attributes/BadRequestResponseAttribute.cs index 171da23..b81f692 100644 --- a/Endpoint/Common/Extensions/BadRequestResponseAttribute.cs +++ b/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; -using System; using Microsoft.AspNetCore.Mvc; +using Mirea.Api.Endpoint.Models; +using System; namespace Mirea.Api.Endpoint.Common.Extensions; diff --git a/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs b/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs similarity index 90% rename from Endpoint/Common/Extensions/NotFoundResponseAttribute.cs rename to Endpoint/Common/Attributes/NotFoundResponseAttribute.cs index 15db1cc..8f05a69 100644 --- a/Endpoint/Common/Extensions/NotFoundResponseAttribute.cs +++ b/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; -using System; using Microsoft.AspNetCore.Mvc; +using Mirea.Api.Endpoint.Models; +using System; namespace Mirea.Api.Endpoint.Common.Extensions; diff --git a/Endpoint/ConfigureSwaggerOptions.cs b/Endpoint/Configuration/ConfigureSwaggerOptions.cs similarity index 96% rename from Endpoint/ConfigureSwaggerOptions.cs rename to Endpoint/Configuration/ConfigureSwaggerOptions.cs index 9b53140..39280bb 100644 --- a/Endpoint/ConfigureSwaggerOptions.cs +++ b/Endpoint/Configuration/ConfigureSwaggerOptions.cs @@ -5,7 +5,7 @@ using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using System; -namespace Mirea.Api.Endpoint; +namespace Mirea.Api.Endpoint.Configuration; public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions { diff --git a/Endpoint/EnvironmentManager.cs b/Endpoint/Configuration/EnvironmentManager.cs similarity index 93% rename from Endpoint/EnvironmentManager.cs rename to Endpoint/Configuration/EnvironmentManager.cs index 3cbe859..cc2b201 100644 --- a/Endpoint/EnvironmentManager.cs +++ b/Endpoint/Configuration/EnvironmentManager.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace Mirea.Api.Endpoint; +namespace Mirea.Api.Endpoint.Configuration; internal static class EnvironmentManager { diff --git a/Endpoint/Common/Extensions/ErrorResponseVm.cs b/Endpoint/Models/ErrorResponseVm.cs similarity index 92% rename from Endpoint/Common/Extensions/ErrorResponseVm.cs rename to Endpoint/Models/ErrorResponseVm.cs index 7c4a099..d409ae1 100644 --- a/Endpoint/Common/Extensions/ErrorResponseVm.cs +++ b/Endpoint/Models/ErrorResponseVm.cs @@ -1,4 +1,4 @@ -namespace Mirea.Api.Endpoint.Common.Extensions; +namespace Mirea.Api.Endpoint.Models; /// /// A class for providing information about an error -- 2.43.0 From 93a1b6ea8ef47b69fbf51d3cdf484f3461778266 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 06:29:47 +0300 Subject: [PATCH 055/474] refactor move files --- Endpoint/{ => Configuration}/SwaggerDefaultValues.cs | 4 ++-- Endpoint/Program.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) rename Endpoint/{ => Configuration}/SwaggerDefaultValues.cs (93%) diff --git a/Endpoint/SwaggerDefaultValues.cs b/Endpoint/Configuration/SwaggerDefaultValues.cs similarity index 93% rename from Endpoint/SwaggerDefaultValues.cs rename to Endpoint/Configuration/SwaggerDefaultValues.cs index d47c591..cefb307 100644 --- a/Endpoint/SwaggerDefaultValues.cs +++ b/Endpoint/Configuration/SwaggerDefaultValues.cs @@ -6,7 +6,7 @@ using System; using System.Linq; using System.Text.Json; -namespace Mirea.Api.Endpoint; +namespace Mirea.Api.Endpoint.Configuration; public class SwaggerDefaultValues : IOperationFilter { @@ -38,7 +38,7 @@ public class SwaggerDefaultValues : IOperationFilter { var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); - parameter.Description ??= description.ModelMetadata?.Description; + parameter.Description ??= description.ModelMetadata.Description; if (parameter.Schema.Default == null && description.DefaultValue != null && diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 920ce15..c71f6c9 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -8,6 +8,8 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; +using Mirea.Api.Endpoint.Configuration; +using Mirea.Api.Endpoint.Middleware; using Mirea.Api.Endpoint.Properties; using Swashbuckle.AspNetCore.SwaggerGen; using System; -- 2.43.0 From 851eccd876dd8bac432eb69b447cfcff3ca728bf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 28 Jan 2024 06:32:43 +0300 Subject: [PATCH 056/474] fix: error CS0234 --- Endpoint/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index c71f6c9..3607500 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Options; using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; using Mirea.Api.Endpoint.Configuration; -using Mirea.Api.Endpoint.Middleware; using Mirea.Api.Endpoint.Properties; using Swashbuckle.AspNetCore.SwaggerGen; using System; -- 2.43.0 From c8c9b7f4560994f2f6129211674b8d2729ddaba9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 18:06:22 +0300 Subject: [PATCH 057/474] feat: add queries for professor --- .../GetProfessorInfoQuery.cs | 8 +++++ .../GetProfessorInfoQueryHandler.cs | 22 +++++++++++++ .../GetProfessorDetails/ProfessorInfoVm.cs | 22 +++++++++++++ .../GetProfessorList/GetProfessorListQuery.cs | 9 ++++++ .../GetProfessorListQueryHandler.cs | 31 +++++++++++++++++++ .../GetProfessorList/ProfessorListVm.cs | 14 +++++++++ .../GetProfessorList/ProfessorLookupDto.cs | 22 +++++++++++++ 7 files changed, 128 insertions(+) create mode 100644 Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs create mode 100644 Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs create mode 100644 Application/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs create mode 100644 Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs create mode 100644 Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs create mode 100644 Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs create mode 100644 Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs diff --git a/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs b/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs new file mode 100644 index 0000000..93f1aa8 --- /dev/null +++ b/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails; + +public class GetProfessorInfoQuery : IRequest +{ + public required int Id { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs b/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs new file mode 100644 index 0000000..411a68d --- /dev/null +++ b/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs @@ -0,0 +1,22 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Common.Exceptions; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails; + +public class GetProfessorInfoQueryHandler(IProfessorDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetProfessorInfoQuery request, CancellationToken cancellationToken) + { + var discipline = await dbContext.Professors.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Group), request.Id); + + return new ProfessorInfoVm() + { + Id = discipline.Id, + Name = discipline.Name, + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs b/Application/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs new file mode 100644 index 0000000..691ef9b --- /dev/null +++ b/Application/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails; + +/// +/// Represents professors. +/// +public class ProfessorInfoVm +{ + /// + /// The unique identifier for the professor. + /// + public int Id { get; set; } + + /// + /// The name of the professor. + /// + public required string Name { get; set; } + + /// + /// The alt name of the professor. Usually is full name. + /// + public string? AltName { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs b/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs new file mode 100644 index 0000000..1a2e14b --- /dev/null +++ b/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorList; + +public class GetProfessorListQuery : IRequest +{ + public int? Page { get; set; } + public int? PageSize { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs b/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs new file mode 100644 index 0000000..adcff57 --- /dev/null +++ b/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs @@ -0,0 +1,31 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorList; + +public class GetProfessorListQueryHandler(IProfessorDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetProfessorListQuery request, CancellationToken cancellationToken) + { + var dtos = dbContext.Professors.OrderBy(p => p.Id).Select(p => new ProfessorLookupDto() + { + Id = p.Id, + Name = p.Name, + AltName = p.AltName + }); + + if (request is { PageSize: not null, Page: not null }) + dtos = dtos.Skip(request.Page.Value * request.PageSize.Value).Take(request.PageSize.Value); + + var result = await dtos.ToListAsync(cancellationToken); + + return new ProfessorListVm + { + Professors = result + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs b/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs new file mode 100644 index 0000000..f3bf0f2 --- /dev/null +++ b/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorList; + +/// +/// Represents a view model containing multiple professors name. +/// +public class ProfessorListVm +{ + /// + /// The list of professors. + /// + public IList Professors { get; set; } = new List(); +} \ No newline at end of file diff --git a/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs b/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs new file mode 100644 index 0000000..4e32f26 --- /dev/null +++ b/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorList; + +/// +/// Represents professor. +/// +public class ProfessorLookupDto +{ + /// + /// The unique identifier for the professor. + /// + public int Id { get; set; } + + /// + /// The name of the professor. + /// + public required string Name { get; set; } + + /// + /// The alt name of the professor. Usually is full name. + /// + public string? AltName { get; set; } +} \ No newline at end of file -- 2.43.0 From 70c9a6347ec45e6f7ef99ffd9da2c477096cdca4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 22:34:41 +0300 Subject: [PATCH 058/474] feat: add queries for group --- .../GetGroupDetails/GetGroupInfoQuery.cs | 8 +++++ .../GetGroupInfoQueryHandler.cs | 28 +++++++++++++++++ .../Queries/GetGroupDetails/GroupInfoVm.cs | 27 ++++++++++++++++ .../Queries/GetGroupList/GetGroupListQuery.cs | 9 ++++++ .../GetGroupList/GetGroupListQueryHandler.cs | 31 +++++++++++++++++++ .../Group/Queries/GetGroupList/GroupListVm.cs | 14 +++++++++ .../Queries/GetGroupList/GroupLookupDto.cs | 22 +++++++++++++ 7 files changed, 139 insertions(+) create mode 100644 Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs create mode 100644 Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs create mode 100644 Application/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs create mode 100644 Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs create mode 100644 Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs create mode 100644 Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs create mode 100644 Application/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs diff --git a/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs b/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs new file mode 100644 index 0000000..de0b87f --- /dev/null +++ b/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupDetails; + +public class GetGroupInfoQuery : IRequest +{ + public required int Id { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs b/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs new file mode 100644 index 0000000..4d1a092 --- /dev/null +++ b/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs @@ -0,0 +1,28 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Common.Exceptions; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Group + .Queries.GetGroupDetails; + +public class GetGroupInfoQueryHandler(IGroupDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetGroupInfoQuery request, CancellationToken cancellationToken) + { + var discipline = await dbContext.Groups + .Include(g => g.Faculty) + .FirstOrDefaultAsync(g => g.Id == request.Id, cancellationToken) + ?? throw new NotFoundException(typeof(Domain.Schedule.Group), request.Id); + + return new GroupInfoVm() + { + Id = discipline.Id, + Name = discipline.Name, + Faculty = discipline.Faculty?.Name, + FacultyId = discipline.FacultyId + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs b/Application/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs new file mode 100644 index 0000000..8a4999c --- /dev/null +++ b/Application/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs @@ -0,0 +1,27 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupDetails; + +/// +/// Represents groups. +/// +public class GroupInfoVm +{ + /// + /// The unique identifier for the group. + /// + public int Id { get; set; } + + /// + /// The name of the group. + /// + public required string Name { get; set; } + + /// + /// The faculty name for the group. + /// + public string? Faculty { get; set; } + + /// + /// The faculty identifier for the group. + /// + public int? FacultyId { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs b/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs new file mode 100644 index 0000000..7bcc6c9 --- /dev/null +++ b/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupList; + +public class GetGroupListQuery : IRequest +{ + public int? Page { get; set; } + public int? PageSize { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs b/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs new file mode 100644 index 0000000..b0d63b1 --- /dev/null +++ b/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs @@ -0,0 +1,31 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupList; + +public class GetGroupListQueryHandler(IGroupDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetGroupListQuery request, CancellationToken cancellationToken) + { + var dtos = dbContext.Groups.OrderBy(g => g.Id).Select(g => new GroupLookupDto() + { + Id = g.Id, + Name = g.Name, + FacultyId = g.FacultyId + }); + + if (request is { PageSize: not null, Page: not null }) + dtos = dtos.Skip(request.Page.Value * request.PageSize.Value).Take(request.PageSize.Value); + + var result = await dtos.ToListAsync(cancellationToken); + + return new GroupListVm + { + Groups = result + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs b/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs new file mode 100644 index 0000000..f4a777c --- /dev/null +++ b/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupList; + +/// +/// Represents a view model containing multiple groups name. +/// +public class GroupListVm +{ + /// + /// The list of groups. + /// + public IList Groups { get; set; } = new List(); +} \ No newline at end of file diff --git a/Application/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs b/Application/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs new file mode 100644 index 0000000..306f8d2 --- /dev/null +++ b/Application/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupList; + +/// +/// Represents groups. +/// +public class GroupLookupDto +{ + /// + /// The unique identifier for the group. + /// + public int Id { get; set; } + + /// + /// The name of the group. + /// + public required string Name { get; set; } + + /// + /// The faculty identifier for the group. + /// + public int? FacultyId { get; set; } +} \ No newline at end of file -- 2.43.0 From 69a554ce762a377a10857980ea965ac30c2479bb Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 22:35:15 +0300 Subject: [PATCH 059/474] fix: change entity --- .../Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs b/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs index 411a68d..868201d 100644 --- a/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs +++ b/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs @@ -11,7 +11,7 @@ public class GetProfessorInfoQueryHandler(IProfessorDbContext dbContext) : IRequ { public async Task Handle(GetProfessorInfoQuery request, CancellationToken cancellationToken) { - var discipline = await dbContext.Professors.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Group), request.Id); + var discipline = await dbContext.Professors.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Professor), request.Id); return new ProfessorInfoVm() { -- 2.43.0 From ce3bd2367379d98e8c329eb0185a9f68bb82679a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 22:40:21 +0300 Subject: [PATCH 060/474] feat: add queries for faculty --- .../GetFacultyDetails/FacultyInfoVm.cs | 32 +++++++++++++++++++ .../GetFacultyDetails/GetFacultyInfoQuery.cs | 8 +++++ .../GetFacultyInfoQueryHandler.cs | 27 ++++++++++++++++ .../Queries/GetFacultyList/FacultyListVm.cs | 14 ++++++++ .../GetFacultyList/FacultyLookupDto.cs | 22 +++++++++++++ .../GetFacultyList/GetFacultyListQuery.cs | 9 ++++++ .../GetFacultyListQueryHandler.cs | 31 ++++++++++++++++++ 7 files changed, 143 insertions(+) create mode 100644 Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs create mode 100644 Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs create mode 100644 Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs create mode 100644 Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs create mode 100644 Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs create mode 100644 Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs create mode 100644 Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs b/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs new file mode 100644 index 0000000..f7fb805 --- /dev/null +++ b/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs @@ -0,0 +1,32 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; + +/// +/// Represents faculties. +/// +public class FacultyInfoVm +{ + /// + /// The unique identifier for the faculty. + /// + public int Id { get; set; } + + /// + /// The name of the faculty. + /// + public required string Name { get; set; } + + /// + /// ID indicating the faculty's affiliation to the campus. + /// + public int? CampusId { get; set; } + + /// + /// Campus name indicating the faculty's affiliation to the campus. + /// + public string? CampusName { get; set; } + + /// + /// Campus code indicating the faculty's affiliation to the campus. + /// + public string? CampusCode { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs b/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs new file mode 100644 index 0000000..2517b14 --- /dev/null +++ b/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; + +public class GetFacultyInfoQuery : IRequest +{ + public required int Id { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs b/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs new file mode 100644 index 0000000..a2f6486 --- /dev/null +++ b/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs @@ -0,0 +1,27 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Common.Exceptions; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; + +public class GetFacultyInfoQueryHandler(IFacultyDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetFacultyInfoQuery request, CancellationToken cancellationToken) + { + var faculty = await dbContext.Faculties + .Include(f => f.Campus) + .FirstOrDefaultAsync(f => f.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Faculty), request.Id); + + return new FacultyInfoVm() + { + Id = faculty.Id, + Name = faculty.Name, + CampusId = faculty.CampusId, + CampusName = faculty.Campus?.FullName, + CampusCode = faculty.Campus?.CodeName + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs b/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs new file mode 100644 index 0000000..8ad7d4b --- /dev/null +++ b/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyList; + +/// +/// Represents a view model containing multiple faculties. +/// +public class FacultyListVm +{ + /// + /// The list of faculties. + /// + public IList Faculties { get; set; } = new List(); +} \ No newline at end of file diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs b/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs new file mode 100644 index 0000000..7a8c615 --- /dev/null +++ b/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyList; + +/// +/// Represents faculties. +/// +public class FacultyLookupDto +{ + /// + /// The unique identifier for the faculty. + /// + public int Id { get; set; } + + /// + /// The name of the faculty. + /// + public required string Name { get; set; } + + /// + /// ID indicating the faculty's affiliation to the campus. + /// + public int? CampusId { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs b/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs new file mode 100644 index 0000000..9abe675 --- /dev/null +++ b/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyList; + +public class GetFacultyListQuery : IRequest +{ + public int? Page { get; set; } + public int? PageSize { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs b/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs new file mode 100644 index 0000000..1249fe7 --- /dev/null +++ b/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs @@ -0,0 +1,31 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyList; + +public class GetFacultyListQueryHandler(IFacultyDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetFacultyListQuery request, CancellationToken cancellationToken) + { + var dtos = dbContext.Faculties.OrderBy(f => f.Id).Select(f => new FacultyLookupDto() + { + Id = f.Id, + Name = f.Name, + CampusId = f.CampusId + }); + + if (request is { PageSize: not null, Page: not null }) + dtos = dtos.Skip(request.Page.Value * request.PageSize.Value).Take(request.PageSize.Value); + + var result = await dtos.ToListAsync(cancellationToken); + + return new FacultyListVm + { + Faculties = result + }; + } +} \ No newline at end of file -- 2.43.0 From a00dc8ccd6fc1db6ab84fc4ab3b22d02eb2a4f37 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 22:51:26 +0300 Subject: [PATCH 061/474] feat: add queries for lecture hall --- .../GetLectureHallInfoQuery.cs | 8 +++++ .../GetLectureHallInfoQueryHandler.cs | 28 ++++++++++++++++ .../LectureHallInfoVm.cs | 32 +++++++++++++++++++ .../GetLectureHallListQuery.cs | 9 ++++++ .../GetLectureHallListQueryHandler.cs | 31 ++++++++++++++++++ .../GetLectureHallList/LectureHallListVm.cs | 14 ++++++++ .../LectureHallLookupDto.cs | 22 +++++++++++++ 7 files changed, 144 insertions(+) create mode 100644 Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs create mode 100644 Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs create mode 100644 Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs create mode 100644 Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs create mode 100644 Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs create mode 100644 Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs create mode 100644 Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs b/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs new file mode 100644 index 0000000..284f218 --- /dev/null +++ b/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallDetails; + +public class GetLectureHallInfoQuery : IRequest +{ + public required int Id { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs b/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs new file mode 100644 index 0000000..17027a6 --- /dev/null +++ b/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs @@ -0,0 +1,28 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Common.Exceptions; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallDetails; + +public class GetLectureHallInfoQueryHandler(ILectureHallDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetLectureHallInfoQuery request, CancellationToken cancellationToken) + { + var lectureHall = await dbContext.LectureHalls + .Include(l => l.Campus) + .FirstOrDefaultAsync(l => l.Id == request.Id, cancellationToken) + ?? throw new NotFoundException(typeof(Domain.Schedule.LectureHall), request.Id); + + return new LectureHallInfoVm() + { + Id = lectureHall.Id, + Name = lectureHall.Name, + CampusId = lectureHall.CampusId, + CampusCode = lectureHall.Campus?.CodeName, + CampusName = lectureHall.Campus?.FullName + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs b/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs new file mode 100644 index 0000000..9b9c42f --- /dev/null +++ b/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs @@ -0,0 +1,32 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallDetails; + +/// +/// Represents disciplines. +/// +public class LectureHallInfoVm +{ + /// + /// The unique identifier for the lecture hall. + /// + public int Id { get; set; } + + /// + /// The name of the lecture hall. + /// + public required string Name { get; set; } + + /// + /// ID indicating the lecture hall's affiliation to the campus. + /// + public int CampusId { get; set; } + + /// + /// Campus name indicating the lecture hall's affiliation to the campus. + /// + public string? CampusName { get; set; } + + /// + /// Campus code indicating the lecture hall's affiliation to the campus. + /// + public string? CampusCode { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs b/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs new file mode 100644 index 0000000..cb8ea83 --- /dev/null +++ b/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallList; + +public class GetLectureHallListQuery : IRequest +{ + public int? Page { get; set; } + public int? PageSize { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs b/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs new file mode 100644 index 0000000..ac7b041 --- /dev/null +++ b/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs @@ -0,0 +1,31 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallList; + +public class GetLectureHallListQueryHandler(ILectureHallDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetLectureHallListQuery request, CancellationToken cancellationToken) + { + var dtos = dbContext.LectureHalls.OrderBy(l => l.Id).Select(l => new LectureHallLookupDto() + { + Id = l.Id, + Name = l.Name, + CampusId = l.CampusId + }); + + if (request is { PageSize: not null, Page: not null }) + dtos = dtos.Skip(request.Page.Value * request.PageSize.Value).Take(request.PageSize.Value); + + var result = await dtos.ToListAsync(cancellationToken); + + return new LectureHallListVm + { + LectureHalls = result + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs b/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs new file mode 100644 index 0000000..8338f99 --- /dev/null +++ b/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallList; + +/// +/// Represents a view model containing multiple disciplines name. +/// +public class LectureHallListVm +{ + /// + /// The list of lecture hall. + /// + public IList LectureHalls { get; set; } = new List(); +} \ No newline at end of file diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs b/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs new file mode 100644 index 0000000..f00083c --- /dev/null +++ b/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallList; + +/// +/// Represents lecture halls. +/// +public class LectureHallLookupDto +{ + /// + /// The unique identifier for the lecture hall. + /// + public int Id { get; set; } + + /// + /// The name of the lecture hall. + /// + public required string Name { get; set; } + + /// + /// ID indicating the lecture hall's affiliation to the campus. + /// + public int CampusId { get; set; } +} \ No newline at end of file -- 2.43.0 From ed823570f1b070d9a93a924fec1f901b48181fda Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 22:52:36 +0300 Subject: [PATCH 062/474] refactor: code small --- Application/Common/Exceptions/NotFoundException.cs | 4 ++-- .../Queries/GetCampusDetails/GetCampusDetailsQuery.cs | 1 - .../GetCampusDetails/GetCampusDetailsQueryHandler.cs | 11 ++++------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Application/Common/Exceptions/NotFoundException.cs b/Application/Common/Exceptions/NotFoundException.cs index 7cacf74..c310b74 100644 --- a/Application/Common/Exceptions/NotFoundException.cs +++ b/Application/Common/Exceptions/NotFoundException.cs @@ -1,5 +1,5 @@ -using System.Reflection; -using System; +using System; +using System.Reflection; namespace Mirea.Api.DataAccess.Application.Common.Exceptions; diff --git a/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs b/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs index f64bf8b..8609b18 100644 --- a/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs +++ b/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs @@ -1,5 +1,4 @@ using MediatR; -using Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusDetails; diff --git a/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs b/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs index 2cb791c..75af510 100644 --- a/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs +++ b/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs @@ -1,12 +1,9 @@ -using System.Globalization; -using System.Linq; -using MediatR; -using Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; -using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; -using System.Threading.Tasks; -using System.Threading; +using MediatR; using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Common.Exceptions; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Threading; +using System.Threading.Tasks; namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusDetails; -- 2.43.0 From 1b8c82949ac1471ed5d583d112fc4a35863c8eb6 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 23:54:52 +0300 Subject: [PATCH 063/474] feat: write a dto in a separate project --- ApiDto/ApiDto.csproj | 23 +++++++++++++++ ApiDto/Responses/CampusBasicInfoResponse.cs | 22 ++++++++++++++ ApiDto/Responses/CampusDetailsResponse.cs | 27 +++++++++++++++++ ApiDto/Responses/DisciplineResponse.cs | 17 +++++++++++ ApiDto/Responses/ErrorResponse.cs | 18 ++++++++++++ ApiDto/Responses/FacultyDetailsResponse.cs | 32 +++++++++++++++++++++ ApiDto/Responses/FacultyResponse.cs | 22 ++++++++++++++ ApiDto/Responses/GroupDetailsResponse.cs | 27 +++++++++++++++++ ApiDto/Responses/GroupResponse.cs | 22 ++++++++++++++ ApiDto/Responses/ProfessorResponse.cs | 22 ++++++++++++++ 10 files changed, 232 insertions(+) create mode 100644 ApiDto/ApiDto.csproj create mode 100644 ApiDto/Responses/CampusBasicInfoResponse.cs create mode 100644 ApiDto/Responses/CampusDetailsResponse.cs create mode 100644 ApiDto/Responses/DisciplineResponse.cs create mode 100644 ApiDto/Responses/ErrorResponse.cs create mode 100644 ApiDto/Responses/FacultyDetailsResponse.cs create mode 100644 ApiDto/Responses/FacultyResponse.cs create mode 100644 ApiDto/Responses/GroupDetailsResponse.cs create mode 100644 ApiDto/Responses/GroupResponse.cs create mode 100644 ApiDto/Responses/ProfessorResponse.cs diff --git a/ApiDto/ApiDto.csproj b/ApiDto/ApiDto.csproj new file mode 100644 index 0000000..f806393 --- /dev/null +++ b/ApiDto/ApiDto.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + disable + enable + Winsomnia + 1.0.0-a0 + 1.0.0.0 + 1.0.0.0 + Mirea.Api.Dto + $(AssemblyName) + True + ApiDtoDocs.xml + + + + + Always + + + + diff --git a/ApiDto/Responses/CampusBasicInfoResponse.cs b/ApiDto/Responses/CampusBasicInfoResponse.cs new file mode 100644 index 0000000..0232050 --- /dev/null +++ b/ApiDto/Responses/CampusBasicInfoResponse.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents basic information about a campus. +/// +public class CampusBasicInfoResponse +{ + /// + /// Gets or sets the unique identifier of the campus. + /// + public int Id { get; set; } + + /// + /// Gets or sets the code name of the campus. + /// + public required string CodeName { get; set; } + + /// + /// Gets or sets the full name of the campus (optional). + /// + public string? FullName { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/CampusDetailsResponse.cs b/ApiDto/Responses/CampusDetailsResponse.cs new file mode 100644 index 0000000..7caff6b --- /dev/null +++ b/ApiDto/Responses/CampusDetailsResponse.cs @@ -0,0 +1,27 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents detailed information about a campus. +/// +public class CampusDetailsResponse +{ + /// + /// Gets or sets the unique identifier of the campus. + /// + public int Id { get; set; } + + /// + /// Gets or sets the code name of the campus. + /// + public required string CodeName { get; set; } + + /// + /// Gets or sets the full name of the campus (optional). + /// + public string? FullName { get; set; } + + /// + /// Gets or sets the address of the campus (optional). + /// + public string? Address { get; set; } +} diff --git a/ApiDto/Responses/DisciplineResponse.cs b/ApiDto/Responses/DisciplineResponse.cs new file mode 100644 index 0000000..d98b723 --- /dev/null +++ b/ApiDto/Responses/DisciplineResponse.cs @@ -0,0 +1,17 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents information about a discipline. +/// +public class DisciplineResponse +{ + /// + /// Gets or sets the unique identifier of the discipline. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the discipline. + /// + public required string Name { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/ErrorResponse.cs b/ApiDto/Responses/ErrorResponse.cs new file mode 100644 index 0000000..b67377c --- /dev/null +++ b/ApiDto/Responses/ErrorResponse.cs @@ -0,0 +1,18 @@ +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. + /// + 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. + /// + public required int Code { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/FacultyDetailsResponse.cs b/ApiDto/Responses/FacultyDetailsResponse.cs new file mode 100644 index 0000000..ff86c47 --- /dev/null +++ b/ApiDto/Responses/FacultyDetailsResponse.cs @@ -0,0 +1,32 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents detailed information about a faculty. +/// +public class FacultyDetailsResponse +{ + /// + /// Gets or sets the unique identifier of the faculty. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the faculty. + /// + public required string Name { get; set; } + + /// + /// Gets or sets the unique identifier of the campus to which the faculty belongs (optional). + /// + public int? CampusId { get; set; } + + /// + /// Gets or sets the name of the campus to which the faculty belongs (optional). + /// + public string? CampusName { get; set; } + + /// + /// Gets or sets the code name of the campus to which the faculty belongs (optional). + /// + public string? CampusCode { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/FacultyResponse.cs b/ApiDto/Responses/FacultyResponse.cs new file mode 100644 index 0000000..136655d --- /dev/null +++ b/ApiDto/Responses/FacultyResponse.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents basic information about a faculty. +/// +public class FacultyResponse +{ + /// + /// Gets or sets the unique identifier of the faculty. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the faculty. + /// + public required string Name { get; set; } + + /// + /// Gets or sets the unique identifier of the campus to which the faculty belongs (optional). + /// + public int? CampusId { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/GroupDetailsResponse.cs b/ApiDto/Responses/GroupDetailsResponse.cs new file mode 100644 index 0000000..35c665b --- /dev/null +++ b/ApiDto/Responses/GroupDetailsResponse.cs @@ -0,0 +1,27 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents detailed information about a group. +/// +public class GroupDetailsResponse +{ + /// + /// Gets or sets the unique identifier of the group. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the group. + /// + public required string Name { get; set; } + + /// + /// Gets or sets the unique identifier of the faculty to which the group belongs (optional). + /// + public int? FacultyId { get; set; } + + /// + /// Gets or sets the name of the faculty to which the group belongs (optional). + /// + public string? FacultyName { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/GroupResponse.cs b/ApiDto/Responses/GroupResponse.cs new file mode 100644 index 0000000..4526e72 --- /dev/null +++ b/ApiDto/Responses/GroupResponse.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents basic information about a group. +/// +public class GroupResponse +{ + /// + /// Gets or sets the unique identifier of the group. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the group. + /// + public required string Name { get; set; } + + /// + /// Gets or sets the unique identifier of the faculty to which the group belongs (optional). + /// + public int? FacultyId { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/ProfessorResponse.cs b/ApiDto/Responses/ProfessorResponse.cs new file mode 100644 index 0000000..3a7b761 --- /dev/null +++ b/ApiDto/Responses/ProfessorResponse.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents information about a professor. +/// +public class ProfessorResponse +{ + /// + /// Gets or sets the unique identifier of the professor. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the professor. + /// + public required string Name { get; set; } + + /// + /// Gets or sets the alternate name of the professor (optional). + /// + public string? AltName { get; set; } +} \ No newline at end of file -- 2.43.0 From 1caaa4759e1dee14ef08ca28146bce6fc0348509 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 23:56:45 +0300 Subject: [PATCH 064/474] refactor: rewrite the api for the dto project --- .../Attributes/BadRequestResponseAttribute.cs | 6 ++--- .../Attributes/NotFoundResponseAttribute.cs | 6 ++--- Endpoint/Controllers/V1/CampusController.cs | 26 +++++++++++++++---- .../Controllers/V1/DisciplineController.cs | 26 ++++++++++++++----- Endpoint/Endpoint.csproj | 1 + Endpoint/Models/ErrorResponseVm.cs | 18 ------------- 6 files changed, 47 insertions(+), 36 deletions(-) delete mode 100644 Endpoint/Models/ErrorResponseVm.cs diff --git a/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs b/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs index b81f692..85f9ed8 100644 --- a/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs +++ b/Endpoint/Common/Attributes/BadRequestResponseAttribute.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Mirea.Api.Endpoint.Models; +using Mirea.Api.Dto.Responses; using System; -namespace Mirea.Api.Endpoint.Common.Extensions; +namespace Mirea.Api.Endpoint.Common.Attributes; [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] -public class BadRequestResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponseVm), StatusCodes.Status400BadRequest); \ No newline at end of file +public class BadRequestResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponse), StatusCodes.Status400BadRequest); \ No newline at end of file diff --git a/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs b/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs index 8f05a69..39527ea 100644 --- a/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs +++ b/Endpoint/Common/Attributes/NotFoundResponseAttribute.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Mirea.Api.Endpoint.Models; +using Mirea.Api.Dto.Responses; using System; -namespace Mirea.Api.Endpoint.Common.Extensions; +namespace Mirea.Api.Endpoint.Common.Attributes; [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] -public class NotFoundResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponseVm), StatusCodes.Status404NotFound); \ No newline at end of file +public class NotFoundResponseAttribute() : ProducesResponseTypeAttribute(typeof(ErrorResponse), StatusCodes.Status404NotFound); \ No newline at end of file diff --git a/Endpoint/Controllers/V1/CampusController.cs b/Endpoint/Controllers/V1/CampusController.cs index c383608..4ee93c8 100644 --- a/Endpoint/Controllers/V1/CampusController.cs +++ b/Endpoint/Controllers/V1/CampusController.cs @@ -3,7 +3,10 @@ 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; -using Mirea.Api.Endpoint.Common.Extensions; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Attributes; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1 @@ -16,11 +19,18 @@ namespace Mirea.Api.Endpoint.Controllers.V1 /// Basic information about campuses. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetBasicInfo() + public async Task>> GetBasicInfo() { var result = await mediator.Send(new GetCampusBasicInfoListQuery()); - return Ok(result); + return Ok(result.Campuses + .Select(c => new CampusBasicInfoResponse() + { + Id = c.Id, + CodeName = c.CodeName, + FullName = c.FullName + }) + ); } /// @@ -32,14 +42,20 @@ namespace Mirea.Api.Endpoint.Controllers.V1 [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] - public async Task> GetDetails(int id) + public async Task> GetDetails(int id) { var result = await mediator.Send(new GetCampusDetailsQuery() { Id = id }); - return Ok(result); + return Ok(new CampusDetailsResponse() + { + Id = result.Id, + CodeName = result.CodeName, + FullName = result.FullName, + Address = result.Address + }); } } } diff --git a/Endpoint/Controllers/V1/DisciplineController.cs b/Endpoint/Controllers/V1/DisciplineController.cs index 6589d31..056b208 100644 --- a/Endpoint/Controllers/V1/DisciplineController.cs +++ b/Endpoint/Controllers/V1/DisciplineController.cs @@ -3,7 +3,10 @@ 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; -using Mirea.Api.Endpoint.Common.Extensions; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Attributes; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1 @@ -19,7 +22,7 @@ namespace Mirea.Api.Endpoint.Controllers.V1 [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] - public async Task> Get([FromQuery] int? page, [FromQuery] int? pageSize) + public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) { var result = await mediator.Send(new GetDisciplineListQuery() { @@ -27,27 +30,36 @@ namespace Mirea.Api.Endpoint.Controllers.V1 PageSize = pageSize }); - return Ok(result); + return Ok(result.Disciplines + .Select(d => new DisciplineResponse() + { + Id = d.Id, + Name = d.Name + }) + ); } /// /// Gets details of a specific discipline by ID. /// /// Discipline ID. - /// Details of the specified discipline. - /// + /// Details of the specified discipline. [HttpGet("{id:int}")] [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] - public async Task> GetDetails(int id) + public async Task> GetDetails(int id) { var result = await mediator.Send(new GetDisciplineInfoQuery() { Id = id }); - return Ok(result); + return Ok(new DisciplineResponse() + { + Id = result.Id, + Name = result.Name + }); } } } diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 84dcfc2..0191a2e 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -28,6 +28,7 @@ + diff --git a/Endpoint/Models/ErrorResponseVm.cs b/Endpoint/Models/ErrorResponseVm.cs deleted file mode 100644 index d409ae1..0000000 --- a/Endpoint/Models/ErrorResponseVm.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Mirea.Api.Endpoint.Models; - -/// -/// A class for providing information about an error -/// -public class ErrorResponseVm -{ - /// - /// 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. - /// - 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. - /// - public required int Code { get; set; } -} \ No newline at end of file -- 2.43.0 From aad3c74fba610c00aa6c40b908442fe51f3e6c2b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 23:57:05 +0300 Subject: [PATCH 065/474] feat: add controller for faculty --- Endpoint/Controllers/V1/FacultyController.cs | 73 ++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Endpoint/Controllers/V1/FacultyController.cs diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs new file mode 100644 index 0000000..fe0190d --- /dev/null +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -0,0 +1,73 @@ +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; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Attributes; +using Swashbuckle.AspNetCore.Annotations; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1 +{ + public class FacultyController(IMediator mediator) : BaseControllerV1 + { + /// + /// Gets a paginated list of faculties. + /// + /// Page number. Start from 0. + /// Number of items per page. + /// Paginated list of faculties. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + public async Task>> Get( + [SwaggerDefault("0")][SwaggerSchema(Nullable = true)][FromQuery] int? page, + [SwaggerDefault("25")][SwaggerSchema(Nullable = true)][FromQuery] int? pageSize) + { + var result = await mediator.Send(new GetFacultyListQuery() + { + Page = page, + PageSize = pageSize + }); + + return Ok(result.Faculties + .Select(f => new FacultyResponse() + { + Id = f.Id, + Name = f.Name, + CampusId = f.CampusId + }) + ); + } + + /// + /// Gets details of a specific faculty by ID. + /// + /// Faculty ID. + /// Details of the specified faculty. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails( + [SwaggerDefault("1")] int id) + { + var result = await mediator.Send(new GetFacultyInfoQuery() + { + Id = id + }); + + return Ok(new FacultyDetailsResponse() + { + Id = result.Id, + Name = result.Name, + CampusId = result.CampusId, + CampusCode = result.CampusCode, + CampusName = result.CampusName + }); + } + } +} -- 2.43.0 From 14aedf9d46bb694a5938087e9a2337ea465c98a6 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 23:57:13 +0300 Subject: [PATCH 066/474] feat: add controller for group --- Endpoint/Controllers/V1/GroupController.cs | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Endpoint/Controllers/V1/GroupController.cs diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs new file mode 100644 index 0000000..970bc66 --- /dev/null +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -0,0 +1,68 @@ +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; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Attributes; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1 +{ + public class GroupController(IMediator mediator) : BaseControllerV1 + { + /// + /// Retrieves a list of groups. + /// + /// The page number for pagination (optional). + /// The page size for pagination (optional). + /// A list of groups. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + { + var result = await mediator.Send(new GetGroupListQuery() + { + Page = page, + PageSize = pageSize + }); + + return Ok(result.Groups + .Select(g => new GroupResponse() + { + Id = g.Id, + Name = g.Name, + FacultyId = g.FacultyId + }) + ); + } + + /// + /// Retrieves detailed information about a specific group. + /// + /// The ID of the group to retrieve. + /// Detailed information about the group. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetGroupInfoQuery() + { + Id = id + }); + + return Ok(new GroupDetailsResponse() + { + Id = result.Id, + Name = result.Name, + FacultyId = result.FacultyId, + FacultyName = result.Faculty + }); + } + } +} -- 2.43.0 From 5cfc07ab7d929b5c659e3f536b56d35dfb2a6773 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 16 Feb 2024 23:57:22 +0300 Subject: [PATCH 067/474] feat: add controller for professor --- .../Controllers/V1/ProfessorController.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Endpoint/Controllers/V1/ProfessorController.cs diff --git a/Endpoint/Controllers/V1/ProfessorController.cs b/Endpoint/Controllers/V1/ProfessorController.cs new file mode 100644 index 0000000..5a9053a --- /dev/null +++ b/Endpoint/Controllers/V1/ProfessorController.cs @@ -0,0 +1,66 @@ +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; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Attributes; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +public class ProfessorController(IMediator mediator) : BaseControllerV1 +{ + /// + /// Retrieves a list of professors. + /// + /// The page number for pagination (optional). + /// The page size for pagination (optional). + /// A list of professors. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + { + var result = await mediator.Send(new GetProfessorListQuery() + { + Page = page, + PageSize = pageSize + }); + + return Ok(result.Professors + .Select(p => new ProfessorResponse() + { + Id = p.Id, + Name = p.Name, + AltName = p.AltName + }) + ); + } + + /// + /// Retrieves detailed information about a specific professor. + /// + /// The ID of the professor to retrieve. + /// Detailed information about the professor. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetProfessorInfoQuery() + { + Id = id + }); + + return Ok(new ProfessorResponse() + { + Id = result.Id, + Name = result.Name, + AltName = result.AltName + }); + } +} \ No newline at end of file -- 2.43.0 From 84872a5d6d0c100445ba69ccf2d22c1d3d85828c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 17 Feb 2024 01:49:37 +0300 Subject: [PATCH 068/474] feat: add course information --- ApiDto/Responses/GroupDetailsResponse.cs | 5 +++++ ApiDto/Responses/GroupResponse.cs | 5 +++++ Endpoint/Controllers/V1/GroupController.cs | 20 ++++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ApiDto/Responses/GroupDetailsResponse.cs b/ApiDto/Responses/GroupDetailsResponse.cs index 35c665b..d4f5cae 100644 --- a/ApiDto/Responses/GroupDetailsResponse.cs +++ b/ApiDto/Responses/GroupDetailsResponse.cs @@ -15,6 +15,11 @@ public class GroupDetailsResponse /// public required string Name { get; set; } + /// + /// Gets or sets the course number of the group. + /// + public int CourseNumber { get; set; } + /// /// Gets or sets the unique identifier of the faculty to which the group belongs (optional). /// diff --git a/ApiDto/Responses/GroupResponse.cs b/ApiDto/Responses/GroupResponse.cs index 4526e72..63b91b3 100644 --- a/ApiDto/Responses/GroupResponse.cs +++ b/ApiDto/Responses/GroupResponse.cs @@ -15,6 +15,11 @@ public class GroupResponse /// public required string Name { get; set; } + /// + /// Gets or sets the course number of the group. + /// + public int CourseNumber { get; set; } + /// /// Gets or sets the unique identifier of the faculty to which the group belongs (optional). /// diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index 970bc66..3dc7966 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -5,6 +5,7 @@ using Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupDetails; using Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupList; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -13,6 +14,19 @@ namespace Mirea.Api.Endpoint.Controllers.V1 { public class GroupController(IMediator mediator) : BaseControllerV1 { + private static int GetCourseNumber(string groupName) + { + var current = DateTime.Now; + if (!int.TryParse(groupName[2..], out var yearOfGroup) + && !int.TryParse(groupName.Split('-')[^1][..2], out yearOfGroup)) + return -1; + + // Convert a two-digit year to a four-digit one + yearOfGroup += current.Year / 100 * 100; + + return current.Year - yearOfGroup + (current.Month < 9 ? 0 : 1); + } + /// /// Retrieves a list of groups. /// @@ -35,7 +49,8 @@ namespace Mirea.Api.Endpoint.Controllers.V1 { Id = g.Id, Name = g.Name, - FacultyId = g.FacultyId + FacultyId = g.FacultyId, + CourseNumber = GetCourseNumber(g.Name) }) ); } @@ -61,7 +76,8 @@ namespace Mirea.Api.Endpoint.Controllers.V1 Id = result.Id, Name = result.Name, FacultyId = result.FacultyId, - FacultyName = result.Faculty + FacultyName = result.Faculty, + CourseNumber = GetCourseNumber(result.Name) }); } } -- 2.43.0 From 6396d8a318a454aa6283385b37cd8b9b1744e462 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 17 Feb 2024 09:07:47 +0300 Subject: [PATCH 069/474] refactor: add an understanding of nullable for swagger gen --- ApiDto/Responses/CampusBasicInfoResponse.cs | 6 +++++- ApiDto/Responses/CampusDetailsResponse.cs | 6 +++++- ApiDto/Responses/DisciplineResponse.cs | 6 +++++- ApiDto/Responses/ErrorResponse.cs | 6 +++++- ApiDto/Responses/FacultyDetailsResponse.cs | 6 +++++- ApiDto/Responses/FacultyResponse.cs | 6 +++++- ApiDto/Responses/GroupDetailsResponse.cs | 7 ++++++- ApiDto/Responses/GroupResponse.cs | 7 ++++++- ApiDto/Responses/ProfessorResponse.cs | 6 +++++- 9 files changed, 47 insertions(+), 9 deletions(-) diff --git a/ApiDto/Responses/CampusBasicInfoResponse.cs b/ApiDto/Responses/CampusBasicInfoResponse.cs index 0232050..4ad46a7 100644 --- a/ApiDto/Responses/CampusBasicInfoResponse.cs +++ b/ApiDto/Responses/CampusBasicInfoResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// Represents basic information about a campus. @@ -8,11 +10,13 @@ public class CampusBasicInfoResponse /// /// Gets or sets the unique identifier of the campus. /// + [Required] public int Id { get; set; } /// /// Gets or sets the code name of the campus. /// + [Required] public required string CodeName { get; set; } /// diff --git a/ApiDto/Responses/CampusDetailsResponse.cs b/ApiDto/Responses/CampusDetailsResponse.cs index 7caff6b..82db94e 100644 --- a/ApiDto/Responses/CampusDetailsResponse.cs +++ b/ApiDto/Responses/CampusDetailsResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// Represents detailed information about a campus. @@ -8,11 +10,13 @@ public class CampusDetailsResponse /// /// Gets or sets the unique identifier of the campus. /// + [Required] public int Id { get; set; } /// /// Gets or sets the code name of the campus. /// + [Required] public required string CodeName { get; set; } /// diff --git a/ApiDto/Responses/DisciplineResponse.cs b/ApiDto/Responses/DisciplineResponse.cs index d98b723..9b35e13 100644 --- a/ApiDto/Responses/DisciplineResponse.cs +++ b/ApiDto/Responses/DisciplineResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// Represents information about a discipline. @@ -8,10 +10,12 @@ public class DisciplineResponse /// /// Gets or sets the unique identifier of the discipline. /// + [Required] public int Id { get; set; } /// /// Gets or sets the name of the discipline. /// + [Required] public required string Name { get; set; } } \ No newline at end of file diff --git a/ApiDto/Responses/ErrorResponse.cs b/ApiDto/Responses/ErrorResponse.cs index b67377c..f0dc33c 100644 --- a/ApiDto/Responses/ErrorResponse.cs +++ b/ApiDto/Responses/ErrorResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// A class for providing information about an error @@ -9,10 +11,12 @@ 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/ApiDto/Responses/FacultyDetailsResponse.cs b/ApiDto/Responses/FacultyDetailsResponse.cs index ff86c47..09c863b 100644 --- a/ApiDto/Responses/FacultyDetailsResponse.cs +++ b/ApiDto/Responses/FacultyDetailsResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// Represents detailed information about a faculty. @@ -8,11 +10,13 @@ public class FacultyDetailsResponse /// /// Gets or sets the unique identifier of the faculty. /// + [Required] public int Id { get; set; } /// /// Gets or sets the name of the faculty. /// + [Required] public required string Name { get; set; } /// diff --git a/ApiDto/Responses/FacultyResponse.cs b/ApiDto/Responses/FacultyResponse.cs index 136655d..adcf127 100644 --- a/ApiDto/Responses/FacultyResponse.cs +++ b/ApiDto/Responses/FacultyResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// Represents basic information about a faculty. @@ -8,11 +10,13 @@ public class FacultyResponse /// /// Gets or sets the unique identifier of the faculty. /// + [Required] public int Id { get; set; } /// /// Gets or sets the name of the faculty. /// + [Required] public required string Name { get; set; } /// diff --git a/ApiDto/Responses/GroupDetailsResponse.cs b/ApiDto/Responses/GroupDetailsResponse.cs index d4f5cae..44670fe 100644 --- a/ApiDto/Responses/GroupDetailsResponse.cs +++ b/ApiDto/Responses/GroupDetailsResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// Represents detailed information about a group. @@ -8,16 +10,19 @@ public class GroupDetailsResponse /// /// Gets or sets the unique identifier of the group. /// + [Required] public int Id { get; set; } /// /// Gets or sets the name of the group. /// + [Required] public required string Name { get; set; } /// /// Gets or sets the course number of the group. /// + [Required] public int CourseNumber { get; set; } /// diff --git a/ApiDto/Responses/GroupResponse.cs b/ApiDto/Responses/GroupResponse.cs index 63b91b3..0177459 100644 --- a/ApiDto/Responses/GroupResponse.cs +++ b/ApiDto/Responses/GroupResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// Represents basic information about a group. @@ -8,16 +10,19 @@ public class GroupResponse /// /// Gets or sets the unique identifier of the group. /// + [Required] public int Id { get; set; } /// /// Gets or sets the name of the group. /// + [Required] public required string Name { get; set; } /// /// Gets or sets the course number of the group. /// + [Required] public int CourseNumber { get; set; } /// diff --git a/ApiDto/Responses/ProfessorResponse.cs b/ApiDto/Responses/ProfessorResponse.cs index 3a7b761..02361f9 100644 --- a/ApiDto/Responses/ProfessorResponse.cs +++ b/ApiDto/Responses/ProfessorResponse.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Dto.Responses; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; /// /// Represents information about a professor. @@ -8,11 +10,13 @@ public class ProfessorResponse /// /// Gets or sets the unique identifier of the professor. /// + [Required] public int Id { get; set; } /// /// Gets or sets the name of the professor. /// + [Required] public required string Name { get; set; } /// -- 2.43.0 From 2b4065bbdf7d60e80567067af87d6d11817c5526 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 17 Feb 2024 09:09:18 +0300 Subject: [PATCH 070/474] fix: error CS0234 --- Endpoint/Controllers/V1/CampusController.cs | 2 +- Endpoint/Controllers/V1/FacultyController.cs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Endpoint/Controllers/V1/CampusController.cs b/Endpoint/Controllers/V1/CampusController.cs index 4ee93c8..ae83834 100644 --- a/Endpoint/Controllers/V1/CampusController.cs +++ b/Endpoint/Controllers/V1/CampusController.cs @@ -19,7 +19,7 @@ namespace Mirea.Api.Endpoint.Controllers.V1 /// Basic information about campuses. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetBasicInfo() + public async Task>> Get() { var result = await mediator.Send(new GetCampusBasicInfoListQuery()); diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index fe0190d..745e673 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -23,9 +23,7 @@ namespace Mirea.Api.Endpoint.Controllers.V1 [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] - public async Task>> Get( - [SwaggerDefault("0")][SwaggerSchema(Nullable = true)][FromQuery] int? page, - [SwaggerDefault("25")][SwaggerSchema(Nullable = true)][FromQuery] int? pageSize) + public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) { var result = await mediator.Send(new GetFacultyListQuery() { @@ -52,8 +50,7 @@ namespace Mirea.Api.Endpoint.Controllers.V1 [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] - public async Task> GetDetails( - [SwaggerDefault("1")] int id) + public async Task> GetDetails(int id) { var result = await mediator.Send(new GetFacultyInfoQuery() { -- 2.43.0 From 477cec2f98415793797e6cdff65fa8dc89b73350 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 17 Feb 2024 10:58:25 +0300 Subject: [PATCH 071/474] fix: error CS0234 --- Endpoint/Controllers/V1/FacultyController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index 745e673..3b4cd61 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -5,7 +5,6 @@ using Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; using Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyList; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; -using Swashbuckle.AspNetCore.Annotations; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -- 2.43.0 From 59eccf8b93fbad16f33fa97ec03107871bad2068 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 17 Feb 2024 14:21:52 +0300 Subject: [PATCH 072/474] feat: add model for lecture hall --- .../Responses/LectureHallDetailsResponse.cs | 37 +++++++++++++++++++ ApiDto/Responses/LectureHallResponse.cs | 27 ++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 ApiDto/Responses/LectureHallDetailsResponse.cs create mode 100644 ApiDto/Responses/LectureHallResponse.cs diff --git a/ApiDto/Responses/LectureHallDetailsResponse.cs b/ApiDto/Responses/LectureHallDetailsResponse.cs new file mode 100644 index 0000000..67b1a66 --- /dev/null +++ b/ApiDto/Responses/LectureHallDetailsResponse.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents the detailed response model for a lecture hall. +/// +public class LectureHallDetailsResponse +{ + /// + /// Gets or sets the ID of the lecture hall. + /// + [Required] + public int Id { get; set; } + + /// + /// Gets or sets the name of the lecture hall. + /// + [Required] + public required string Name { get; set; } + + /// + /// Gets or sets the ID of the campus to which the lecture hall belongs. + /// + [Required] + public int CampusId { get; set; } + + /// + /// Gets or sets the name of the campus. + /// + public string? CampusName { get; set; } + + /// + /// Gets or sets the code of the campus. + /// + public string? CampusCode { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/LectureHallResponse.cs b/ApiDto/Responses/LectureHallResponse.cs new file mode 100644 index 0000000..6bb288f --- /dev/null +++ b/ApiDto/Responses/LectureHallResponse.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents the response model for a lecture hall. +/// +public class LectureHallResponse +{ + /// + /// Gets or sets the ID of the lecture hall. + /// + [Required] + public int Id { get; set; } + + /// + /// Gets or sets the name of the lecture hall. + /// + [Required] + public required string Name { get; set; } + + /// + /// Gets or sets the ID of the campus to which the lecture hall belongs. + /// + [Required] + public int CampusId { get; set; } +} \ No newline at end of file -- 2.43.0 From e3d74c373e741764552cd2064dcbcdd208eaabc4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 17 Feb 2024 14:22:29 +0300 Subject: [PATCH 073/474] feat: add controller for lecture hall --- .../Controllers/V1/LectureHallController.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Endpoint/Controllers/V1/LectureHallController.cs diff --git a/Endpoint/Controllers/V1/LectureHallController.cs b/Endpoint/Controllers/V1/LectureHallController.cs new file mode 100644 index 0000000..4ccede5 --- /dev/null +++ b/Endpoint/Controllers/V1/LectureHallController.cs @@ -0,0 +1,63 @@ +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; +using Mirea.Api.DataAccess.Persistence; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Attributes; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1 +{ + public class LectureHallController(IMediator mediator, UberDbContext dbContext) : BaseControllerV1 + { + /// + /// Retrieves a list of all lecture halls. + /// + /// A list of lecture halls. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> Get() + { + var result = await mediator.Send(new GetLectureHallListQuery()); + + return Ok(result.LectureHalls + .Select(l => new LectureHallResponse() + { + Id = l.Id, + Name = l.Name, + CampusId = l.CampusId + }) + ); + } + + /// + /// Retrieves details of a specific lecture hall by its ID. + /// + /// The ID of the lecture hall to retrieve. + /// The details of the specified lecture hall. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetLectureHallInfoQuery() + { + Id = id + }); + + return Ok(new LectureHallDetailsResponse() + { + Id = result.Id, + Name = result.Name, + CampusId = result.CampusId, + CampusCode = result.CampusCode, + CampusName = result.CampusName + }); + } + } +} -- 2.43.0 From e3e7c4550f08ea73fb7952c76783c2598ae36a53 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 19 Feb 2024 10:06:44 +0300 Subject: [PATCH 074/474] feat: add a query to get schedule data --- .../GetScheduleList/GetScheduleListQuery.cs | 12 +++ .../GetScheduleListQueryHandler.cs | 85 +++++++++++++++++++ .../Queries/GetScheduleList/ScheduleListVm.cs | 14 +++ .../GetScheduleList/ScheduleLookupDto.cs | 80 +++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs create mode 100644 Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs create mode 100644 Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs create mode 100644 Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs new file mode 100644 index 0000000..939e1c6 --- /dev/null +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs @@ -0,0 +1,12 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Schedule.Queries.GetScheduleList; + +public class GetScheduleListQuery : IRequest +{ + public int[]? GroupIds { get; set; } + public int[]? DisciplineIds { get; set; } + public int[]? LectureHallIds { get; set; } + public int[]? ProfessorIds { get; set; } + public bool? IsEven { get; set; } +} \ No newline at end of file diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs new file mode 100644 index 0000000..76993c1 --- /dev/null +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs @@ -0,0 +1,85 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Schedule.Queries.GetScheduleList; + +public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetScheduleListQuery request, CancellationToken cancellationToken) + { + var query = dbContext.Lessons.Include(l => l.LessonAssociations) + .ThenInclude(la => la.LectureHall) + .ThenInclude(lh => lh!.Campus) + .Include(l => l.LessonAssociations) + .ThenInclude(la => la.Professor) + .Include(l => l.Group) + .Include(l => l.TypeOfOccupation) + .Include(l => l.Discipline) + .AsQueryable(); + + if (request.IsEven != null) + query = query.Where(l => l.IsEven == request.IsEven); + + if (request.GroupIds != null && request.GroupIds.Length != 0) + query = query.Where(l => request.GroupIds.Contains(l.GroupId)); + + if (request.DisciplineIds != null && request.DisciplineIds.Length != 0) + query = query.Where(l => request.DisciplineIds.Contains(l.DisciplineId)); + + if (request.LectureHallIds != null && request.LectureHallIds.Length != 0) + query = query.Where(l => l.LessonAssociations!.Any(la => + la.LectureHallId != null && request.LectureHallIds.Contains(la.LectureHallId.Value))); + + if (request.ProfessorIds != null && request.ProfessorIds.Length != 0) + query = query.Where(l => + l.LessonAssociations!.Any(la => + la.ProfessorId != null && request.ProfessorIds.Contains(la.ProfessorId.Value))); + + var data = await query.ToArrayAsync(cancellationToken); + + var result = data.Select(l => new ScheduleLookupDto() + { + DayOfWeek = l.DayOfWeek, + PairNumber = l.PairNumber, + IsEven = l.IsEven, + Discipline = l.Discipline!.Name, + TypeOfOccupation = l.TypeOfOccupation!.ShortName, + + Group = l.Group!.Name, + GroupId = l.GroupId, + + LectureHalls = l.LessonAssociations! + .Where(la => !string.IsNullOrEmpty(la.LectureHall?.Name)) + .Select(la => la.LectureHall?.Name), + LectureHallsId = l.LessonAssociations! + .Where(la => la.LectureHallId != null) + .Select(la => la.LectureHallId), + + Campus = l.LessonAssociations! + .Where(la => !string.IsNullOrEmpty(la.LectureHall?.Campus?.CodeName)) + .Select(la => la.LectureHall?.Campus?.CodeName), + CampusId = l.LessonAssociations! + .Where(la => la.LectureHall?.Campus != null) + .Select(la => la.LectureHall?.CampusId), + + + Professors = l.LessonAssociations! + .Where(la => !string.IsNullOrEmpty(la.Professor?.Name)) + .Select(la => la.Professor?.Name), + ProfessorsId = l.LessonAssociations! + .Where(la => la.ProfessorId != null) + .Select(la => la.ProfessorId), + + LinkToMeet = l.LessonAssociations!.Select(la => la.LinkToMeet) + }).ToList(); + + return new ScheduleListVm + { + Schedules = result + }; + } +} \ No newline at end of file diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs new file mode 100644 index 0000000..565e5b9 --- /dev/null +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Schedule.Queries.GetScheduleList; + +/// +/// Represents a view model for a list of schedules. +/// +public class ScheduleListVm +{ + /// + /// Gets or sets the list of schedules. + /// + public IList Schedules { get; set; } = new List(); +} \ No newline at end of file diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs new file mode 100644 index 0000000..028a190 --- /dev/null +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Schedule.Queries.GetScheduleList; + +/// +/// Represents a data transfer object for schedule lookup. +/// +public class ScheduleLookupDto +{ + /// + /// Gets or sets the day of the week. + /// + public DayOfWeek DayOfWeek { get; set; } + + /// + /// Gets or sets the pair number. + /// + public int PairNumber { get; set; } + + /// + /// Gets or sets a value indicating whether the pair is on an even week. + /// + public bool IsEven { get; set; } + + /// + /// Gets or sets the name of the discipline. + /// + public required string Discipline { get; set; } + + /// + /// Gets or sets the type of occupation. + /// + public required string TypeOfOccupation { get; set; } + + /// + /// Gets or sets the name of the group. + /// + public required string Group { get; set; } + + /// + /// Gets or sets the ID of the group. + /// + public required int GroupId { get; set; } + + /// + /// Gets or sets the names of the lecture halls. + /// + public required IEnumerable LectureHalls { get; set; } + + /// + /// Gets or sets the IDs of the lecture halls. + /// + public required IEnumerable LectureHallsId { get; set; } + + /// + /// Gets or sets the names of the professors. + /// + public required IEnumerable Professors { get; set; } + + /// + /// Gets or sets the IDs of the professors. + /// + public required IEnumerable ProfessorsId { get; set; } + + /// + /// Gets or sets the names of the campuses. + /// + public required IEnumerable Campus { get; set; } + + /// + /// Gets or sets the IDs of the campuses. + /// + public required IEnumerable CampusId { get; set; } + + /// + /// Gets or sets the links to online meetings. + /// + public required IEnumerable LinkToMeet { get; set; } +} \ No newline at end of file -- 2.43.0 From 29485368f88374900e3d45a7c9c1b24ece2304f1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 19 Feb 2024 11:20:05 +0300 Subject: [PATCH 075/474] fix: add discipline id --- .../Queries/GetScheduleList/GetScheduleListQueryHandler.cs | 4 +++- .../Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs index 76993c1..ba08003 100644 --- a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs @@ -46,9 +46,11 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH DayOfWeek = l.DayOfWeek, PairNumber = l.PairNumber, IsEven = l.IsEven, - Discipline = l.Discipline!.Name, TypeOfOccupation = l.TypeOfOccupation!.ShortName, + Discipline = l.Discipline!.Name, + DisciplineId = l.DisciplineId, + Group = l.Group!.Name, GroupId = l.GroupId, diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs index 028a190..bd3808f 100644 --- a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs @@ -28,6 +28,11 @@ public class ScheduleLookupDto /// public required string Discipline { get; set; } + /// + /// Gets or sets the ID of the discipline. + /// + public required int DisciplineId { get; set; } + /// /// Gets or sets the type of occupation. /// -- 2.43.0 From ee2351b5ed4ff0fda6424aa07fd338c70b4cd492 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 19 Feb 2024 11:20:49 +0300 Subject: [PATCH 076/474] feat: add controller --- ApiDto/Requests/ScheduleRequest.cs | 37 +++ .../Schedule/DisciplineScheduleResponse.cs | 106 ++++++ .../Schedule/GroupScheduleResponse.cs | 106 ++++++ .../Schedule/LectureHallScheduleResponse.cs | 105 ++++++ .../Schedule/ProfessorScheduleResponse.cs | 108 +++++++ ApiDto/Responses/Schedule/ScheduleResponse.cs | 94 ++++++ Endpoint/Controllers/V1/ScheduleController.cs | 304 ++++++++++++++++++ 7 files changed, 860 insertions(+) create mode 100644 ApiDto/Requests/ScheduleRequest.cs create mode 100644 ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs create mode 100644 ApiDto/Responses/Schedule/GroupScheduleResponse.cs create mode 100644 ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs create mode 100644 ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs create mode 100644 ApiDto/Responses/Schedule/ScheduleResponse.cs create mode 100644 Endpoint/Controllers/V1/ScheduleController.cs diff --git a/ApiDto/Requests/ScheduleRequest.cs b/ApiDto/Requests/ScheduleRequest.cs new file mode 100644 index 0000000..476d8f3 --- /dev/null +++ b/ApiDto/Requests/ScheduleRequest.cs @@ -0,0 +1,37 @@ +namespace Mirea.Api.Dto.Requests; + +/// +/// Represents a request object for retrieving schedules based on various filters. +/// +public class ScheduleRequest +{ + /// + /// Gets or sets an array of group IDs. + /// + /// This array can contain null values. + public int[]? Groups { get; set; } = null; + + /// + /// Gets or sets a value indicating whether to retrieve schedules for even weeks. + /// + /// This property can contain null. + public bool? IsEven { get; set; } = null; + + /// + /// Gets or sets an array of discipline IDs. + /// + /// This array can contain null values. + public int[]? Disciplines { get; set; } = null; + + /// + /// Gets or sets an array of professor IDs. + /// + /// This array can contain null values. + public int[]? Professors { get; set; } = null; + + /// + /// Gets or sets an array of lecture hall IDs. + /// + /// This array can contain null values. + public int[]? LectureHalls { get; set; } = null; +} diff --git a/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs b/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs new file mode 100644 index 0000000..bf4a337 --- /dev/null +++ b/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses.Schedule; + +/// +/// Represents information about a specific schedule entry for a professor. +/// +public class DisciplineScheduleInfo +{ + /// + /// Gets or sets the day of the week for the schedule entry. + /// + [Required] + public DayOfWeek DayOfWeek { get; set; } + + /// + /// Gets or sets the pair number for the schedule entry. + /// + [Required] + public int PairNumber { get; set; } + + /// + /// Gets or sets a value indicating whether the pair is on an even week. + /// + [Required] + public bool IsEven { get; set; } + + /// + /// Gets or sets the type of occupation for the schedule entry. + /// + [Required] + public required string TypeOfOccupation { get; set; } + + /// + /// Gets or sets the names of the group for the schedule entry. + /// + [Required] + + public required string Group { get; set; } + /// + /// Gets or sets the IDs of the group for the schedule entry. + /// + [Required] + public required int GroupId { get; set; } + + /// + /// Gets or sets the names of the lecture halls for the schedule entry. + /// + public required IEnumerable LectureHalls { get; set; } + + /// + /// Gets or sets the IDs of the lecture halls for the schedule entry. + /// + public required IEnumerable LectureHallsId { get; set; } + + /// + /// Gets or sets the names of the professors for the schedule entry. + /// + public required IEnumerable Professors { get; set; } + + /// + /// Gets or sets the IDs of the professors for the schedule entry. + /// + public required IEnumerable ProfessorsId { get; set; } + + /// + /// Gets or sets the names of the campuses for the schedule entry. + /// + public required IEnumerable Campus { get; set; } + + /// + /// Gets or sets the IDs of the campuses for the schedule entry. + /// + public required IEnumerable CampusId { get; set; } + + /// + /// Gets or sets the links to online meetings for the schedule entry. + /// + public required IEnumerable LinkToMeet { get; set; } +} + +/// +/// Represents a response containing schedule information for a professor. +/// +public class DisciplineScheduleResponse +{ + /// + /// Gets or sets the name of the discipline. + /// + [Required] + public required string Discipline { get; set; } + + /// + /// Gets or sets the ID of the discipline. + /// + [Required] + public required int DisciplineId { get; set; } + + /// + /// Gets or sets the schedules for the professor. + /// + [Required] + public required IEnumerable Schedules { get; set; } +} diff --git a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs new file mode 100644 index 0000000..7fa13d1 --- /dev/null +++ b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses.Schedule; + +/// +/// Represents information about a specific schedule entry for a group. +/// +public class GroupScheduleInfo +{ + /// + /// Gets or sets the day of the week for the schedule entry. + /// + [Required] + public DayOfWeek DayOfWeek { get; set; } + + /// + /// Gets or sets the pair number for the schedule entry. + /// + [Required] + public int PairNumber { get; set; } + + /// + /// Gets or sets a value indicating whether the pair is on an even week. + /// + [Required] + public bool IsEven { get; set; } + + /// + /// Gets or sets the name of the discipline for the schedule entry. + /// + [Required] + public required string Discipline { get; set; } + + /// + /// Gets or sets the ID of the discipline for the schedule entry. + /// + [Required] + public required int DisciplineId { get; set; } + + /// + /// Gets or sets the type of occupation for the schedule entry. + /// + [Required] + public required string TypeOfOccupation { get; set; } + + /// + /// Gets or sets the names of the lecture halls for the schedule entry. + /// + public required IEnumerable LectureHalls { get; set; } + + /// + /// Gets or sets the IDs of the lecture halls for the schedule entry. + /// + public required IEnumerable LectureHallsId { get; set; } + + /// + /// Gets or sets the names of the professors for the schedule entry. + /// + public required IEnumerable Professors { get; set; } + + /// + /// Gets or sets the IDs of the professors for the schedule entry. + /// + public required IEnumerable ProfessorsId { get; set; } + + /// + /// Gets or sets the names of the campuses for the schedule entry. + /// + public required IEnumerable Campus { get; set; } + + /// + /// Gets or sets the IDs of the campuses for the schedule entry. + /// + public required IEnumerable CampusId { get; set; } + + /// + /// Gets or sets the links to online meetings for the schedule entry. + /// + public required IEnumerable LinkToMeet { get; set; } +} + +/// +/// Represents a response containing schedule information for a group. +/// +public class GroupScheduleResponse +{ + /// + /// Gets or sets the name of the group. + /// + [Required] + public required string Group { get; set; } + + /// + /// Gets or sets the ID of the group. + /// + [Required] + public required int GroupId { get; set; } + + /// + /// Gets or sets the schedules for the group. + /// + [Required] + public required IEnumerable Schedules { get; set; } +} diff --git a/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs b/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs new file mode 100644 index 0000000..369a8f5 --- /dev/null +++ b/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses.Schedule; + +/// +/// Represents information about a specific schedule entry for a lecture hall. +/// +public class LectureHallScheduleInfo +{ + /// + /// Gets or sets the day of the week for the schedule entry. + /// + [Required] + public DayOfWeek DayOfWeek { get; set; } + + /// + /// Gets or sets the pair number for the schedule entry. + /// + [Required] + public int PairNumber { get; set; } + + /// + /// Gets or sets a value indicating whether the pair is on an even week. + /// + [Required] + public bool IsEven { get; set; } + + /// + /// Gets or sets the name of the discipline for the schedule entry. + /// + [Required] + public required string Discipline { get; set; } + + /// + /// Gets or sets the ID of the discipline for the schedule entry. + /// + [Required] + public required int DisciplineId { get; set; } + + /// + /// Gets or sets the type of occupation for the schedule entry. + /// + [Required] + public required string TypeOfOccupation { get; set; } + + /// + /// Gets or sets the names of the group for the schedule entry. + /// + [Required] + public required string Group { get; set; } + /// + /// Gets or sets the IDs of the group for the schedule entry. + /// + [Required] + public required int GroupId { get; set; } + + /// + /// Gets or sets the names of the campuses for the schedule entry. + /// + public required IEnumerable Campus { get; set; } + + /// + /// Gets or sets the IDs of the campuses for the schedule entry. + /// + public required IEnumerable CampusId { get; set; } + + /// + /// Gets or sets the names of the professors for the schedule entry. + /// + public required IEnumerable Professors { get; set; } + + /// + /// Gets or sets the IDs of the professors for the schedule entry. + /// + public required IEnumerable ProfessorsId { get; set; } + + /// + /// Gets or sets the links to online meetings for the schedule entry. + /// + public required IEnumerable LinkToMeet { get; set; } +} + +/// +/// Represents a response containing schedule information for a lecture hall. +/// +public class LectureHallScheduleResponse +{ + /// + /// Gets or sets the names of the lecture halls. + /// + public required string LectureHalls { get; set; } + + /// + /// Gets or sets the IDs of the lecture halls. + /// + public required int LectureHallsId { get; set; } + + /// + /// Gets or sets the schedules for the lecture hall. + /// + [Required] + public required IEnumerable Schedules { get; set; } +} diff --git a/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs b/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs new file mode 100644 index 0000000..3a53ebf --- /dev/null +++ b/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses.Schedule; + +/// +/// Represents information about a specific schedule entry for a professor. +/// +public class ProfessorScheduleInfo +{ + /// + /// Gets or sets the day of the week for the schedule entry. + /// + [Required] + public DayOfWeek DayOfWeek { get; set; } + + /// + /// Gets or sets the pair number for the schedule entry. + /// + [Required] + public int PairNumber { get; set; } + + /// + /// Gets or sets a value indicating whether the pair is on an even week. + /// + [Required] + public bool IsEven { get; set; } + + /// + /// Gets or sets the name of the discipline for the schedule entry. + /// + [Required] + public required string Discipline { get; set; } + + /// + /// Gets or sets the ID of the discipline for the schedule entry. + /// + [Required] + public required int DisciplineId { get; set; } + + /// + /// Gets or sets the type of occupation for the schedule entry. + /// + [Required] + public required string TypeOfOccupation { get; set; } + + /// + /// Gets or sets the names of the group for the schedule entry. + /// + [Required] + + public required string Group { get; set; } + /// + /// Gets or sets the IDs of the group for the schedule entry. + /// + [Required] + public required int GroupId { get; set; } + + /// + /// Gets or sets the names of the lecture halls for the schedule entry. + /// + public required IEnumerable LectureHalls { get; set; } + + /// + /// Gets or sets the IDs of the lecture halls for the schedule entry. + /// + public required IEnumerable LectureHallsId { get; set; } + + /// + /// Gets or sets the names of the campuses for the schedule entry. + /// + public required IEnumerable Campus { get; set; } + + /// + /// Gets or sets the IDs of the campuses for the schedule entry. + /// + public required IEnumerable CampusId { get; set; } + + /// + /// Gets or sets the links to online meetings for the schedule entry. + /// + public required IEnumerable LinkToMeet { get; set; } +} + +/// +/// Represents a response containing schedule information for a professor. +/// +public class ProfessorScheduleResponse +{ + /// + /// Gets or sets the name of the professor. + /// + [Required] + public required string Professor { get; set; } + + /// + /// Gets or sets the ID of the professor. + /// + [Required] + public required int ProfessorId { get; set; } + + /// + /// Gets or sets the schedules for the professor. + /// + [Required] + public required IEnumerable Schedules { get; set; } +} diff --git a/ApiDto/Responses/Schedule/ScheduleResponse.cs b/ApiDto/Responses/Schedule/ScheduleResponse.cs new file mode 100644 index 0000000..2a03178 --- /dev/null +++ b/ApiDto/Responses/Schedule/ScheduleResponse.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses.Schedule; + +/// +/// Represents a response object containing schedule information. +/// +public class ScheduleResponse +{ + /// + /// Gets or sets the day of the week for the schedule entry. + /// + [Required] + public DayOfWeek DayOfWeek { get; set; } + + /// + /// Gets or sets the pair number for the schedule entry. + /// + [Required] + public int PairNumber { get; set; } + + /// + /// Gets or sets a value indicating whether the pair is on an even week. + /// + [Required] + public bool IsEven { get; set; } + + /// + /// Gets or sets the name of the discipline for the schedule entry. + /// + [Required] + public required string Discipline { get; set; } + + /// + /// Gets or sets the ID of the discipline for the schedule entry. + /// + [Required] + public required int DisciplineId { get; set; } + + /// + /// Gets or sets the type of occupation for the schedule entry. + /// + [Required] + public required string TypeOfOccupation { get; set; } + + /// + /// Gets or sets the name of the group for the schedule entry. + /// + [Required] + public required string Group { get; set; } + + /// + /// Gets or sets the ID of the group for the schedule entry. + /// + [Required] + public required int GroupId { get; set; } + + /// + /// Gets or sets the names of the lecture halls for the schedule entry. + /// + public required IEnumerable LectureHalls { get; set; } + + /// + /// Gets or sets the IDs of the lecture halls for the schedule entry. + /// + public required IEnumerable LectureHallsId { get; set; } + + /// + /// Gets or sets the names of the professors for the schedule entry. + /// + public required IEnumerable Professors { get; set; } + + /// + /// Gets or sets the IDs of the professors for the schedule entry. + /// + public required IEnumerable ProfessorsId { get; set; } + + /// + /// Gets or sets the names of the campuses for the schedule entry. + /// + public required IEnumerable Campus { get; set; } + + /// + /// Gets or sets the IDs of the campuses for the schedule entry. + /// + public required IEnumerable CampusId { get; set; } + + /// + /// Gets or sets the links to online meetings for the schedule entry. + /// + public required IEnumerable LinkToMeet { get; set; } +} diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs new file mode 100644 index 0000000..43b47ad --- /dev/null +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -0,0 +1,304 @@ +using MediatR; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Mirea.Api.DataAccess.Application.Cqrs.Schedule.Queries.GetScheduleList; +using Mirea.Api.Dto.Requests; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Dto.Responses.Schedule; +using Mirea.Api.Endpoint.Common.Attributes; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +public class ScheduleController(IMediator mediator) : BaseControllerV1 +{ + /// + /// Retrieves schedules based on various filters. + /// + /// The request object containing filter criteria. + /// A list of schedules matching the filter criteria. + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [BadRequestResponse] + [NotFoundResponse] + public async Task>> Get([FromBody] ScheduleRequest request) + + { + if ((request.Groups == null || !request.Groups.Any()) && + (request.Disciplines == null || !request.Disciplines.Any()) && + (request.Professors == null || !request.Professors.Any()) && + (request.LectureHalls == null || !request.LectureHalls.Any())) + { + return BadRequest(new ErrorResponse() + { + Error = "At least one of the arguments must be selected." + + (request.IsEven.HasValue + ? $" \"{nameof(request.IsEven)}\" is not a strong argument" + : string.Empty), + Code = StatusCodes.Status400BadRequest + }); + } + + var result = (await mediator.Send(new GetScheduleListQuery() + { + IsEven = request.IsEven, + DisciplineIds = request.Disciplines, + GroupIds = request.Groups, + LectureHallIds = request.LectureHalls, + ProfessorIds = request.Professors + })).Schedules; + + if (result.Count == 0) NoContent(); + + return Ok(result.Select(s => new ScheduleResponse() + { + DayOfWeek = s.DayOfWeek, + PairNumber = s.PairNumber, + IsEven = s.IsEven, + Discipline = s.Discipline, + DisciplineId = s.DisciplineId, + TypeOfOccupation = s.TypeOfOccupation, + Group = s.Group, + GroupId = s.GroupId, + LectureHalls = s.LectureHalls, + LectureHallsId = s.LectureHallsId, + Professors = s.Professors, + ProfessorsId = s.ProfessorsId, + Campus = s.Campus, + CampusId = s.CampusId, + LinkToMeet = s.LinkToMeet + })); + + } + + /// + /// Retrieves schedules for a specific group based on various filters. + /// + /// The ID of the group. + /// A value indicating whether to retrieve schedules for even weeks. + /// An array of discipline IDs. + /// An array of professor IDs. + /// An array of lecture hall IDs. + /// A response containing schedules for the specified group. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetByGroup(int id, + [FromQuery] bool? isEven = null, + [FromQuery] int[]? disciplines = null, + [FromQuery] int[]? professors = null, + [FromQuery] int[]? lectureHalls = null) + + { + var result = (await mediator.Send(new GetScheduleListQuery() + { + IsEven = isEven, + DisciplineIds = disciplines, + GroupIds = [id], + LectureHallIds = lectureHalls, + ProfessorIds = professors + })).Schedules; + + if (result.Count == 0) NoContent(); + + return Ok(new GroupScheduleResponse() + { + Group = result[0].Group, + GroupId = result[0].GroupId, + Schedules = result.Select(g => new GroupScheduleInfo() + { + DayOfWeek = g.DayOfWeek, + PairNumber = g.PairNumber, + IsEven = g.IsEven, + Discipline = g.Discipline, + DisciplineId = g.DisciplineId, + TypeOfOccupation = g.TypeOfOccupation, + LectureHalls = g.LectureHalls, + LectureHallsId = g.LectureHallsId, + Professors = g.Professors, + ProfessorsId = g.ProfessorsId, + Campus = g.Campus, + CampusId = g.CampusId, + LinkToMeet = g.LinkToMeet + }) + }); + } + + /// + /// Retrieves schedules for a specific professor based on various filters. + /// + /// The ID of the professor. + /// A value indicating whether to retrieve schedules for even weeks. + /// An array of discipline IDs. + /// An array of group IDs. + /// An array of lecture hall IDs. + /// A response containing schedules for the specified professor. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetByProfessor(int id, + [FromQuery] bool? isEven = null, + [FromQuery] int[]? disciplines = null, + [FromQuery] int[]? groups = null, + [FromQuery] int[]? lectureHalls = null) + + { + var result = (await mediator.Send(new GetScheduleListQuery() + { + IsEven = isEven, + DisciplineIds = disciplines, + GroupIds = groups, + LectureHallIds = lectureHalls, + ProfessorIds = [id] + })).Schedules; + + if (result.Count == 0) NoContent(); + + return Ok(new ProfessorScheduleResponse() + { + Professor = result.Select(professor => + professor.Professors.FirstOrDefault(x => !string.IsNullOrEmpty(x)) + ).First()!, + ProfessorId = result.Select(professor => + professor.ProfessorsId.FirstOrDefault(x => x != null) + ).First()!.Value, + Schedules = result.Select(p => new ProfessorScheduleInfo() + { + DayOfWeek = p.DayOfWeek, + PairNumber = p.PairNumber, + IsEven = p.IsEven, + Discipline = p.Discipline, + DisciplineId = p.DisciplineId, + TypeOfOccupation = p.TypeOfOccupation, + Group = p.Group, + GroupId = p.GroupId, + LectureHalls = p.LectureHalls, + LectureHallsId = p.LectureHallsId, + Campus = p.Campus, + CampusId = p.CampusId, + LinkToMeet = p.LinkToMeet + }) + }); + } + + /// + /// Retrieves schedules for a specific lecture hall based on various filters. + /// + /// The ID of the lecture hall. + /// A value indicating whether to retrieve schedules for even weeks. + /// An array of discipline IDs. + /// An array of professor IDs. + /// An array of group IDs. + /// A response containing schedules for the specified lecture hall. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetByLectureHall(int id, + [FromQuery] bool? isEven = null, + [FromQuery] int[]? disciplines = null, + [FromQuery] int[]? groups = null, + [FromQuery] int[]? professors = null) + + { + var result = (await mediator.Send(new GetScheduleListQuery() + { + IsEven = isEven, + DisciplineIds = disciplines, + GroupIds = groups, + LectureHallIds = [id], + ProfessorIds = professors + })).Schedules; + + if (result.Count == 0) NoContent(); + + return Ok(new LectureHallScheduleResponse() + { + LectureHalls = result.Select(lectureHall => + lectureHall.LectureHalls.FirstOrDefault(x => !string.IsNullOrEmpty(x)) + ).First()!, + LectureHallsId = result.Select(lectureHall => + lectureHall.LectureHallsId.FirstOrDefault(x => x != null) + ).First()!.Value, + Schedules = result.Select(l => new LectureHallScheduleInfo() + { + DayOfWeek = l.DayOfWeek, + PairNumber = l.PairNumber, + IsEven = l.IsEven, + Discipline = l.Discipline, + DisciplineId = l.DisciplineId, + TypeOfOccupation = l.TypeOfOccupation, + Group = l.Group, + GroupId = l.GroupId, + Professors = l.Professors, + ProfessorsId = l.ProfessorsId, + Campus = l.Campus, + CampusId = l.CampusId, + LinkToMeet = l.LinkToMeet + }) + }); + } + + /// + /// Retrieves schedules for a specific discipline based on various filters. + /// + /// The ID of the discipline. + /// A value indicating whether to retrieve schedules for even weeks. + /// An array of group IDs. + /// An array of professor IDs. + /// An array of lecture hall IDs. + /// A response containing schedules for the specified discipline. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetByDiscipline(int id, + [FromQuery] bool? isEven = null, + [FromQuery] int[]? groups = null, + [FromQuery] int[]? professors = null, + [FromQuery] int[]? lectureHalls = null) + + { + var result = (await mediator.Send(new GetScheduleListQuery() + { + IsEven = isEven, + DisciplineIds = [id], + GroupIds = groups, + LectureHallIds = [id], + ProfessorIds = professors + })).Schedules; + + if (result.Count == 0) NoContent(); + + return Ok(new DisciplineScheduleResponse() + { + Discipline = result[0].Discipline, + DisciplineId = result[0].DisciplineId, + Schedules = result.Select(d => new DisciplineScheduleInfo() + { + DayOfWeek = d.DayOfWeek, + PairNumber = d.PairNumber, + IsEven = d.IsEven, + TypeOfOccupation = d.TypeOfOccupation, + Group = d.Group, + GroupId = d.GroupId, + LectureHalls = d.LectureHalls, + LectureHallsId = d.LectureHallsId, + Professors = d.Professors, + ProfessorsId = d.ProfessorsId, + Campus = d.Campus, + CampusId = d.CampusId, + LinkToMeet = d.LinkToMeet + }) + }); + } +} \ No newline at end of file -- 2.43.0 From 5536162521b8831224505353892f026f99fb12d3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 19 Feb 2024 12:02:37 +0300 Subject: [PATCH 077/474] feat: add get group by faculty id --- Endpoint/Controllers/V1/GroupController.cs | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index 3dc7966..cead029 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -80,5 +80,29 @@ namespace Mirea.Api.Endpoint.Controllers.V1 CourseNumber = GetCourseNumber(result.Name) }); } + + /// + /// Retrieves a list of groups by faculty ID. + /// + /// The ID of the faculty. + /// A list of groups belonging to the specified faculty. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task>> GetByFaculty(int id) + { + var result = await mediator.Send(new GetGroupListQuery()); + + return Ok(result.Groups + .Where(g => g.FacultyId == id) + .Select(g => new GroupResponse() + { + Id = g.Id, + Name = g.Name, + CourseNumber = GetCourseNumber(g.Name), + FacultyId = g.FacultyId + })); + } } } -- 2.43.0 From d02fb8becbb964e942b289ab712c0561c0e2c776 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 19 Feb 2024 12:03:10 +0300 Subject: [PATCH 078/474] feat: get lecture halls by campus --- .../Controllers/V1/LectureHallController.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/V1/LectureHallController.cs b/Endpoint/Controllers/V1/LectureHallController.cs index 4ccede5..de2f21f 100644 --- a/Endpoint/Controllers/V1/LectureHallController.cs +++ b/Endpoint/Controllers/V1/LectureHallController.cs @@ -3,7 +3,6 @@ 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; -using Mirea.Api.DataAccess.Persistence; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; using System.Collections.Generic; @@ -12,7 +11,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1 { - public class LectureHallController(IMediator mediator, UberDbContext dbContext) : BaseControllerV1 + public class LectureHallController(IMediator mediator) : BaseControllerV1 { /// /// Retrieves a list of all lecture halls. @@ -59,5 +58,27 @@ namespace Mirea.Api.Endpoint.Controllers.V1 CampusName = result.CampusName }); } + + /// + /// Retrieves a list of lecture halls by campus ID. + /// + /// The ID of the campus. + /// A list of lecture halls in the specified campus. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task>> GetByCampus(int id) + { + var result = await mediator.Send(new GetLectureHallListQuery()); + + return Ok(result.LectureHalls.Where(l => l.CampusId == id) + .Select(l => new LectureHallResponse() + { + Id = l.Id, + Name = l.Name, + CampusId = l.CampusId + })); + } } } -- 2.43.0 From 05dadff455f3a3cf02239d016d00cab761d96457 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 19 May 2024 11:49:03 +0300 Subject: [PATCH 079/474] feat: add specific week --- Domain/Schedule/Lesson.cs | 4 ++-- Domain/Schedule/LessonAssociation.cs | 2 ++ Domain/Schedule/SpecificWeek.cs | 10 ++++++++++ Domain/Schedule/TypeOfOccupation.cs | 3 +-- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 Domain/Schedule/SpecificWeek.cs diff --git a/Domain/Schedule/Lesson.cs b/Domain/Schedule/Lesson.cs index 3ff5e52..e854d3e 100644 --- a/Domain/Schedule/Lesson.cs +++ b/Domain/Schedule/Lesson.cs @@ -9,13 +9,13 @@ public class Lesson public bool IsEven { get; set; } public DayOfWeek DayOfWeek { get; set; } public int PairNumber { get; set; } + public bool? IsExcludedWeeks { get; set; } public int GroupId { get; set; } public Group? Group { get; set; } - public int TypeOfOccupationId { get; set; } - public TypeOfOccupation? TypeOfOccupation { get; set; } public int DisciplineId { get; set; } public Discipline? Discipline { get; set; } public List? LessonAssociations { get; set; } + public List? SpecificWeeks { get; set; } } \ No newline at end of file diff --git a/Domain/Schedule/LessonAssociation.cs b/Domain/Schedule/LessonAssociation.cs index 89b5651..e101bdd 100644 --- a/Domain/Schedule/LessonAssociation.cs +++ b/Domain/Schedule/LessonAssociation.cs @@ -5,6 +5,8 @@ public class LessonAssociation public int Id { get; set; } public string? LinkToMeet { get; set; } + public int TypeOfOccupationId { get; set; } + public TypeOfOccupation? TypeOfOccupation { get; set; } public int LessonId { get; set; } public Lesson? Lesson { get; set; } public int? ProfessorId { get; set; } diff --git a/Domain/Schedule/SpecificWeek.cs b/Domain/Schedule/SpecificWeek.cs new file mode 100644 index 0000000..b0adf31 --- /dev/null +++ b/Domain/Schedule/SpecificWeek.cs @@ -0,0 +1,10 @@ +namespace Mirea.Api.DataAccess.Domain.Schedule; + +public class SpecificWeek +{ + public int Id { get; set; } + public int WeekNumber { get; set; } + + public int LessonId { get; set; } + public Lesson? Lesson { get; set; } +} \ No newline at end of file diff --git a/Domain/Schedule/TypeOfOccupation.cs b/Domain/Schedule/TypeOfOccupation.cs index a2152a8..4a52a4a 100644 --- a/Domain/Schedule/TypeOfOccupation.cs +++ b/Domain/Schedule/TypeOfOccupation.cs @@ -6,7 +6,6 @@ public class TypeOfOccupation { public int Id { get; set; } public required string ShortName { get; set; } - public string? FullName { get; set; } - public List? Lessons { get; set; } + public List? Lessons { get; set; } } \ No newline at end of file -- 2.43.0 From 628faf7e687058dddfff75aed74a8e82eda48f75 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 19 May 2024 11:52:08 +0300 Subject: [PATCH 080/474] feat: add interface for specific week --- .../DbContexts/Schedule/ISpecificWeekDbContext.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Application/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs b/Application/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs new file mode 100644 index 0000000..a58115c --- /dev/null +++ b/Application/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; + +public interface ISpecificWeekDbContext : IDbContextBase +{ + DbSet SpecificWeeks { get; set; } +} \ No newline at end of file -- 2.43.0 From 97d2fae79e0f897b9aab87f3df8763a0259339f6 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 19 May 2024 11:52:56 +0300 Subject: [PATCH 081/474] feat: add specific week to persistence --- .../Schedule/SpecificWeekDbContext.cs | 17 ++++++++++++ Persistence/DependencyInjection.cs | 2 ++ .../LessonAssociationConfiguration.cs | 8 ++++++ .../Schedule/LessonConfiguration.cs | 9 +------ .../Schedule/SpecificWeekConfiguration.cs | 26 +++++++++++++++++++ .../Schedule/TypeOfOccupationConfiguration.cs | 1 - Persistence/UberDbContext.cs | 2 ++ 7 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 Persistence/Contexts/Schedule/SpecificWeekDbContext.cs create mode 100644 Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs diff --git a/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs b/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs new file mode 100644 index 0000000..b3601bd --- /dev/null +++ b/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; + +public class SpecificWeekDbContext(DbContextOptions options) : DbContext(options), ISpecificWeekDbContext +{ + public DbSet SpecificWeeks { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new SpecificWeekConfiguration()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Persistence/DependencyInjection.cs b/Persistence/DependencyInjection.cs index 9ec71f1..ed6f554 100644 --- a/Persistence/DependencyInjection.cs +++ b/Persistence/DependencyInjection.cs @@ -43,6 +43,7 @@ public static class DependencyInjection services.AddDbContext(dbConfig); services.AddDbContext(dbConfig); services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); services.AddDbContext(dbConfig); } @@ -58,6 +59,7 @@ public static class DependencyInjection services.AddScoped(provider => provider.GetService()!); services.AddScoped(provider => provider.GetService()!); services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); return services; } diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs index 44152dd..c9ecb8c 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs @@ -18,6 +18,8 @@ public class LessonAssociationConfiguration : IEntityTypeConfiguration l.LessonId).HasColumnType("INTEGER").IsRequired(); builder.Property(l => l.ProfessorId).HasColumnType("INTEGER"); builder.Property(l => l.LectureHallId).HasColumnType("INTEGER"); + builder.Property(l => l.TypeOfOccupationId).HasColumnType("INTEGER").IsRequired(); + builder .HasOne(l => l.Lesson) @@ -36,5 +38,11 @@ public class LessonAssociationConfiguration : IEntityTypeConfiguration l.LessonAssociations) .HasForeignKey(l => l.LectureHallId) .OnDelete(DeleteBehavior.SetNull); + + builder + .HasOne(l => l.TypeOfOccupation) + .WithMany(t => t.Lessons) + .HasForeignKey(d => d.TypeOfOccupationId) + .OnDelete(DeleteBehavior.Cascade); } } \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs index a3e878a..561df32 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs @@ -16,24 +16,17 @@ public class LessonConfiguration : IEntityTypeConfiguration builder.Property(l => l.IsEven).HasColumnType("BIT").IsRequired(); builder.Property(l => l.DayOfWeek).HasColumnType("INTEGER").IsRequired(); builder.Property(l => l.PairNumber).HasColumnType("INTEGER").IsRequired(); + builder.Property(l => l.IsExcludedWeeks).HasColumnType("BOOLEAN"); builder.Property(l => l.GroupId).HasColumnType("INTEGER").IsRequired(); - builder.Property(l => l.TypeOfOccupationId).HasColumnType("INTEGER").IsRequired(); builder.Property(l => l.DisciplineId).HasColumnType("INTEGER").IsRequired(); - builder .HasOne(l => l.Group) .WithMany(g => g.Lessons) .HasForeignKey(d => d.GroupId) .OnDelete(DeleteBehavior.Cascade); - builder - .HasOne(l => l.TypeOfOccupation) - .WithMany(t => t.Lessons) - .HasForeignKey(d => d.TypeOfOccupationId) - .OnDelete(DeleteBehavior.Cascade); - builder .HasOne(l => l.Discipline) .WithMany(d => d.Lessons) diff --git a/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs new file mode 100644 index 0000000..f4fc8a3 --- /dev/null +++ b/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; + +public class SpecificWeekConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(SpecificWeek)); + builder.HasKey(s => s.Id); + builder.HasIndex(s => s.Id).IsUnique(); + builder.Property(s => s.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(s => s.WeekNumber).HasColumnType("INTEGER").IsRequired(); + + builder.Property(s => s.LessonId).HasColumnType("INTEGER").IsRequired(); + + builder + .HasOne(s => s.Lesson) + .WithMany(l => l.SpecificWeeks) + .HasForeignKey(s => s.LessonId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs index 82c0b61..5341a5a 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs @@ -14,6 +14,5 @@ public class TypeOfOccupationConfiguration : IEntityTypeConfiguration t.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); builder.Property(t => t.ShortName).HasColumnType("TEXT").IsRequired().HasMaxLength(16); - builder.Property(t => t.FullName).HasColumnType("TEXT").HasMaxLength(64); } } \ No newline at end of file diff --git a/Persistence/UberDbContext.cs b/Persistence/UberDbContext.cs index 699cc49..ccd1877 100644 --- a/Persistence/UberDbContext.cs +++ b/Persistence/UberDbContext.cs @@ -15,6 +15,7 @@ public class UberDbContext(DbContextOptions options) : DbContext( public DbSet Lessons { get; set; } = null!; public DbSet Professors { get; set; } = null!; public DbSet TypeOfOccupations { get; set; } = null!; + public DbSet SpecificWeeks { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -27,6 +28,7 @@ public class UberDbContext(DbContextOptions options) : DbContext( modelBuilder.ApplyConfiguration(new ProfessorConfiguration()); modelBuilder.ApplyConfiguration(new LessonAssociationConfiguration()); modelBuilder.ApplyConfiguration(new TypeOfOccupationConfiguration()); + modelBuilder.ApplyConfiguration(new SpecificWeekConfiguration()); base.OnModelCreating(modelBuilder); } -- 2.43.0 From ca18804a33076092caaae24e3132ff109dab74cf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 19 May 2024 12:30:17 +0300 Subject: [PATCH 082/474] fix: error CS1061 --- .../Schedule/DisciplineScheduleResponse.cs | 2 +- .../Schedule/GroupScheduleResponse.cs | 2 +- .../Schedule/LectureHallScheduleResponse.cs | 2 +- .../Schedule/ProfessorScheduleResponse.cs | 2 +- ApiDto/Responses/Schedule/ScheduleResponse.cs | 2 +- Endpoint/Controllers/V1/ScheduleController.cs | 20 +++++++++---------- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs b/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs index bf4a337..ffc5578 100644 --- a/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs @@ -31,7 +31,7 @@ public class DisciplineScheduleInfo /// Gets or sets the type of occupation for the schedule entry. /// [Required] - public required string TypeOfOccupation { get; set; } + public required IEnumerable TypeOfOccupation { get; set; } /// /// Gets or sets the names of the group for the schedule entry. diff --git a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs index 7fa13d1..c85778c 100644 --- a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs @@ -43,7 +43,7 @@ public class GroupScheduleInfo /// Gets or sets the type of occupation for the schedule entry. /// [Required] - public required string TypeOfOccupation { get; set; } + public required IEnumerable TypeOfOccupations { get; set; } /// /// Gets or sets the names of the lecture halls for the schedule entry. diff --git a/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs b/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs index 369a8f5..294876f 100644 --- a/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs @@ -43,7 +43,7 @@ public class LectureHallScheduleInfo /// Gets or sets the type of occupation for the schedule entry. /// [Required] - public required string TypeOfOccupation { get; set; } + public required IEnumerable TypeOfOccupations { get; set; } /// /// Gets or sets the names of the group for the schedule entry. diff --git a/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs b/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs index 3a53ebf..07a7471 100644 --- a/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs @@ -43,7 +43,7 @@ public class ProfessorScheduleInfo /// Gets or sets the type of occupation for the schedule entry. /// [Required] - public required string TypeOfOccupation { get; set; } + public required IEnumerable TypeOfOccupations { get; set; } /// /// Gets or sets the names of the group for the schedule entry. diff --git a/ApiDto/Responses/Schedule/ScheduleResponse.cs b/ApiDto/Responses/Schedule/ScheduleResponse.cs index 2a03178..8698ed7 100644 --- a/ApiDto/Responses/Schedule/ScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/ScheduleResponse.cs @@ -43,7 +43,7 @@ public class ScheduleResponse /// Gets or sets the type of occupation for the schedule entry. /// [Required] - public required string TypeOfOccupation { get; set; } + public required IEnumerable TypeOfOccupations { get; set; } /// /// Gets or sets the name of the group for the schedule entry. diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 43b47ad..3e9a5ed 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -27,10 +27,10 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 public async Task>> Get([FromBody] ScheduleRequest request) { - if ((request.Groups == null || !request.Groups.Any()) && - (request.Disciplines == null || !request.Disciplines.Any()) && - (request.Professors == null || !request.Professors.Any()) && - (request.LectureHalls == null || !request.LectureHalls.Any())) + if ((request.Groups == null || request.Groups.Length == 0) && + (request.Disciplines == null || request.Disciplines.Length == 0) && + (request.Professors == null || request.Professors.Length == 0) && + (request.LectureHalls == null || request.LectureHalls.Length == 0)) { return BadRequest(new ErrorResponse() { @@ -60,7 +60,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = s.IsEven, Discipline = s.Discipline, DisciplineId = s.DisciplineId, - TypeOfOccupation = s.TypeOfOccupation, + TypeOfOccupations = s.TypeOfOccupations, Group = s.Group, GroupId = s.GroupId, LectureHalls = s.LectureHalls, @@ -117,7 +117,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = g.IsEven, Discipline = g.Discipline, DisciplineId = g.DisciplineId, - TypeOfOccupation = g.TypeOfOccupation, + TypeOfOccupations = g.TypeOfOccupations, LectureHalls = g.LectureHalls, LectureHallsId = g.LectureHallsId, Professors = g.Professors, @@ -176,7 +176,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = p.IsEven, Discipline = p.Discipline, DisciplineId = p.DisciplineId, - TypeOfOccupation = p.TypeOfOccupation, + TypeOfOccupations = p.TypeOfOccupations, Group = p.Group, GroupId = p.GroupId, LectureHalls = p.LectureHalls, @@ -235,7 +235,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = l.IsEven, Discipline = l.Discipline, DisciplineId = l.DisciplineId, - TypeOfOccupation = l.TypeOfOccupation, + TypeOfOccupations = l.TypeOfOccupations, Group = l.Group, GroupId = l.GroupId, Professors = l.Professors, @@ -273,7 +273,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = isEven, DisciplineIds = [id], GroupIds = groups, - LectureHallIds = [id], + LectureHallIds = lectureHalls, ProfessorIds = professors })).Schedules; @@ -288,7 +288,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 DayOfWeek = d.DayOfWeek, PairNumber = d.PairNumber, IsEven = d.IsEven, - TypeOfOccupation = d.TypeOfOccupation, + TypeOfOccupation = d.TypeOfOccupations, Group = d.Group, GroupId = d.GroupId, LectureHalls = d.LectureHalls, -- 2.43.0 From d8f2f51cd713c96a62b1a23ce11ab88d3f84cb60 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 19 May 2024 12:44:35 +0300 Subject: [PATCH 083/474] fix: error CS1061 --- .../GetScheduleList/GetScheduleListQueryHandler.cs | 10 +++++++--- .../Queries/GetScheduleList/ScheduleLookupDto.cs | 10 +++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs index ba08003..9c91c64 100644 --- a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs @@ -11,13 +11,15 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH { public async Task Handle(GetScheduleListQuery request, CancellationToken cancellationToken) { - var query = dbContext.Lessons.Include(l => l.LessonAssociations) + var query = dbContext.Lessons + .Include(l => l.LessonAssociations) .ThenInclude(la => la.LectureHall) .ThenInclude(lh => lh!.Campus) .Include(l => l.LessonAssociations) .ThenInclude(la => la.Professor) + .Include(l => l.LessonAssociations) + .ThenInclude(la => la.TypeOfOccupation) .Include(l => l.Group) - .Include(l => l.TypeOfOccupation) .Include(l => l.Discipline) .AsQueryable(); @@ -46,7 +48,9 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH DayOfWeek = l.DayOfWeek, PairNumber = l.PairNumber, IsEven = l.IsEven, - TypeOfOccupation = l.TypeOfOccupation!.ShortName, + TypeOfOccupations = l.LessonAssociations! + .Where(la => !string.IsNullOrEmpty(la.TypeOfOccupation?.ShortName)) + .Select(la => la.TypeOfOccupation!.ShortName), Discipline = l.Discipline!.Name, DisciplineId = l.DisciplineId, diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs index bd3808f..6e8cc1f 100644 --- a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs @@ -33,11 +33,6 @@ public class ScheduleLookupDto /// public required int DisciplineId { get; set; } - /// - /// Gets or sets the type of occupation. - /// - public required string TypeOfOccupation { get; set; } - /// /// Gets or sets the name of the group. /// @@ -48,6 +43,11 @@ public class ScheduleLookupDto /// public required int GroupId { get; set; } + /// + /// Gets or sets the type of occupation. + /// + public required IEnumerable TypeOfOccupations { get; set; } + /// /// Gets or sets the names of the lecture halls. /// -- 2.43.0 From 22579038d333270ec4aeac145ec2dab92e2cdc20 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 19 May 2024 13:52:10 +0300 Subject: [PATCH 084/474] fix: change BIT to BOOLEAN --- .../EntityTypeConfigurations/Schedule/LessonConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs b/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs index 561df32..8d9c8cd 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs +++ b/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs @@ -13,7 +13,7 @@ public class LessonConfiguration : IEntityTypeConfiguration builder.HasIndex(l => l.Id).IsUnique(); builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); - builder.Property(l => l.IsEven).HasColumnType("BIT").IsRequired(); + builder.Property(l => l.IsEven).HasColumnType("BOOLEAN").IsRequired(); builder.Property(l => l.DayOfWeek).HasColumnType("INTEGER").IsRequired(); builder.Property(l => l.PairNumber).HasColumnType("INTEGER").IsRequired(); builder.Property(l => l.IsExcludedWeeks).HasColumnType("BOOLEAN"); -- 2.43.0 From 0d3461b76956fd9be18d87e6cd889a44c04264ce Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 19 May 2024 13:53:25 +0300 Subject: [PATCH 085/474] feat: add specific weeks --- .../Schedule/GroupScheduleResponse.cs | 23 +++++++++++++++ .../Schedule/LectureHallScheduleResponse.cs | 23 +++++++++++++++ .../Schedule/ProfessorScheduleResponse.cs | 23 +++++++++++++++ ApiDto/Responses/Schedule/ScheduleResponse.cs | 23 +++++++++++++++ .../GetScheduleListQueryHandler.cs | 28 ++++++++++--------- .../GetScheduleList/ScheduleLookupDto.cs | 10 +++++++ Endpoint/Controllers/V1/ScheduleController.cs | 8 ++++++ 7 files changed, 125 insertions(+), 13 deletions(-) diff --git a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs index c85778c..c0a7f42 100644 --- a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs @@ -39,6 +39,29 @@ public class GroupScheduleInfo [Required] public required int DisciplineId { get; set; } + /// + /// Gets or sets exclude or include weeks for a specific discipline. + /// + /// + /// If is , then the values in show the weeks when there will be no discipline. + ///
+ /// If is , then the values in indicate the weeks during which a particular discipline will be studied. + ///
+ /// If is , then there are no specific + ///
+ /// + + public bool? IsExcludedWeeks { get; set; } + /// + /// The week numbers required for the correct display of the schedule. + ///
+ /// Whether there will be during the week or not depends on the property. + ///
+ /// + /// To get the current week's number, use other queries. + /// + public IEnumerable? Weeks { get; set; } + /// /// Gets or sets the type of occupation for the schedule entry. /// diff --git a/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs b/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs index 294876f..84adb3c 100644 --- a/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs @@ -39,6 +39,29 @@ public class LectureHallScheduleInfo [Required] public required int DisciplineId { get; set; } + /// + /// Gets or sets exclude or include weeks for a specific discipline. + /// + /// + /// If is , then the values in show the weeks when there will be no discipline. + ///
+ /// If is , then the values in indicate the weeks during which a particular discipline will be studied. + ///
+ /// If is , then there are no specific + ///
+ /// + + public bool? IsExcludedWeeks { get; set; } + /// + /// The week numbers required for the correct display of the schedule. + ///
+ /// Whether there will be during the week or not depends on the property. + ///
+ /// + /// To get the current week's number, use other queries. + /// + public IEnumerable? Weeks { get; set; } + /// /// Gets or sets the type of occupation for the schedule entry. /// diff --git a/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs b/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs index 07a7471..b25711e 100644 --- a/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs @@ -39,6 +39,29 @@ public class ProfessorScheduleInfo [Required] public required int DisciplineId { get; set; } + /// + /// Gets or sets exclude or include weeks for a specific discipline. + /// + /// + /// If is , then the values in show the weeks when there will be no discipline. + ///
+ /// If is , then the values in indicate the weeks during which a particular discipline will be studied. + ///
+ /// If is , then there are no specific + ///
+ /// + + public bool? IsExcludedWeeks { get; set; } + /// + /// The week numbers required for the correct display of the schedule. + ///
+ /// Whether there will be during the week or not depends on the property. + ///
+ /// + /// To get the current week's number, use other queries. + /// + public IEnumerable? Weeks { get; set; } + /// /// Gets or sets the type of occupation for the schedule entry. /// diff --git a/ApiDto/Responses/Schedule/ScheduleResponse.cs b/ApiDto/Responses/Schedule/ScheduleResponse.cs index 8698ed7..599cc3e 100644 --- a/ApiDto/Responses/Schedule/ScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/ScheduleResponse.cs @@ -39,6 +39,29 @@ public class ScheduleResponse [Required] public required int DisciplineId { get; set; } + /// + /// Gets or sets exclude or include weeks for a specific discipline. + /// + /// + /// If is , then the values in show the weeks when there will be no discipline. + ///
+ /// If is , then the values in indicate the weeks during which a particular discipline will be studied. + ///
+ /// If is , then there are no specific + ///
+ /// + + public bool? IsExcludedWeeks { get; set; } + /// + /// The week numbers required for the correct display of the schedule. + ///
+ /// Whether there will be during the week or not depends on the property. + ///
+ /// + /// To get the current week's number, use other queries. + /// + public IEnumerable? Weeks { get; set; } + /// /// Gets or sets the type of occupation for the schedule entry. /// diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs index 9c91c64..63a8a4d 100644 --- a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs @@ -11,17 +11,7 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH { public async Task Handle(GetScheduleListQuery request, CancellationToken cancellationToken) { - var query = dbContext.Lessons - .Include(l => l.LessonAssociations) - .ThenInclude(la => la.LectureHall) - .ThenInclude(lh => lh!.Campus) - .Include(l => l.LessonAssociations) - .ThenInclude(la => la.Professor) - .Include(l => l.LessonAssociations) - .ThenInclude(la => la.TypeOfOccupation) - .Include(l => l.Group) - .Include(l => l.Discipline) - .AsQueryable(); + var query = dbContext.Lessons.AsQueryable(); if (request.IsEven != null) query = query.Where(l => l.IsEven == request.IsEven); @@ -41,7 +31,17 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH l.LessonAssociations!.Any(la => la.ProfessorId != null && request.ProfessorIds.Contains(la.ProfessorId.Value))); - var data = await query.ToArrayAsync(cancellationToken); + var data = await query + .Include(lesson => lesson.Discipline!) + .Include(lesson => lesson.SpecificWeeks) + .Include(lesson => lesson.Group!) + .Include(lesson => lesson.LessonAssociations!) + .ThenInclude(lessonAssociation => lessonAssociation.TypeOfOccupation!) + .Include(lesson => lesson.LessonAssociations!) + .ThenInclude(lessonAssociation => lessonAssociation.Professor) + .Include(lesson => lesson.LessonAssociations!) + .ThenInclude(lessonAssociation => lessonAssociation.LectureHall) + .ThenInclude(lectureHall => lectureHall!.Campus).ToListAsync(cancellationToken); var result = data.Select(l => new ScheduleLookupDto() { @@ -55,6 +55,9 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH Discipline = l.Discipline!.Name, DisciplineId = l.DisciplineId, + IsExcludedWeeks = l.IsExcludedWeeks, + Weeks = l.SpecificWeeks?.Select(w => w.WeekNumber), + Group = l.Group!.Name, GroupId = l.GroupId, @@ -72,7 +75,6 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH .Where(la => la.LectureHall?.Campus != null) .Select(la => la.LectureHall?.CampusId), - Professors = l.LessonAssociations! .Where(la => !string.IsNullOrEmpty(la.Professor?.Name)) .Select(la => la.Professor?.Name), diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs index 6e8cc1f..b7a3802 100644 --- a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs +++ b/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs @@ -33,6 +33,16 @@ public class ScheduleLookupDto ///
public required int DisciplineId { get; set; } + /// + /// Gets or sets exclude or include weeks for a specific discipline. + /// + public bool? IsExcludedWeeks { get; set; } + + /// + /// The week numbers required for the correct display of the schedule. + /// + public IEnumerable? Weeks { get; set; } + /// /// Gets or sets the name of the group. /// diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 3e9a5ed..1500efa 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -60,6 +60,8 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = s.IsEven, Discipline = s.Discipline, DisciplineId = s.DisciplineId, + IsExcludedWeeks = s.IsExcludedWeeks, + Weeks = s.Weeks, TypeOfOccupations = s.TypeOfOccupations, Group = s.Group, GroupId = s.GroupId, @@ -117,6 +119,8 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = g.IsEven, Discipline = g.Discipline, DisciplineId = g.DisciplineId, + IsExcludedWeeks = g.IsExcludedWeeks, + Weeks = g.Weeks, TypeOfOccupations = g.TypeOfOccupations, LectureHalls = g.LectureHalls, LectureHallsId = g.LectureHallsId, @@ -176,6 +180,8 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = p.IsEven, Discipline = p.Discipline, DisciplineId = p.DisciplineId, + IsExcludedWeeks = p.IsExcludedWeeks, + Weeks = p.Weeks, TypeOfOccupations = p.TypeOfOccupations, Group = p.Group, GroupId = p.GroupId, @@ -235,6 +241,8 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 IsEven = l.IsEven, Discipline = l.Discipline, DisciplineId = l.DisciplineId, + IsExcludedWeeks = l.IsExcludedWeeks, + Weeks = l.Weeks, TypeOfOccupations = l.TypeOfOccupations, Group = l.Group, GroupId = l.GroupId, -- 2.43.0 From dfdc2ec1090967e595cdad8d0ce0e3b842af9b57 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:28:24 +0300 Subject: [PATCH 086/474] feat: add interface for check configured settings --- Endpoint/Configuration/General/Interfaces/IIsConfigured.cs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Endpoint/Configuration/General/Interfaces/IIsConfigured.cs diff --git a/Endpoint/Configuration/General/Interfaces/IIsConfigured.cs b/Endpoint/Configuration/General/Interfaces/IIsConfigured.cs new file mode 100644 index 0000000..60c09e0 --- /dev/null +++ b/Endpoint/Configuration/General/Interfaces/IIsConfigured.cs @@ -0,0 +1,6 @@ +namespace Mirea.Api.Endpoint.Configuration.General.Interfaces; + +public interface IIsConfigured +{ + bool IsConfigured(); +} \ No newline at end of file -- 2.43.0 From c06ed8b4794d986d578af49627b97a3a665b82dc Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:30:42 +0300 Subject: [PATCH 087/474] refactor: move files --- Endpoint/Configuration/{ => Swagger}/ConfigureSwaggerOptions.cs | 2 +- Endpoint/Configuration/{ => Swagger}/SwaggerDefaultValues.cs | 2 +- Endpoint/Program.cs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename Endpoint/Configuration/{ => Swagger}/ConfigureSwaggerOptions.cs (96%) rename Endpoint/Configuration/{ => Swagger}/SwaggerDefaultValues.cs (97%) diff --git a/Endpoint/Configuration/ConfigureSwaggerOptions.cs b/Endpoint/Configuration/Swagger/ConfigureSwaggerOptions.cs similarity index 96% rename from Endpoint/Configuration/ConfigureSwaggerOptions.cs rename to Endpoint/Configuration/Swagger/ConfigureSwaggerOptions.cs index 39280bb..bf47325 100644 --- a/Endpoint/Configuration/ConfigureSwaggerOptions.cs +++ b/Endpoint/Configuration/Swagger/ConfigureSwaggerOptions.cs @@ -5,7 +5,7 @@ using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using System; -namespace Mirea.Api.Endpoint.Configuration; +namespace Mirea.Api.Endpoint.Configuration.Swagger; public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions { diff --git a/Endpoint/Configuration/SwaggerDefaultValues.cs b/Endpoint/Configuration/Swagger/SwaggerDefaultValues.cs similarity index 97% rename from Endpoint/Configuration/SwaggerDefaultValues.cs rename to Endpoint/Configuration/Swagger/SwaggerDefaultValues.cs index cefb307..581393e 100644 --- a/Endpoint/Configuration/SwaggerDefaultValues.cs +++ b/Endpoint/Configuration/Swagger/SwaggerDefaultValues.cs @@ -6,7 +6,7 @@ using System; using System.Linq; using System.Text.Json; -namespace Mirea.Api.Endpoint.Configuration; +namespace Mirea.Api.Endpoint.Configuration.Swagger; public class SwaggerDefaultValues : IOperationFilter { diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 3607500..4674bc9 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -10,6 +10,7 @@ using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; using Mirea.Api.Endpoint.Configuration; using Mirea.Api.Endpoint.Properties; +using Mirea.Api.Endpoint.Configuration.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.Collections; -- 2.43.0 From 41689bca7fa711716cbee3f39abee24f8b65a694 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:36:18 +0300 Subject: [PATCH 088/474] feat: add attribute for required configuration --- .../General/Attributes/RequiredSettingsAttribute.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Endpoint/Configuration/General/Attributes/RequiredSettingsAttribute.cs diff --git a/Endpoint/Configuration/General/Attributes/RequiredSettingsAttribute.cs b/Endpoint/Configuration/General/Attributes/RequiredSettingsAttribute.cs new file mode 100644 index 0000000..dcb13ac --- /dev/null +++ b/Endpoint/Configuration/General/Attributes/RequiredSettingsAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace Mirea.Api.Endpoint.Configuration.General.Attributes; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public class RequiredSettingsAttribute : Attribute; + +// todo: only with IIsConfigured. If possible add Roslyn Analyzer later \ No newline at end of file -- 2.43.0 From 266e66a35c7ed05fe09ba34ef70116b53e46af85 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:38:24 +0300 Subject: [PATCH 089/474] feat: expand the configuration functionality --- .../Configuration/General/GeneralConfig.cs | 14 ++++++ .../General/Settings/CacheSettings.cs | 23 ++++++++++ .../General/Settings/DbSettings.cs | 22 +++++++++ .../General/Settings/EmailSettings.cs | 23 ++++++++++ .../General/Settings/LogSettings.cs | 19 ++++++++ .../General/Settings/ScheduleSettings.cs | 45 +++++++++++++++++++ Endpoint/Properties/Settings.cs | 36 --------------- 7 files changed, 146 insertions(+), 36 deletions(-) create mode 100644 Endpoint/Configuration/General/GeneralConfig.cs create mode 100644 Endpoint/Configuration/General/Settings/CacheSettings.cs create mode 100644 Endpoint/Configuration/General/Settings/DbSettings.cs create mode 100644 Endpoint/Configuration/General/Settings/EmailSettings.cs create mode 100644 Endpoint/Configuration/General/Settings/LogSettings.cs create mode 100644 Endpoint/Configuration/General/Settings/ScheduleSettings.cs delete mode 100644 Endpoint/Properties/Settings.cs diff --git a/Endpoint/Configuration/General/GeneralConfig.cs b/Endpoint/Configuration/General/GeneralConfig.cs new file mode 100644 index 0000000..60f98a1 --- /dev/null +++ b/Endpoint/Configuration/General/GeneralConfig.cs @@ -0,0 +1,14 @@ +using Mirea.Api.Endpoint.Configuration.General.Settings; + +namespace Mirea.Api.Endpoint.Configuration.General; + +public class GeneralConfig +{ + public const string FilePath = "Settings.json"; + + public DbSettings? DbSettings { get; set; } + public CacheSettings? CacheSettings { get; set; } + public ScheduleSettings? ScheduleSettings { get; set; } + public EmailSettings? EmailSettings { get; set; } + public LogSettings? LogSettings { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Configuration/General/Settings/CacheSettings.cs b/Endpoint/Configuration/General/Settings/CacheSettings.cs new file mode 100644 index 0000000..a0a7802 --- /dev/null +++ b/Endpoint/Configuration/General/Settings/CacheSettings.cs @@ -0,0 +1,23 @@ +using Mirea.Api.Endpoint.Configuration.General.Attributes; +using Mirea.Api.Endpoint.Configuration.General.Interfaces; + +namespace Mirea.Api.Endpoint.Configuration.General.Settings; + +[RequiredSettings] +public class CacheSettings : IIsConfigured +{ + public enum CacheEnum + { + Memcached, + Redis + } + + public CacheEnum TypeDatabase { get; set; } + public string? ConnectionString { get; set; } + + public bool IsConfigured() + { + return TypeDatabase == CacheEnum.Memcached || + !string.IsNullOrEmpty(ConnectionString); + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/General/Settings/DbSettings.cs b/Endpoint/Configuration/General/Settings/DbSettings.cs new file mode 100644 index 0000000..f8ae35e --- /dev/null +++ b/Endpoint/Configuration/General/Settings/DbSettings.cs @@ -0,0 +1,22 @@ +using Mirea.Api.Endpoint.Configuration.General.Attributes; +using Mirea.Api.Endpoint.Configuration.General.Interfaces; + +namespace Mirea.Api.Endpoint.Configuration.General.Settings; + +[RequiredSettings] +public class DbSettings : IIsConfigured +{ + public enum DatabaseEnum + { + Mysql, + Sqlite, + PostgresSql + } + public DatabaseEnum TypeDatabase { get; set; } + public required string ConnectionStringSql { get; set; } + + public bool IsConfigured() + { + return !string.IsNullOrEmpty(ConnectionStringSql); + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/General/Settings/EmailSettings.cs b/Endpoint/Configuration/General/Settings/EmailSettings.cs new file mode 100644 index 0000000..bdd5179 --- /dev/null +++ b/Endpoint/Configuration/General/Settings/EmailSettings.cs @@ -0,0 +1,23 @@ +using Mirea.Api.Endpoint.Configuration.General.Interfaces; + +namespace Mirea.Api.Endpoint.Configuration.General.Settings; + +public class EmailSettings : IIsConfigured +{ + public string? Server { get; set; } + public string? User { get; set; } + public string? Password { get; set; } + public string? From { get; set; } + public int? Port { get; set; } + public bool? Ssl { get; set; } + + public bool IsConfigured() + { + return !string.IsNullOrEmpty(Server) && + !string.IsNullOrEmpty(User) && + !string.IsNullOrEmpty(Password) && + !string.IsNullOrEmpty(From) && + Port.HasValue && + Ssl.HasValue; + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/General/Settings/LogSettings.cs b/Endpoint/Configuration/General/Settings/LogSettings.cs new file mode 100644 index 0000000..4a42d28 --- /dev/null +++ b/Endpoint/Configuration/General/Settings/LogSettings.cs @@ -0,0 +1,19 @@ +using Mirea.Api.Endpoint.Configuration.General.Attributes; +using Mirea.Api.Endpoint.Configuration.General.Interfaces; + +namespace Mirea.Api.Endpoint.Configuration.General.Settings; + +[RequiredSettings] +public class LogSettings : IIsConfigured +{ + public bool EnableLogToFile { get; set; } + public string? LogFilePath { get; set; } + public string? LogFileName { get; set; } + + public bool IsConfigured() + { + return !EnableLogToFile || + !string.IsNullOrEmpty(LogFilePath) && + !string.IsNullOrEmpty(LogFileName); + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/General/Settings/ScheduleSettings.cs b/Endpoint/Configuration/General/Settings/ScheduleSettings.cs new file mode 100644 index 0000000..a45b41f --- /dev/null +++ b/Endpoint/Configuration/General/Settings/ScheduleSettings.cs @@ -0,0 +1,45 @@ +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.General.Settings; + +[RequiredSettings] +public class ScheduleSettings : IIsConfigured +{ + public struct PairPeriodTime + { + public TimeOnly Start { get; set; } + public TimeOnly End { get; set; } + + public PairPeriodTime(TimeOnly t1, TimeOnly t2) + { + if (t1 > t2) + { + Start = t2; + End = t1; + } + else + { + Start = t1; + End = t2; + } + } + + public PairPeriodTime(Dto.Common.PairPeriodTime time) : this(time.Start, time.End) { } + } + + public required string CronUpdateSchedule { get; set; } + public DateOnly StartTerm { get; set; } + public required IDictionary PairPeriod { get; set; } + + public bool IsConfigured() + { + return !string.IsNullOrEmpty(CronUpdateSchedule) && + StartTerm != default && + PairPeriod.Count != 0 && + PairPeriod.Any(); + } +} \ No newline at end of file diff --git a/Endpoint/Properties/Settings.cs b/Endpoint/Properties/Settings.cs deleted file mode 100644 index 1bee892..0000000 --- a/Endpoint/Properties/Settings.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Mirea.Api.DataAccess.Persistence.Properties; - -namespace Mirea.Api.Endpoint.Properties; - -public class EmailSettings -{ - public string? Server { get; set; } - public string? User { get; set; } - public string? Password { get; set; } - public string? From { get; set; } - public int? Port { get; set; } - public bool? Ssl { get; set; } -} - -public class LogSettings -{ - public bool EnableLogToFile { get; set; } - public string? LogFilePath { get; set; } - public string? LogFileName { get; set; } -} - -public class ScheduleSettings -{ - // Every 6 hours - public string CronUpdateSchedule { get; set; } = "0 0 0/6 * * *"; -} - -public class Settings -{ - public const string FilePath = "Settings.json"; - - public EmailSettings? EmailSettings { get; set; } - public LogSettings? LogSettings { get; set; } - public DbSettings? DbSettings { get; set; } - public ScheduleSettings? ScheduleSettings { get; set; } -} \ No newline at end of file -- 2.43.0 From 202098b723a15e8baed80e3234996c0739608079 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:43:24 +0300 Subject: [PATCH 090/474] refactor: move the functionality to create a persistence database on Enpoint --- Persistence/DependencyInjection.cs | 54 ++++++++-------------------- Persistence/Properties/DbSettings.cs | 15 -------- 2 files changed, 14 insertions(+), 55 deletions(-) delete mode 100644 Persistence/Properties/DbSettings.cs diff --git a/Persistence/DependencyInjection.cs b/Persistence/DependencyInjection.cs index ed6f554..f98052c 100644 --- a/Persistence/DependencyInjection.cs +++ b/Persistence/DependencyInjection.cs @@ -1,54 +1,26 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -using Mirea.Api.DataAccess.Persistence.Properties; -using System; -using System.Collections.Generic; namespace Mirea.Api.DataAccess.Persistence; public static class DependencyInjection { - public static IServiceCollection AddPersistence(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddPersistence(this IServiceCollection services, string connection) { - var settings = configuration.GetSection(nameof(DbSettings)).Get(); - var connection = settings?.ConnectionStringSql; + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); + services.AddDbContext(DbConfig); - Dictionary> dbConfigurations = new() - { - { - DatabaseEnum.Mysql, - options => options.UseMySql(connection, ServerVersion.AutoDetect(connection)) - }, - { - DatabaseEnum.Sqlite, - options => options.UseSqlite(connection) - }, - { - DatabaseEnum.PostgresSql, - options => options.UseNpgsql(connection) - } - }; - - if (dbConfigurations.TryGetValue((DatabaseEnum)settings?.TypeDatabase!, out var dbConfig)) - { - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - services.AddDbContext(dbConfig); - - services.AddDbContext(dbConfig); - } - else - throw new NotSupportedException("Unsupported database type"); + services.AddDbContext(DbConfig); services.AddScoped(provider => provider.GetService()!); services.AddScoped(provider => provider.GetService()!); @@ -62,5 +34,7 @@ public static class DependencyInjection services.AddScoped(provider => provider.GetService()!); return services; + + void DbConfig(DbContextOptionsBuilder options) => options.UseSqlite(connection); } } \ No newline at end of file diff --git a/Persistence/Properties/DbSettings.cs b/Persistence/Properties/DbSettings.cs deleted file mode 100644 index 7ee06d9..0000000 --- a/Persistence/Properties/DbSettings.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Mirea.Api.DataAccess.Persistence.Properties; - -public enum DatabaseEnum -{ - Mysql, - Sqlite, - PostgresSql -} -public class DbSettings -{ - public bool IsDoneConfiguration { get; set; } - public DatabaseEnum TypeDatabase { get; set; } - public required string ConnectionStringSql { get; set; } - public DatabaseEnum? MigrateTo { get; set; } -} \ No newline at end of file -- 2.43.0 From 99ecc4af5c7dbad51f81c9e58ce2d31972706991 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:45:31 +0300 Subject: [PATCH 091/474] fix: get connection string from configuration --- Endpoint/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 4674bc9..7c34446 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -45,7 +45,7 @@ public class Program builder.Configuration.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true); builder.Services.AddApplication(); - builder.Services.AddPersistence(builder.Configuration); + builder.Services.AddPersistence(builder.Configuration.Get()?.DbSettings?.ConnectionStringSql ?? string.Empty); builder.Services.AddControllers(); builder.Services.AddCors(options => -- 2.43.0 From 817f9d2308dd8fd965747df4cfeaba7078240aed Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:47:25 +0300 Subject: [PATCH 092/474] feat: add default path --- .env | 19 +++++++++++++++++++ Backend.sln | 14 ++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..af67fc3 --- /dev/null +++ b/.env @@ -0,0 +1,19 @@ +# The .env configuration file +# Please DO NOT share this file, it contains confidential data. + +# All variables are specified according to this rule: +# DESCRIPTION - information about what the variable is responsible for +# TYPE - the type of the variable (string, boolean, etc.) +# Any additional information +# SOME_ENV_CODE=data - default data. If specified, then the variable is optional + +# General + +# The path to save the data. +# string +# (optional) +# Saving logs (if the full path is not specified), +# databases (if Sqlite) and other data that should be saved in a place other than the place where the program is launched. +# REQUIRED if the application is inside the container +# If you want to change this value, you need to change the values in Settings.json and move the file itself to the desired location. +PATH_TO_SAVE= \ No newline at end of file diff --git a/Backend.sln b/Backend.sln index 383bd42..95ebfb7 100644 --- a/Backend.sln +++ b/Backend.sln @@ -10,22 +10,20 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", "Elements of the solution", "{3E087889-A4A0-4A55-A07D-7D149A5BC928}" ProjectSection(SolutionItems) = preProject .dockerignore = .dockerignore + .env = .env .gitattributes = .gitattributes .gitignore = .gitignore Dockerfile = Dockerfile LICENSE.txt = LICENSE.txt README.md = README.md + .gitea\workflows\test.yaml = .gitea\workflows\test.yaml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}" - ProjectSection(ProjectDependencies) = postProject - {C27FB5CD-6A70-4FB2-847A-847B34806902} = {C27FB5CD-6A70-4FB2-847A-847B34806902} - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\Persistence.csproj", "{4C1E558F-633F-438E-AC3A-61CDDED917C5}" - ProjectSection(ProjectDependencies) = postProject - {E7F0A4D4-B032-4BB9-9526-1AF688F341A4} = {E7F0A4D4-B032-4BB9-9526-1AF688F341A4} - EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiDto", "ApiDto\ApiDto.csproj", "{0335FA36-E137-453F-853B-916674C168FE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -49,6 +47,10 @@ Global {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Release|Any CPU.Build.0 = Release|Any CPU + {0335FA36-E137-453F-853B-916674C168FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0335FA36-E137-453F-853B-916674C168FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0335FA36-E137-453F-853B-916674C168FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0335FA36-E137-453F-853B-916674C168FE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE -- 2.43.0 From d7299a1afdb2a8a3f25e52b46f1415f2dc56e604 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:48:23 +0300 Subject: [PATCH 093/474] feat: add wrapper for Path.Combine with default path --- Endpoint/Common/Services/PathBuilder.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Endpoint/Common/Services/PathBuilder.cs diff --git a/Endpoint/Common/Services/PathBuilder.cs b/Endpoint/Common/Services/PathBuilder.cs new file mode 100644 index 0000000..7df60d8 --- /dev/null +++ b/Endpoint/Common/Services/PathBuilder.cs @@ -0,0 +1,11 @@ +using System; +using System.IO; +using System.Linq; + +namespace Mirea.Api.Endpoint.Common.Services; + +public static class PathBuilder +{ + public static string PathToSave => Environment.GetEnvironmentVariable("PATH_TO_SAVE") ?? Directory.GetCurrentDirectory(); + public static string Combine(params string[] paths) => Path.Combine([.. paths.Prepend(PathToSave)]); +} \ No newline at end of file -- 2.43.0 From fb736a1c34d6a0522bc8d8b3bb702711e5668200 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:49:40 +0300 Subject: [PATCH 094/474] feat: use path wrapper --- Endpoint/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 7c34446..bb7b69b 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; +using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration; using Mirea.Api.Endpoint.Properties; using Mirea.Api.Endpoint.Configuration.Swagger; @@ -42,7 +43,8 @@ public class Program var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddConfiguration(ConfigureEnvironment()); - builder.Configuration.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile(PathBuilder.Combine(GeneralConfig.FilePath), optional: true, reloadOnChange: true); + builder.Services.Configure(builder.Configuration); builder.Services.AddApplication(); builder.Services.AddPersistence(builder.Configuration.Get()?.DbSettings?.ConnectionStringSql ?? string.Empty); -- 2.43.0 From 7a1281692e406f54b0b8c9d6b5a5c92415df787e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:51:40 +0300 Subject: [PATCH 095/474] fix: it is correct to delete comments --- Endpoint/Configuration/EnvironmentManager.cs | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Endpoint/Configuration/EnvironmentManager.cs b/Endpoint/Configuration/EnvironmentManager.cs index cc2b201..c21d59e 100644 --- a/Endpoint/Configuration/EnvironmentManager.cs +++ b/Endpoint/Configuration/EnvironmentManager.cs @@ -5,20 +5,32 @@ namespace Mirea.Api.Endpoint.Configuration; internal static class EnvironmentManager { - public static void LoadEnvironment(string filePath) + public static void LoadEnvironment(string envFile) { - if (!File.Exists(filePath)) return; + if (!File.Exists(envFile)) return; - foreach (var line in File.ReadAllLines(filePath)) + foreach (var line in File.ReadAllLines(envFile)) { - var parts = line.Split( + if (string.IsNullOrEmpty(line)) continue; + + var commentIndex = line.IndexOf('#', StringComparison.Ordinal); + + string arg = line; + + if (commentIndex != -1) + arg = arg.Remove(commentIndex, arg.Length - commentIndex); + + var parts = arg.Split( '=', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length > 2) + parts = [parts[0], string.Join("=", parts[1..])]; + if (parts.Length != 2) continue; - Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1][..(parts[1].Contains('#') ? parts[1].IndexOf('#') : parts[1].Length)].Trim()); + Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1].Trim()); } } } \ No newline at end of file -- 2.43.0 From 36a78a82849b1965f28db490a2d30a45b6b85941 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:53:52 +0300 Subject: [PATCH 096/474] fix: add using Configuration.General --- Endpoint/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index bb7b69b..7101c7e 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -10,7 +10,7 @@ using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration; -using Mirea.Api.Endpoint.Properties; +using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using System; -- 2.43.0 From 0e9bb04b96025aafe9ed32e1de46cae0c902eae8 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:56:25 +0300 Subject: [PATCH 097/474] feat: add setup token --- Endpoint/Common/Interfaces/ISetupToken.cs | 9 ++++++ .../General/SetupTokenService.cs | 28 +++++++++++++++++++ Endpoint/Program.cs | 2 ++ 3 files changed, 39 insertions(+) create mode 100644 Endpoint/Common/Interfaces/ISetupToken.cs create mode 100644 Endpoint/Configuration/General/SetupTokenService.cs diff --git a/Endpoint/Common/Interfaces/ISetupToken.cs b/Endpoint/Common/Interfaces/ISetupToken.cs new file mode 100644 index 0000000..462b759 --- /dev/null +++ b/Endpoint/Common/Interfaces/ISetupToken.cs @@ -0,0 +1,9 @@ +using System; + +namespace Mirea.Api.Endpoint.Common.Interfaces; + +public interface ISetupToken +{ + bool MatchToken(ReadOnlySpan token); + void SetToken(ReadOnlySpan token); +} \ No newline at end of file diff --git a/Endpoint/Configuration/General/SetupTokenService.cs b/Endpoint/Configuration/General/SetupTokenService.cs new file mode 100644 index 0000000..4cc1216 --- /dev/null +++ b/Endpoint/Configuration/General/SetupTokenService.cs @@ -0,0 +1,28 @@ +using Mirea.Api.Endpoint.Common.Interfaces; +using System; + +namespace Mirea.Api.Endpoint.Configuration.General; + +public class SetupTokenService : ISetupToken +{ + public ReadOnlyMemory? Token { get; private set; } + + public bool MatchToken(ReadOnlySpan token) + { + if (Token == null || token.Length != Token.Value.Length) + return false; + + var token2 = Token.Value.Span; + + int result = 0; + for (int i = 0; i < Token.Value.Length; i++) + result |= token2[i] ^ token[i]; + + return result == 0; + } + + public void SetToken(ReadOnlySpan token) + { + Token = token.ToArray(); + } +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 7101c7e..4d79328 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; +using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration; using Mirea.Api.Endpoint.Configuration.General; @@ -50,6 +51,7 @@ public class Program builder.Services.AddPersistence(builder.Configuration.Get()?.DbSettings?.ConnectionStringSql ?? string.Empty); builder.Services.AddControllers(); + builder.Services.AddSingleton(); builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policy => -- 2.43.0 From baedc667b7faacb6d5b9bf1df7235a5d85b143ed Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:57:54 +0300 Subject: [PATCH 098/474] feat: add PairPeriod for ApiDto --- ApiDto/Common/PairPeriodTime.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 ApiDto/Common/PairPeriodTime.cs diff --git a/ApiDto/Common/PairPeriodTime.cs b/ApiDto/Common/PairPeriodTime.cs new file mode 100644 index 0000000..89b0732 --- /dev/null +++ b/ApiDto/Common/PairPeriodTime.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Common; + +/// +/// Represents a pair of time periods. +/// +public class PairPeriodTime +{ + /// + /// Gets or sets the start time of the period. + /// + [Required] + public TimeOnly Start { get; set; } + + /// + /// Gets or sets the end time of the period. + /// + [Required] + public TimeOnly End { get; set; } +} \ No newline at end of file -- 2.43.0 From 7b779463bbd2711c82f374f5b90e14e1ea077d39 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 06:59:28 +0300 Subject: [PATCH 099/474] feat: add converter from Dto.PairPeriodTime toEnpoint.PairPeriodTime and vice versa --- Endpoint/Common/Services/PairPeriodTimeConverter.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Endpoint/Common/Services/PairPeriodTimeConverter.cs diff --git a/Endpoint/Common/Services/PairPeriodTimeConverter.cs b/Endpoint/Common/Services/PairPeriodTimeConverter.cs new file mode 100644 index 0000000..ad46295 --- /dev/null +++ b/Endpoint/Common/Services/PairPeriodTimeConverter.cs @@ -0,0 +1,13 @@ +using Mirea.Api.Endpoint.Configuration.General.Settings; +using System.Collections.Generic; +using System.Linq; + +namespace Mirea.Api.Endpoint.Common.Services; + +public static class PairPeriodTimeConverter +{ + public static Dictionary ConvertToDto(this IDictionary pairPeriod) => + pairPeriod.ToDictionary(kvp => kvp.Key, kvp => new Dto.Common.PairPeriodTime { Start = kvp.Value.Start, End = kvp.Value.End }); + + public static Dictionary ConvertFromDto(this IDictionary pairPeriod) => pairPeriod.ToDictionary(kvp => kvp.Key, kvp => new ScheduleSettings.PairPeriodTime(kvp.Value.Start, kvp.Value.End)); +} -- 2.43.0 From d1a806545d9f5e746a204a82aadca7b33db76149 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:01:23 +0300 Subject: [PATCH 100/474] feat: add maintenance mode --- .../IMaintenanceModeNotConfigureService.cs | 8 ++++++++ .../Common/Interfaces/IMaintenanceModeService.cs | 10 ++++++++++ .../Services/MaintenanceModeNotConfigureService.cs | 11 +++++++++++ Endpoint/Common/Services/MaintenanceModeService.cs | 14 ++++++++++++++ Endpoint/Program.cs | 2 ++ 5 files changed, 45 insertions(+) create mode 100644 Endpoint/Common/Interfaces/IMaintenanceModeNotConfigureService.cs create mode 100644 Endpoint/Common/Interfaces/IMaintenanceModeService.cs create mode 100644 Endpoint/Common/Services/MaintenanceModeNotConfigureService.cs create mode 100644 Endpoint/Common/Services/MaintenanceModeService.cs diff --git a/Endpoint/Common/Interfaces/IMaintenanceModeNotConfigureService.cs b/Endpoint/Common/Interfaces/IMaintenanceModeNotConfigureService.cs new file mode 100644 index 0000000..e2e6a5d --- /dev/null +++ b/Endpoint/Common/Interfaces/IMaintenanceModeNotConfigureService.cs @@ -0,0 +1,8 @@ +namespace Mirea.Api.Endpoint.Common.Interfaces; + +public interface IMaintenanceModeNotConfigureService +{ + bool IsMaintenanceMode { get; } + + void DisableMaintenanceMode(); +} \ No newline at end of file diff --git a/Endpoint/Common/Interfaces/IMaintenanceModeService.cs b/Endpoint/Common/Interfaces/IMaintenanceModeService.cs new file mode 100644 index 0000000..7f2d7cb --- /dev/null +++ b/Endpoint/Common/Interfaces/IMaintenanceModeService.cs @@ -0,0 +1,10 @@ +namespace Mirea.Api.Endpoint.Common.Interfaces; + +public interface IMaintenanceModeService +{ + bool IsMaintenanceMode { get; } + + void EnableMaintenanceMode(); + + void DisableMaintenanceMode(); +} \ No newline at end of file diff --git a/Endpoint/Common/Services/MaintenanceModeNotConfigureService.cs b/Endpoint/Common/Services/MaintenanceModeNotConfigureService.cs new file mode 100644 index 0000000..b11516b --- /dev/null +++ b/Endpoint/Common/Services/MaintenanceModeNotConfigureService.cs @@ -0,0 +1,11 @@ +using Mirea.Api.Endpoint.Common.Interfaces; + +namespace Mirea.Api.Endpoint.Common.Services; + +public class MaintenanceModeNotConfigureService : IMaintenanceModeNotConfigureService +{ + public bool IsMaintenanceMode { get; private set; } = true; + + public void DisableMaintenanceMode() => + IsMaintenanceMode = false; +} \ No newline at end of file diff --git a/Endpoint/Common/Services/MaintenanceModeService.cs b/Endpoint/Common/Services/MaintenanceModeService.cs new file mode 100644 index 0000000..a967fd1 --- /dev/null +++ b/Endpoint/Common/Services/MaintenanceModeService.cs @@ -0,0 +1,14 @@ +using Mirea.Api.Endpoint.Common.Interfaces; + +namespace Mirea.Api.Endpoint.Common.Services; + +public class MaintenanceModeService : IMaintenanceModeService +{ + public bool IsMaintenanceMode { get; private set; }; + + public void EnableMaintenanceMode() => + IsMaintenanceMode = true; + + public void DisableMaintenanceMode() => + IsMaintenanceMode = false; +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 4d79328..a238bbf 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -51,6 +51,8 @@ public class Program builder.Services.AddPersistence(builder.Configuration.Get()?.DbSettings?.ConnectionStringSql ?? string.Empty); builder.Services.AddControllers(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddCors(options => { -- 2.43.0 From b62ddc9015ae2a0fe72413b93c3649bfab41f300 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:01:58 +0300 Subject: [PATCH 101/474] fix: delete ';' from property --- Endpoint/Common/Services/MaintenanceModeService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Common/Services/MaintenanceModeService.cs b/Endpoint/Common/Services/MaintenanceModeService.cs index a967fd1..ef57365 100644 --- a/Endpoint/Common/Services/MaintenanceModeService.cs +++ b/Endpoint/Common/Services/MaintenanceModeService.cs @@ -4,7 +4,7 @@ namespace Mirea.Api.Endpoint.Common.Services; public class MaintenanceModeService : IMaintenanceModeService { - public bool IsMaintenanceMode { get; private set; }; + public bool IsMaintenanceMode { get; private set; } public void EnableMaintenanceMode() => IsMaintenanceMode = true; -- 2.43.0 From af284e945f6b4e8e8c2b3da07d8407ebbb632ae9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:02:35 +0300 Subject: [PATCH 102/474] feat: add attribute maintenance ignore for controllers --- .../Common/Attributes/MaintenanceModeIgnoreAttribute.cs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs diff --git a/Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs b/Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs new file mode 100644 index 0000000..6f69b7a --- /dev/null +++ b/Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs @@ -0,0 +1,6 @@ +using System; + +namespace Mirea.Api.Endpoint.Common.Attributes; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public class MaintenanceModeIgnoreAttribute : Attribute; \ No newline at end of file -- 2.43.0 From 3f30b98cf948ea1dc5f8904e328085d046148c8c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:04:07 +0300 Subject: [PATCH 103/474] feat: add middleware for ignore maintenance --- .../Middleware/MaintenanceModeMiddleware.cs | 39 +++++++++++++++++++ Endpoint/Program.cs | 2 + 2 files changed, 41 insertions(+) create mode 100644 Endpoint/Middleware/MaintenanceModeMiddleware.cs diff --git a/Endpoint/Middleware/MaintenanceModeMiddleware.cs b/Endpoint/Middleware/MaintenanceModeMiddleware.cs new file mode 100644 index 0000000..2bd5d3b --- /dev/null +++ b/Endpoint/Middleware/MaintenanceModeMiddleware.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Http; +using Mirea.Api.Endpoint.Common.Attributes; +using Mirea.Api.Endpoint.Common.Interfaces; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Middleware; + +public class MaintenanceModeMiddleware(RequestDelegate next, IMaintenanceModeService maintenanceModeService, IMaintenanceModeNotConfigureService maintenanceModeNotConfigureService) +{ + private static bool IsIgnoreMaintenanceMode(HttpContext context) + { + var endpoint = context.GetEndpoint(); + return endpoint?.Metadata.GetMetadata() != null; + } + + public async Task Invoke(HttpContext context) + { + if (!maintenanceModeService.IsMaintenanceMode && !maintenanceModeNotConfigureService.IsMaintenanceMode || IsIgnoreMaintenanceMode(context)) + await next(context); + else + { + + context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; + context.Response.ContentType = "plain/text"; + + string error; + if (maintenanceModeService.IsMaintenanceMode) + { + context.Response.Headers.RetryAfter = "600"; + error = "The service is currently undergoing maintenance. Please try again later."; + } + else + error = + "The service is currently not configured. Go to the setup page if you are an administrator or try again later."; + + await context.Response.WriteAsync(error); + } + } +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index a238bbf..340478c 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -13,6 +13,7 @@ using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.Swagger; +using Mirea.Api.Endpoint.Middleware; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.Collections; @@ -118,6 +119,7 @@ public class Program } }); } + app.UseMiddleware(); app.UseHttpsRedirection(); -- 2.43.0 From ae0b9daefa9c5ccad79446514f6e7fa0e96e2eac Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:09:40 +0300 Subject: [PATCH 104/474] refactor: change the api path --- Endpoint/Controllers/BaseController.cs | 3 +- Endpoint/Controllers/V1/BaseControllerV1.cs | 8 - Endpoint/Controllers/V1/CampusController.cs | 90 ++++----- .../Controllers/V1/DisciplineController.cs | 96 ++++----- Endpoint/Controllers/V1/FacultyController.cs | 104 +++++----- Endpoint/Controllers/V1/GroupController.cs | 182 +++++++++--------- .../Controllers/V1/LectureHallController.cs | 136 ++++++------- .../Controllers/V1/ProfessorController.cs | 3 +- Endpoint/Controllers/V1/ScheduleController.cs | 12 +- 9 files changed, 314 insertions(+), 320 deletions(-) delete mode 100644 Endpoint/Controllers/V1/BaseControllerV1.cs diff --git a/Endpoint/Controllers/BaseController.cs b/Endpoint/Controllers/BaseController.cs index 82c4c7a..53f02a1 100644 --- a/Endpoint/Controllers/BaseController.cs +++ b/Endpoint/Controllers/BaseController.cs @@ -2,6 +2,7 @@ namespace Mirea.Api.Endpoint.Controllers; +[Produces("application/json")] +[Route("api/v{version:apiVersion}/[controller]")] [ApiController] -[Route("api/[controller]/[action]")] public class BaseController : ControllerBase; \ No newline at end of file diff --git a/Endpoint/Controllers/V1/BaseControllerV1.cs b/Endpoint/Controllers/V1/BaseControllerV1.cs deleted file mode 100644 index 6ebe1f3..0000000 --- a/Endpoint/Controllers/V1/BaseControllerV1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Mirea.Api.Endpoint.Controllers.V1; - -[ApiVersion("1.0")] -[Produces("application/json")] -[Route("api/v{version:apiVersion}/[controller]/[action]")] -public class BaseControllerV1 : BaseController; \ No newline at end of file diff --git a/Endpoint/Controllers/V1/CampusController.cs b/Endpoint/Controllers/V1/CampusController.cs index ae83834..4f4f101 100644 --- a/Endpoint/Controllers/V1/CampusController.cs +++ b/Endpoint/Controllers/V1/CampusController.cs @@ -9,53 +9,53 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Controllers.V1 +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class CampusController(IMediator mediator) : BaseController { - public class CampusController(IMediator mediator) : BaseControllerV1 + /// + /// Gets basic information about campuses. + /// + /// Basic information about campuses. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> Get() { - /// - /// Gets basic information about campuses. - /// - /// Basic information about campuses. - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> Get() - { - var result = await mediator.Send(new GetCampusBasicInfoListQuery()); + var result = await mediator.Send(new GetCampusBasicInfoListQuery()); - return Ok(result.Campuses - .Select(c => new CampusBasicInfoResponse() - { - Id = c.Id, - CodeName = c.CodeName, - FullName = c.FullName - }) - ); - } - - /// - /// Gets details of a specific campus by ID. - /// - /// Campus ID. - /// Details of the specified campus. - [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - [NotFoundResponse] - public async Task> GetDetails(int id) - { - var result = await mediator.Send(new GetCampusDetailsQuery() + return Ok(result.Campuses + .Select(c => new CampusBasicInfoResponse() { - Id = id - }); - - return Ok(new CampusDetailsResponse() - { - Id = result.Id, - CodeName = result.CodeName, - FullName = result.FullName, - Address = result.Address - }); - } + Id = c.Id, + CodeName = c.CodeName, + FullName = c.FullName + }) + ); } -} + + /// + /// Gets details of a specific campus by ID. + /// + /// Campus ID. + /// Details of the specified campus. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetCampusDetailsQuery() + { + Id = id + }); + + return Ok(new CampusDetailsResponse() + { + Id = result.Id, + CodeName = result.CodeName, + FullName = result.FullName, + Address = result.Address + }); + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/DisciplineController.cs b/Endpoint/Controllers/V1/DisciplineController.cs index 056b208..1de1136 100644 --- a/Endpoint/Controllers/V1/DisciplineController.cs +++ b/Endpoint/Controllers/V1/DisciplineController.cs @@ -9,57 +9,57 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Controllers.V1 +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class DisciplineController(IMediator mediator) : BaseController { - public class DisciplineController(IMediator mediator) : BaseControllerV1 + /// + /// Gets a paginated list of disciplines. + /// + /// Page number. Start from 0. + /// Number of items per page. + /// Paginated list of disciplines. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) { - /// - /// Gets a paginated list of disciplines. - /// - /// Page number. Start from 0. - /// Number of items per page. - /// Paginated list of disciplines. - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + var result = await mediator.Send(new GetDisciplineListQuery() { - var result = await mediator.Send(new GetDisciplineListQuery() - { - Page = page, - PageSize = pageSize - }); + Page = page, + PageSize = pageSize + }); - return Ok(result.Disciplines - .Select(d => new DisciplineResponse() - { - Id = d.Id, - Name = d.Name - }) - ); - } - - /// - /// Gets details of a specific discipline by ID. - /// - /// Discipline ID. - /// Details of the specified discipline. - [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - [NotFoundResponse] - public async Task> GetDetails(int id) - { - var result = await mediator.Send(new GetDisciplineInfoQuery() + return Ok(result.Disciplines + .Select(d => new DisciplineResponse() { - Id = id - }); - - return Ok(new DisciplineResponse() - { - Id = result.Id, - Name = result.Name - }); - } + Id = d.Id, + Name = d.Name + }) + ); } -} + + /// + /// Gets details of a specific discipline by ID. + /// + /// Discipline ID. + /// Details of the specified discipline. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetDisciplineInfoQuery() + { + Id = id + }); + + return Ok(new DisciplineResponse() + { + Id = result.Id, + Name = result.Name + }); + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index 3b4cd61..9652551 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -9,61 +9,61 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Controllers.V1 +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class FacultyController(IMediator mediator) : BaseController { - public class FacultyController(IMediator mediator) : BaseControllerV1 + /// + /// Gets a paginated list of faculties. + /// + /// Page number. Start from 0. + /// Number of items per page. + /// Paginated list of faculties. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) { - /// - /// Gets a paginated list of faculties. - /// - /// Page number. Start from 0. - /// Number of items per page. - /// Paginated list of faculties. - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + var result = await mediator.Send(new GetFacultyListQuery() { - var result = await mediator.Send(new GetFacultyListQuery() - { - Page = page, - PageSize = pageSize - }); + Page = page, + PageSize = pageSize + }); - return Ok(result.Faculties - .Select(f => new FacultyResponse() - { - Id = f.Id, - Name = f.Name, - CampusId = f.CampusId - }) - ); - } - - /// - /// Gets details of a specific faculty by ID. - /// - /// Faculty ID. - /// Details of the specified faculty. - [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - [NotFoundResponse] - public async Task> GetDetails(int id) - { - var result = await mediator.Send(new GetFacultyInfoQuery() + return Ok(result.Faculties + .Select(f => new FacultyResponse() { - Id = id - }); - - return Ok(new FacultyDetailsResponse() - { - Id = result.Id, - Name = result.Name, - CampusId = result.CampusId, - CampusCode = result.CampusCode, - CampusName = result.CampusName - }); - } + Id = f.Id, + Name = f.Name, + CampusId = f.CampusId + }) + ); } -} + + /// + /// Gets details of a specific faculty by ID. + /// + /// Faculty ID. + /// Details of the specified faculty. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetFacultyInfoQuery() + { + Id = id + }); + + return Ok(new FacultyDetailsResponse() + { + Id = result.Id, + Name = result.Name, + CampusId = result.CampusId, + CampusCode = result.CampusCode, + CampusName = result.CampusName + }); + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index cead029..c12f3da 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -10,99 +10,99 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Controllers.V1 +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class GroupController(IMediator mediator) : BaseController { - public class GroupController(IMediator mediator) : BaseControllerV1 + private static int GetCourseNumber(string groupName) { - private static int GetCourseNumber(string groupName) - { - var current = DateTime.Now; - if (!int.TryParse(groupName[2..], out var yearOfGroup) - && !int.TryParse(groupName.Split('-')[^1][..2], out yearOfGroup)) - return -1; + var current = DateTime.Now; + if (!int.TryParse(groupName[2..], out var yearOfGroup) + && !int.TryParse(groupName.Split('-')[^1][..2], out yearOfGroup)) + return -1; - // Convert a two-digit year to a four-digit one - yearOfGroup += current.Year / 100 * 100; + // Convert a two-digit year to a four-digit one + yearOfGroup += current.Year / 100 * 100; - return current.Year - yearOfGroup + (current.Month < 9 ? 0 : 1); - } - - /// - /// Retrieves a list of groups. - /// - /// The page number for pagination (optional). - /// The page size for pagination (optional). - /// A list of groups. - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) - { - var result = await mediator.Send(new GetGroupListQuery() - { - Page = page, - PageSize = pageSize - }); - - return Ok(result.Groups - .Select(g => new GroupResponse() - { - Id = g.Id, - Name = g.Name, - FacultyId = g.FacultyId, - CourseNumber = GetCourseNumber(g.Name) - }) - ); - } - - /// - /// Retrieves detailed information about a specific group. - /// - /// The ID of the group to retrieve. - /// Detailed information about the group. - [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - [NotFoundResponse] - public async Task> GetDetails(int id) - { - var result = await mediator.Send(new GetGroupInfoQuery() - { - Id = id - }); - - return Ok(new GroupDetailsResponse() - { - Id = result.Id, - Name = result.Name, - FacultyId = result.FacultyId, - FacultyName = result.Faculty, - CourseNumber = GetCourseNumber(result.Name) - }); - } - - /// - /// Retrieves a list of groups by faculty ID. - /// - /// The ID of the faculty. - /// A list of groups belonging to the specified faculty. - [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - [NotFoundResponse] - public async Task>> GetByFaculty(int id) - { - var result = await mediator.Send(new GetGroupListQuery()); - - return Ok(result.Groups - .Where(g => g.FacultyId == id) - .Select(g => new GroupResponse() - { - Id = g.Id, - Name = g.Name, - CourseNumber = GetCourseNumber(g.Name), - FacultyId = g.FacultyId - })); - } + return current.Year - yearOfGroup + (current.Month < 9 ? 0 : 1); } -} + + /// + /// Retrieves a list of groups. + /// + /// The page number for pagination (optional). + /// The page size for pagination (optional). + /// A list of groups. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + { + var result = await mediator.Send(new GetGroupListQuery() + { + Page = page, + PageSize = pageSize + }); + + return Ok(result.Groups + .Select(g => new GroupResponse() + { + Id = g.Id, + Name = g.Name, + FacultyId = g.FacultyId, + CourseNumber = GetCourseNumber(g.Name) + }) + ); + } + + /// + /// Retrieves detailed information about a specific group. + /// + /// The ID of the group to retrieve. + /// Detailed information about the group. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetGroupInfoQuery() + { + Id = id + }); + + return Ok(new GroupDetailsResponse() + { + Id = result.Id, + Name = result.Name, + FacultyId = result.FacultyId, + FacultyName = result.Faculty, + CourseNumber = GetCourseNumber(result.Name) + }); + } + + /// + /// Retrieves a list of groups by faculty ID. + /// + /// The ID of the faculty. + /// A list of groups belonging to the specified faculty. + [HttpGet("GetByFaculty/{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task>> GetByFaculty(int id) + { + var result = await mediator.Send(new GetGroupListQuery()); + + return Ok(result.Groups + .Where(g => g.FacultyId == id) + .Select(g => new GroupResponse() + { + Id = g.Id, + Name = g.Name, + CourseNumber = GetCourseNumber(g.Name), + FacultyId = g.FacultyId + })); + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/LectureHallController.cs b/Endpoint/Controllers/V1/LectureHallController.cs index de2f21f..c6e67b8 100644 --- a/Endpoint/Controllers/V1/LectureHallController.cs +++ b/Endpoint/Controllers/V1/LectureHallController.cs @@ -9,76 +9,76 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Controllers.V1 +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class LectureHallController(IMediator mediator) : BaseController { - public class LectureHallController(IMediator mediator) : BaseControllerV1 + /// + /// Retrieves a list of all lecture halls. + /// + /// A list of lecture halls. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> Get() { - /// - /// Retrieves a list of all lecture halls. - /// - /// A list of lecture halls. - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> Get() - { - var result = await mediator.Send(new GetLectureHallListQuery()); + var result = await mediator.Send(new GetLectureHallListQuery()); - return Ok(result.LectureHalls - .Select(l => new LectureHallResponse() - { - Id = l.Id, - Name = l.Name, - CampusId = l.CampusId - }) - ); - } - - /// - /// Retrieves details of a specific lecture hall by its ID. - /// - /// The ID of the lecture hall to retrieve. - /// The details of the specified lecture hall. - [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - [NotFoundResponse] - public async Task> GetDetails(int id) - { - var result = await mediator.Send(new GetLectureHallInfoQuery() + return Ok(result.LectureHalls + .Select(l => new LectureHallResponse() { - Id = id - }); - - return Ok(new LectureHallDetailsResponse() - { - Id = result.Id, - Name = result.Name, - CampusId = result.CampusId, - CampusCode = result.CampusCode, - CampusName = result.CampusName - }); - } - - /// - /// Retrieves a list of lecture halls by campus ID. - /// - /// The ID of the campus. - /// A list of lecture halls in the specified campus. - [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [BadRequestResponse] - [NotFoundResponse] - public async Task>> GetByCampus(int id) - { - var result = await mediator.Send(new GetLectureHallListQuery()); - - return Ok(result.LectureHalls.Where(l => l.CampusId == id) - .Select(l => new LectureHallResponse() - { - Id = l.Id, - Name = l.Name, - CampusId = l.CampusId - })); - } + Id = l.Id, + Name = l.Name, + CampusId = l.CampusId + }) + ); } -} + + /// + /// Retrieves details of a specific lecture hall by its ID. + /// + /// The ID of the lecture hall to retrieve. + /// The details of the specified lecture hall. + [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task> GetDetails(int id) + { + var result = await mediator.Send(new GetLectureHallInfoQuery() + { + Id = id + }); + + return Ok(new LectureHallDetailsResponse() + { + Id = result.Id, + Name = result.Name, + CampusId = result.CampusId, + CampusCode = result.CampusCode, + CampusName = result.CampusName + }); + } + + /// + /// Retrieves a list of lecture halls by campus ID. + /// + /// The ID of the campus. + /// A list of lecture halls in the specified campus. + [HttpGet("GetByCampus/{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [BadRequestResponse] + [NotFoundResponse] + public async Task>> GetByCampus(int id) + { + var result = await mediator.Send(new GetLectureHallListQuery()); + + return Ok(result.LectureHalls.Where(l => l.CampusId == id) + .Select(l => new LectureHallResponse() + { + Id = l.Id, + Name = l.Name, + CampusId = l.CampusId + })); + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/ProfessorController.cs b/Endpoint/Controllers/V1/ProfessorController.cs index 5a9053a..3093a38 100644 --- a/Endpoint/Controllers/V1/ProfessorController.cs +++ b/Endpoint/Controllers/V1/ProfessorController.cs @@ -11,7 +11,8 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; -public class ProfessorController(IMediator mediator) : BaseControllerV1 +[ApiVersion("1.0")] +public class ProfessorController(IMediator mediator) : BaseController { /// /// Retrieves a list of professors. diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 1500efa..a0d5b7b 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -12,7 +12,8 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; -public class ScheduleController(IMediator mediator) : BaseControllerV1 +[ApiVersion("1.0")] +public class ScheduleController(IMediator mediator) : BaseController { /// /// Retrieves schedules based on various filters. @@ -25,7 +26,6 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 [BadRequestResponse] [NotFoundResponse] public async Task>> Get([FromBody] ScheduleRequest request) - { if ((request.Groups == null || request.Groups.Length == 0) && (request.Disciplines == null || request.Disciplines.Length == 0) && @@ -85,7 +85,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 /// An array of professor IDs. /// An array of lecture hall IDs. /// A response containing schedules for the specified group. - [HttpGet("{id:int}")] + [HttpGet("GetByGroup/{id:int}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] @@ -142,7 +142,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 /// An array of group IDs. /// An array of lecture hall IDs. /// A response containing schedules for the specified professor. - [HttpGet("{id:int}")] + [HttpGet("GetByProfessor/{id:int}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] @@ -203,7 +203,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 /// An array of professor IDs. /// An array of group IDs. /// A response containing schedules for the specified lecture hall. - [HttpGet("{id:int}")] + [HttpGet("GetByLectureHall/{id:int}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] @@ -264,7 +264,7 @@ public class ScheduleController(IMediator mediator) : BaseControllerV1 /// An array of professor IDs. /// An array of lecture hall IDs. /// A response containing schedules for the specified discipline. - [HttpGet("{id:int}")] + [HttpGet("GetByDiscipline/{id:int}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] -- 2.43.0 From 08f13108d8ddff52e65003b54d49540fd0bd630d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:10:32 +0300 Subject: [PATCH 105/474] feat: add validator for settings --- .../Validators/SettingsRequiredValidator.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs diff --git a/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs b/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs new file mode 100644 index 0000000..a1e8fbc --- /dev/null +++ b/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Options; +using Mirea.Api.Endpoint.Configuration.General.Attributes; +using Mirea.Api.Endpoint.Configuration.General.Interfaces; +using System; +using System.Reflection; + +namespace Mirea.Api.Endpoint.Configuration.General.Validators; + +public class SettingsRequiredValidator +{ + private readonly GeneralConfig _generalConfig; + + public SettingsRequiredValidator(IOptionsSnapshot configuration) => + _generalConfig = configuration.Value; + + public SettingsRequiredValidator(GeneralConfig configuration) => + _generalConfig = configuration; + + public bool AreSettingsValid() + { + foreach (var property in _generalConfig + .GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (!Attribute.IsDefined(property.PropertyType, typeof(RequiredSettingsAttribute))) continue; + + var value = property.GetValue(_generalConfig); + if (value == null) + return false; + + var isConfigured = value as IIsConfigured; + if (!isConfigured!.IsConfigured()) + return false; + } + + return true; + } +} \ No newline at end of file -- 2.43.0 From 35eb1eab39791838ca3fa75ceb2f622ea8bde06c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:12:58 +0300 Subject: [PATCH 106/474] feat: implement validation of settings --- Endpoint/Program.cs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 340478c..0938434 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -12,6 +12,7 @@ using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration; using Mirea.Api.Endpoint.Configuration.General; +using Mirea.Api.Endpoint.Configuration.General.Validators; using Mirea.Api.Endpoint.Configuration.Swagger; using Mirea.Api.Endpoint.Middleware; using Swashbuckle.AspNetCore.SwaggerGen; @@ -100,8 +101,25 @@ public class Program Console.WriteLine($"{item.Key}:{item.Value}"); #endif - var uber = app.Services.CreateScope().ServiceProvider.GetService(); - DbInitializer.Initialize(uber!); + using (var scope = app.Services.CreateScope()) + { + var serviceProvider = scope.ServiceProvider; + + var optionsSnapshot = serviceProvider.GetRequiredService>(); + var settingsValidator = new SettingsRequiredValidator(optionsSnapshot); + var isDoneConfig = settingsValidator.AreSettingsValid(); + + if (isDoneConfig) + { + var uberDbContext = serviceProvider.GetRequiredService(); + var maintenanceModeService = serviceProvider.GetRequiredService(); + + maintenanceModeService.DisableMaintenanceMode(); + DbInitializer.Initialize(uberDbContext); + + // todo: if admin not found + } + } // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) -- 2.43.0 From fb6e119a34643db91ef78d8abeaecfa94c3da4eb Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:14:17 +0300 Subject: [PATCH 107/474] feat: add token for setup controllers --- .../TokenAuthenticationAttribute.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs diff --git a/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs b/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs new file mode 100644 index 0000000..81812bf --- /dev/null +++ b/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.Endpoint.Common.Interfaces; +using System; + +namespace Mirea.Api.Endpoint.Common.Attributes; + +[AttributeUsage(AttributeTargets.Method)] +public class TokenAuthenticationAttribute : Attribute, IActionFilter +{ + public void OnActionExecuting(ActionExecutingContext context) + { + var setupToken = context.HttpContext.RequestServices.GetRequiredService(); + if (!context.HttpContext.Request.Cookies.TryGetValue("AuthToken", out string? tokenFromCookie)) + { + context.Result = new UnauthorizedResult(); + return; + } + + if (setupToken.MatchToken(Convert.FromBase64String(tokenFromCookie))) return; + + context.Result = new UnauthorizedResult(); + } + + public void OnActionExecuted(ActionExecutedContext context) { } +} \ No newline at end of file -- 2.43.0 From 59785f600fc3b0afe81c9a6677b56be530789722 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:15:13 +0300 Subject: [PATCH 108/474] feat: add argument exception for controllers --- Endpoint/Common/Exceptions/ControllerArgumentException.cs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Endpoint/Common/Exceptions/ControllerArgumentException.cs diff --git a/Endpoint/Common/Exceptions/ControllerArgumentException.cs b/Endpoint/Common/Exceptions/ControllerArgumentException.cs new file mode 100644 index 0000000..36e6b26 --- /dev/null +++ b/Endpoint/Common/Exceptions/ControllerArgumentException.cs @@ -0,0 +1,5 @@ +using System; + +namespace Mirea.Api.Endpoint.Common.Exceptions; + +public class ControllerArgumentException(string message) : Exception(message); \ No newline at end of file -- 2.43.0 From 481839159cdb60c7bf8f02f92d27a91878c3a30c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:16:15 +0300 Subject: [PATCH 109/474] feat: add middleware for custom exception --- .../CustomExceptionHandlerMiddleware.cs | 60 +++++++++++++++++++ Endpoint/Program.cs | 1 + 2 files changed, 61 insertions(+) create mode 100644 Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs diff --git a/Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs new file mode 100644 index 0000000..62c41e2 --- /dev/null +++ b/Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs @@ -0,0 +1,60 @@ +using FluentValidation; +using Microsoft.AspNetCore.Http; +using Mirea.Api.DataAccess.Application.Common.Exceptions; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Exceptions; +using System; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Middleware; + +public class CustomExceptionHandlerMiddleware(RequestDelegate next) +{ + public async Task Invoke(HttpContext context) + { + try + { + await next(context); + } + catch (Exception exception) + { + await HandleExceptionAsync(context, exception); + } + } + + private static Task HandleExceptionAsync(HttpContext context, Exception exception) + { + var code = StatusCodes.Status500InternalServerError; + var result = string.Empty; + switch (exception) + { + case ValidationException validationException: + code = StatusCodes.Status400BadRequest; + result = JsonSerializer.Serialize(new ErrorResponse() + { + Error = validationException.Message, + Code = code + }); + break; + case NotFoundException: + code = StatusCodes.Status404NotFound; + break; + case ControllerArgumentException: + code = StatusCodes.Status400BadRequest; + break; + } + + context.Response.ContentType = "application/json"; + context.Response.StatusCode = code; + + if (string.IsNullOrEmpty(result)) + result = JsonSerializer.Serialize(new ErrorResponse() + { + Error = exception.Message, + Code = code + }); + + return context.Response.WriteAsync(result); + } +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 0938434..89d98ab 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -138,6 +138,7 @@ public class Program }); } app.UseMiddleware(); + app.UseMiddleware(); app.UseHttpsRedirection(); -- 2.43.0 From 966ab9bdda8bcac890670fa3fa699e77d432fae6 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:19:40 +0300 Subject: [PATCH 110/474] feat: add generate and check token --- .../Configuration/SetupController.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Endpoint/Controllers/Configuration/SetupController.cs diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs new file mode 100644 index 0000000..225da6e --- /dev/null +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -0,0 +1,48 @@ +using System; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Mirea.Api.Endpoint.Common.Attributes; +using Mirea.Api.Endpoint.Common.Exceptions; +using Mirea.Api.Endpoint.Common.Interfaces; +using Mirea.Api.Endpoint.Configuration.General; + +namespace Mirea.Api.Endpoint.Controllers.Configuration; + +[ApiVersion("1.0")] +[ApiController] +[MaintenanceModeIgnore] +public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService) : BaseController +{ + [HttpGet("GenerateToken")] + public ActionResult GenerateToken() + { + if (!notConfigureService.IsMaintenanceMode) + throw new ControllerArgumentException( + "The token cannot be generated because the server has been configured. " + + $"If you need to restart the configuration, then delete the \"{GeneralConfig.FilePath}\" file and restart the application."); + + var token = new byte[32]; + RandomNumberGenerator.Create().GetBytes(token); + setupToken.SetToken(token); + + return Ok(Convert.ToBase64String(token)); + } + + [HttpGet("CheckToken")] + public ActionResult CheckToken([FromQuery] string token) + { + if (!setupToken.MatchToken(Convert.FromBase64String(token))) return Unauthorized("The token is not valid"); + + Response.Cookies.Append("AuthToken", token, new CookieOptions + { + HttpOnly = false, + Secure = false, + Path = "/" + }); + + return Ok(true); + } + + +} \ No newline at end of file -- 2.43.0 From 9bf9eabad7d6a943276702c12b86fbece492f0dc Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 28 May 2024 07:20:21 +0300 Subject: [PATCH 111/474] fix: add full path to settings --- Endpoint/Controllers/Configuration/SetupController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 225da6e..c315237 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -20,7 +20,7 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur if (!notConfigureService.IsMaintenanceMode) throw new ControllerArgumentException( "The token cannot be generated because the server has been configured. " + - $"If you need to restart the configuration, then delete the \"{GeneralConfig.FilePath}\" file and restart the application."); + $"If you need to restart the configuration, then delete the \"{PathBuilder.Combine(GeneralConfig.FilePath)}\" file and restart the application."); var token = new byte[32]; RandomNumberGenerator.Create().GetBytes(token); -- 2.43.0 From 22793c78826a86779230f4eafcad97e8fb2eb987 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:30:00 +0300 Subject: [PATCH 112/474] feat: add localhost attribute --- .../Common/Attributes/LocalhostAttribute.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Endpoint/Common/Attributes/LocalhostAttribute.cs diff --git a/Endpoint/Common/Attributes/LocalhostAttribute.cs b/Endpoint/Common/Attributes/LocalhostAttribute.cs new file mode 100644 index 0000000..734e0bf --- /dev/null +++ b/Endpoint/Common/Attributes/LocalhostAttribute.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System.Net; + +namespace Mirea.Api.Endpoint.Common.Attributes; + +public class LocalhostAttribute : ActionFilterAttribute +{ + public override void OnActionExecuting(ActionExecutingContext context) + { + var ip = context.HttpContext.Connection.RemoteIpAddress; + if (ip == null || !IPAddress.IsLoopback(ip)) + { + context.Result = new UnauthorizedResult(); + return; + } + base.OnActionExecuting(context); + } +} \ No newline at end of file -- 2.43.0 From 07d7fec24fe9ee55798284ac45799866adad0109 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:30:26 +0300 Subject: [PATCH 113/474] feat: add localhost for generate token --- Endpoint/Controllers/Configuration/SetupController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index c315237..4317b95 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -15,6 +15,7 @@ namespace Mirea.Api.Endpoint.Controllers.Configuration; public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService) : BaseController { [HttpGet("GenerateToken")] + [Localhost] public ActionResult GenerateToken() { if (!notConfigureService.IsMaintenanceMode) -- 2.43.0 From eefb049e0e22efdb358df16f5bac86daa001c64a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:35:52 +0300 Subject: [PATCH 114/474] feat: add cache for save intermediate settings --- .../Controllers/Configuration/SetupController.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 4317b95..b629952 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -2,18 +2,28 @@ using System.Security.Cryptography; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; 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.General; +using Mirea.Api.Endpoint.Configuration.General.Settings; namespace Mirea.Api.Endpoint.Controllers.Configuration; [ApiVersion("1.0")] [ApiController] [MaintenanceModeIgnore] -public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService) : BaseController +public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache) : BaseController { + private const string CacheGeneralKey = "config_part"; + private GeneralConfig GeneralConfig +{ + get => cache.Get(CacheGeneralKey) ?? new GeneralConfig(); + set => cache.Set(CacheGeneralKey, value); + } + [HttpGet("GenerateToken")] [Localhost] public ActionResult GenerateToken() -- 2.43.0 From e7ed69169c5c1ddbcae5a1dbd53afb6c4ab7031f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:37:04 +0300 Subject: [PATCH 115/474] feat: add setter database --- .../Configuration/SetupController.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index b629952..1384d9b 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -9,6 +9,8 @@ using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Settings; +using System; +using System.Data; namespace Mirea.Api.Endpoint.Controllers.Configuration; @@ -55,5 +57,32 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur return Ok(true); } + private ActionResult SetDatabase(string connectionString, DbSettings.DatabaseEnum databaseType) + where TConnection : class, IDbConnection, new() + where TException : Exception + { + try + { + using var connection = new TConnection(); + connection.ConnectionString = connectionString; + connection.Open(); + connection.Close(); + + var general = GeneralConfig; + general.DbSettings = new DbSettings + { + ConnectionStringSql = connectionString, + TypeDatabase = databaseType + }; + GeneralConfig = general; + + return Ok(true); + } + catch (TException ex) + { + throw new ControllerArgumentException($"Error when connecting: {ex.Message}"); + } + } + } \ No newline at end of file -- 2.43.0 From ba8ccf8b7e4278b6cbeefd44566716d2c4608471 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:38:21 +0300 Subject: [PATCH 116/474] feat: add set database endpoints --- .../Requests/Configuration/DatabaseRequest.cs | 22 ++++++++ .../Configuration/SetupController.cs | 52 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 ApiDto/Requests/Configuration/DatabaseRequest.cs diff --git a/ApiDto/Requests/Configuration/DatabaseRequest.cs b/ApiDto/Requests/Configuration/DatabaseRequest.cs new file mode 100644 index 0000000..4308471 --- /dev/null +++ b/ApiDto/Requests/Configuration/DatabaseRequest.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Requests.Configuration; + +public class DatabaseRequest +{ + [Required] + public required string Server { get; set; } + + [Required] + public int Port { get; set; } + + [Required] + public required string Database { get; set; } + + [Required] + public required string User { get; set; } + + [Required] + public bool Ssl { get; set; } + public string? Password { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 1384d9b..04924ac 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -84,5 +84,57 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur } } + [HttpPost("SetPsql")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult SetPsql([FromBody] DatabaseRequest request) + { + string connectionString = $"Host={request.Server}:{request.Port};Username={request.User};Database={request.Database}"; + if (request.Password != null) + connectionString += $";Password={request.Password}"; + if (request.Ssl) + connectionString += ";SSL Mode=Require;"; + + return SetDatabase(connectionString, DbSettings.DatabaseEnum.PostgresSql); + } + + [HttpPost("SetMysql")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult SetMysql([FromBody] DatabaseRequest request) + { + string connectionString = $"Server={request.Server}:{request.Port};Uid={request.User};Database={request.Database}"; + if (request.Password != null) + connectionString += $";Pwd={request.Password}"; + if (request.Ssl) + connectionString += ";SslMode=Require;"; + + return SetDatabase(connectionString, DbSettings.DatabaseEnum.Mysql); + } + + [HttpPost("SetSqlite")] + [TokenAuthentication] + public ActionResult SetSqlite([FromQuery] string? path) + { + if (string.IsNullOrEmpty(path)) path = "database"; + + path = PathBuilder.Combine(path); + + if (!Directory.Exists(path)) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Directory.CreateDirectory(path); + else + Directory.CreateDirectory(path, UnixFileMode.UserRead | UnixFileMode.UserWrite); + } + else + throw new ControllerArgumentException("Such a folder exists. Enter a different name"); + + string connectionString = $"Data Source={PathBuilder.Combine(path, "database.db3")}"; + + return SetDatabase(connectionString, DbSettings.DatabaseEnum.Sqlite); + } + } + } \ No newline at end of file -- 2.43.0 From 2d67a565cabec799622ac8eae804694c88828638 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:41:54 +0300 Subject: [PATCH 117/474] docs: add xml doc for request --- .../Requests/Configuration/DatabaseRequest.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ApiDto/Requests/Configuration/DatabaseRequest.cs b/ApiDto/Requests/Configuration/DatabaseRequest.cs index 4308471..5da58e3 100644 --- a/ApiDto/Requests/Configuration/DatabaseRequest.cs +++ b/ApiDto/Requests/Configuration/DatabaseRequest.cs @@ -2,21 +2,43 @@ namespace Mirea.Api.Dto.Requests.Configuration; +/// +/// Represents a request to configure the database connection settings. +/// public class DatabaseRequest { + /// + /// Gets or sets the server address. + /// [Required] public required string Server { get; set; } + /// + /// Gets or sets the port number. + /// [Required] public int Port { get; set; } + /// + /// Gets or sets the database name. + /// [Required] public required string Database { get; set; } + /// + /// Gets or sets the username. + /// [Required] public required string User { get; set; } + /// + /// Gets or sets a value indicating whether SSL is enabled. + /// [Required] public bool Ssl { get; set; } + + /// + /// Gets or sets the password. + /// public string? Password { get; set; } } \ No newline at end of file -- 2.43.0 From c02240077f5c3521a7ea44b31094bf2b321767a9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:42:39 +0300 Subject: [PATCH 118/474] fix: add missing ref --- Endpoint/Controllers/Configuration/SetupController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 04924ac..43fb6c3 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -2,13 +2,18 @@ using System.Security.Cryptography; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.Sqlite; using Microsoft.Extensions.Caching.Memory; +using Mirea.Api.Dto.Requests; +using Mirea.Api.Dto.Requests.Configuration; 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.General; using Mirea.Api.Endpoint.Configuration.General.Settings; +using MySqlConnector; +using Npgsql; using System; using System.Data; -- 2.43.0 From 29c9c10a53e064c15011927b1a39ff771ab06ce0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:43:08 +0300 Subject: [PATCH 119/474] feat: add endpoint for cache --- .../Configuration/SetupController.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 43fb6c3..ed8512a 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -14,6 +14,7 @@ using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Settings; using MySqlConnector; using Npgsql; +using StackExchange.Redis; using System; using System.Data; @@ -139,6 +140,52 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur return SetDatabase(connectionString, DbSettings.DatabaseEnum.Sqlite); } + + [HttpPost("SetRedis")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult SetRedis([FromBody] CacheRequest request) + { + string connectionString = $"{request.Server}:{request.Port},ssl=false"; + if (request.Password != null) + connectionString += $",password={request.Password}"; + + try + { + var redis = ConnectionMultiplexer.Connect(connectionString); + redis.Close(); + + var general = GeneralConfig; + general.CacheSettings = new CacheSettings + { + ConnectionString = connectionString, + TypeDatabase = CacheSettings.CacheEnum.Redis + }; + GeneralConfig = general; + + return Ok(true); + } + catch (Exception ex) + { + throw new ControllerArgumentException("Error when connecting to Redis: " + ex.Message); + } + } + + [HttpPost("SetMemcached")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult SetMemcached() + { + var general = GeneralConfig; + general.CacheSettings = new CacheSettings + { + ConnectionString = null, + TypeDatabase = CacheSettings.CacheEnum.Memcached + }; + GeneralConfig = general; + + return Ok(true); + } } -- 2.43.0 From f5deeec6c94282dc2191230828e7c1ed266c8c75 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:44:24 +0300 Subject: [PATCH 120/474] feat: add endpoint for logging --- .../Requests/Configuration/LoggingRequest.cs | 25 +++++++++++++++++ .../Configuration/SetupController.cs | 28 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 ApiDto/Requests/Configuration/LoggingRequest.cs diff --git a/ApiDto/Requests/Configuration/LoggingRequest.cs b/ApiDto/Requests/Configuration/LoggingRequest.cs new file mode 100644 index 0000000..eba6428 --- /dev/null +++ b/ApiDto/Requests/Configuration/LoggingRequest.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Requests.Configuration; + +/// +/// Represents a request to configure logging settings. +/// +public class LoggingRequest +{ + /// + /// Gets or sets a value indicating whether logging to file is enabled. + /// + [Required] + public bool EnableLogToFile { get; set; } + + /// + /// Gets or sets the log file name. + /// + public string? LogFileName { get; set; } + + /// + /// Gets or sets the log file path. + /// + public string? LogFilePath { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index ed8512a..ccc51be 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -186,6 +186,34 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur return Ok(true); } + + [HttpPost("SetLogging")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult SetLogging([FromBody] LoggingRequest? request) + { + var settings = (request == null) switch + { + true => new LogSettings + { + EnableLogToFile = true, + LogFileName = "logging-", + LogFilePath = "logs" + }, + false => new LogSettings + { + EnableLogToFile = request.EnableLogToFile, + LogFileName = request.LogFileName, + LogFilePath = request.LogFilePath + } + }; + + var general = GeneralConfig; + general.LogSettings = settings; + GeneralConfig = general; + + return true; + } } -- 2.43.0 From 5d308f1a242024ca4706e9d91c5d157756c6815e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:44:39 +0300 Subject: [PATCH 121/474] feat: add request for cache --- ApiDto/Requests/Configuration/CacheRequest.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 ApiDto/Requests/Configuration/CacheRequest.cs diff --git a/ApiDto/Requests/Configuration/CacheRequest.cs b/ApiDto/Requests/Configuration/CacheRequest.cs new file mode 100644 index 0000000..cafc1c6 --- /dev/null +++ b/ApiDto/Requests/Configuration/CacheRequest.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Requests.Configuration; + +/// +/// Represents a request to configure cache settings. +/// +public class CacheRequest +{ + /// + /// Gets or sets the server address. + /// + [Required] + public required string Server { get; set; } + + /// + /// Gets or sets the port number. + /// + [Required] + public int Port { get; set; } + + /// + /// Gets or sets the password. + /// + public string? Password { get; set; } +} \ No newline at end of file -- 2.43.0 From 17961ccefc782ad8782eaeb10297281731c0a459 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:45:02 +0300 Subject: [PATCH 122/474] feat: add email endpoint --- ApiDto/Requests/Configuration/EmailRequest.cs | 45 +++++++++++++++++++ .../Configuration/SetupController.cs | 26 +++++++++++ 2 files changed, 71 insertions(+) create mode 100644 ApiDto/Requests/Configuration/EmailRequest.cs diff --git a/ApiDto/Requests/Configuration/EmailRequest.cs b/ApiDto/Requests/Configuration/EmailRequest.cs new file mode 100644 index 0000000..7770c6e --- /dev/null +++ b/ApiDto/Requests/Configuration/EmailRequest.cs @@ -0,0 +1,45 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Requests.Configuration; + +/// +/// Represents a request to configure email settings. +/// +public class EmailRequest +{ + /// + /// Gets or sets the server address. + /// + [Required] + public required string Server { get; set; } + + /// + /// Gets or sets the email address from which emails will be sent. + /// + [Required] + public required string From { get; set; } + + /// + /// Gets or sets the password for the email account. + /// + [Required] + public required string Password { get; set; } + + /// + /// Gets or sets the port number. + /// + [Required] + public int Port { get; set; } + + /// + /// Gets or sets a value indicating whether SSL is enabled. + /// + [Required] + public bool Ssl { get; set; } + + /// + /// Gets or sets the username. + /// + [Required] + public required string User { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index ccc51be..d5eb17e 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -214,6 +214,32 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur return true; } + + [HttpPost("SetEmail")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult SetEmail([FromBody] EmailRequest? request) + { + var settings = (request == null) switch + { + true => new EmailSettings(), + false => new EmailSettings + { + Server = request.Server, + From = request.From, + Password = request.Password, + Port = request.Port, + Ssl = request.Ssl, + User = request.User + } + }; + + var general = GeneralConfig; + general.EmailSettings = settings; + GeneralConfig = general; + + return true; + } } -- 2.43.0 From f9750ef039bcb17c266878e478e221202eedf320 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:46:16 +0300 Subject: [PATCH 123/474] feat: add endpoint for schedule --- .../ScheduleConfigurationRequest.cs | 29 +++++++++++++++++++ .../Configuration/SetupController.cs | 28 ++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 ApiDto/Requests/Configuration/ScheduleConfigurationRequest.cs diff --git a/ApiDto/Requests/Configuration/ScheduleConfigurationRequest.cs b/ApiDto/Requests/Configuration/ScheduleConfigurationRequest.cs new file mode 100644 index 0000000..ca6e950 --- /dev/null +++ b/ApiDto/Requests/Configuration/ScheduleConfigurationRequest.cs @@ -0,0 +1,29 @@ +using Mirea.Api.Dto.Common; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Requests.Configuration; + +/// +/// Represents a request to configure the schedule settings. +/// +public class ScheduleConfigurationRequest +{ + /// + /// Gets or sets the cron expression for updating the schedule. + /// + public string? CronUpdateSchedule { get; set; } + + /// + /// Gets or sets the start date of the term. + /// + [Required] + public DateOnly StartTerm { get; set; } + + /// + /// Gets or sets the pair period times, keyed by pair number. + /// + [Required] + public required IDictionary PairPeriod { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index d5eb17e..7abd5ee 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -1,5 +1,4 @@ -using System; -using System.Security.Cryptography; +using Cronos; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; @@ -17,6 +16,10 @@ using Npgsql; using StackExchange.Redis; using System; using System.Data; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text.Json; namespace Mirea.Api.Endpoint.Controllers.Configuration; @@ -240,6 +243,27 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur return true; } + + [HttpPost("SetSchedule")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult SetSchedule([FromBody] ScheduleConfigurationRequest request) + { + var general = GeneralConfig; + general.ScheduleSettings = new ScheduleSettings + { + // every 6 hours + CronUpdateSchedule = request.CronUpdateSchedule ?? "0 */6 * * *", + StartTerm = request.StartTerm, + PairPeriod = request.PairPeriod.ConvertFromDto() + }; + + if (!CronExpression.TryParse(general.ScheduleSettings.CronUpdateSchedule, CronFormat.Standard, out _)) + throw new ControllerArgumentException("The Cron task could not be parsed. Check the format of the entered data."); + + GeneralConfig = general; + + return true; } -- 2.43.0 From e1ad287da1131c89f3d6b169a8d768e304567f3c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:47:55 +0300 Subject: [PATCH 124/474] build: add missing reference --- Endpoint/Endpoint.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 0191a2e..a1e08aa 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -22,8 +22,9 @@ - + + -- 2.43.0 From c427006283c824d21b29f9d2b56f796eb87edc07 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 03:52:31 +0300 Subject: [PATCH 125/474] docs: add env data --- .env | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/.env b/.env index af67fc3..f06ff63 100644 --- a/.env +++ b/.env @@ -16,4 +16,86 @@ # databases (if Sqlite) and other data that should be saved in a place other than the place where the program is launched. # REQUIRED if the application is inside the container # If you want to change this value, you need to change the values in Settings.json and move the file itself to the desired location. -PATH_TO_SAVE= \ No newline at end of file +PATH_TO_SAVE= + +# Security + +# JWT signature token +# string (UTF8) +# This token will be used to create and verify the signature of JWT tokens. +# The token must be equal to 64 characters +SECURITY_SIGNING_TOKEN= + +# Token for JWT encryption +# string (UTF8) +# This token will be used to encrypt and decrypt JWT tokens. +# The token must be equal to 32 characters +SECURITY_ENCRYPTION_TOKEN= + +# Time in minutes, which indicates after which time the Refresh Token will become invalid +# integer +# The token indicates how long after the user is inactive, he will need to log in again +SECURITY_LIFE_TIME_RT=1440 + +# The time in a minute, which indicates that this is exactly what it takes to become a non-state +# integer +# Do not specify a time that is too long or too short. Optimally 5 > x > 60 +SECURITY_LIFE_TIME_JWT=15 + +# Time in minutes, which indicates after which time the token of the first factor will become invalid +# integer +# Do not specify a short time. The user must be able to log in using the second factor +SECURITY_LIFE_TIME_1_FA=15 + +# An identifier that points to the server that created the token +# string +SECURITY_JWT_ISSUER= + +# ID of the audience for which the token is intended +# string +SECURITY_JWT_AUDIENCE= + +### Hashing + +# In order to set up hashing correctly, you need to start from the security requirements +# You can use the settings that were used in https://github.com/P-H-C/phc-winner-argon2 +# These parameters have a STRONG impact on performance +# When testing the system, these values were used: +# 10 <= SECURITY_HASH_ITERATION <= 25 iterations +# 16384 <= SECURITY_HASH_MEMORY <= 32768 KB +# 4 <= SECURITY_HASH_PARALLELISM <= 8 lines +# If we take all the large values, it will take a little more than 1 second to get the hash. If this time is critical, reduce the parameters + +# The number of iterations used to hash passwords in the Argon2 algorithm +# integer +# This parameter determines the number of iterations that the Argon2 algorithm goes through when hashing passwords. +# Increasing this value can improve security by increasing the time it takes to calculate the password hash. +# The average number of iterations to increase the security level should be set to at least 10. +SECURITY_HASH_ITERATION= + +# The amount of memory used to hash passwords in the Argon2 algorithm +# integer +# 65536 +# This parameter determines the number of kilobytes of memory that will be used for the password hashing process. +# Increasing this value may increase security, but it may also require more system resources. +SECURITY_HASH_MEMORY= + +# Parallelism determines how many of the memory fragments divided into strips will be used to generate a hash +# integer +# This value affects the hash itself, but can be changed to achieve an ideal execution time, taking into account the processor and the number of cores. +SECURITY_HASH_PARALLELISM= + +# The size of the output hash generated by the password hashing algorithm +# integer +SECURITY_HASH_SIZE=32 + +# Additional protection for Argon2 +# string (BASE64) +# (optional) +# We recommend installing a token so that even if the data is compromised, an attacker cannot brute force a password without a token +SECURITY_HASH_TOKEN= + +# The size of the salt used to hash passwords +# integer +# The salt is a random value added to the password before hashing to prevent the use of rainbow hash tables and other attacks. +SECURITY_SALT_SIZE=16 \ No newline at end of file -- 2.43.0 From 7e283fe6437f90eb9b2529541399884d93fc6047 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:03:20 +0300 Subject: [PATCH 126/474] feat: add security layer --- Security/Security.csproj | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Security/Security.csproj diff --git a/Security/Security.csproj b/Security/Security.csproj new file mode 100644 index 0000000..94ec65a --- /dev/null +++ b/Security/Security.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + disable + enable + Winsomnia + 1.0.0-a0 + 1.0.0.0 + 1.0.0.0 + Mirea.Api.Security + $(AssemblyName) + + + -- 2.43.0 From 930edd4c2c83f36185d3b165c7fa9c35a68f2687 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:03:47 +0300 Subject: [PATCH 127/474] build: add ref --- Security/Security.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Security/Security.csproj b/Security/Security.csproj index 94ec65a..d9e1953 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -12,4 +12,8 @@ $(AssemblyName) + + + + -- 2.43.0 From e1123cf36b98515a841199b7a5b18b7e7f91e1a4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:04:02 +0300 Subject: [PATCH 128/474] feat: add password hashing --- Security/PasswordHashService.cs | 76 +++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 Security/PasswordHashService.cs diff --git a/Security/PasswordHashService.cs b/Security/PasswordHashService.cs new file mode 100644 index 0000000..a299901 --- /dev/null +++ b/Security/PasswordHashService.cs @@ -0,0 +1,76 @@ +using System.Buffers.Text; +using System.Text; +using Konscious.Security.Cryptography; + +namespace Security; + +public class PasswordHashService +{ + public int SaltSize { private get; init; } + public int HashSize { private get; init; } + public int Iterations { private get; init; } + public int Memory { private get; init; } + public int Parallelism { private get; init; } + public string? Secret { private get; init; } + + private ReadOnlySpan HashPassword(string password, ReadOnlySpan salt) + { + var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)) + { + Iterations = Iterations, + MemorySize = Memory, + DegreeOfParallelism = Parallelism, + Salt = salt.ToArray() + }; + + if (!string.IsNullOrEmpty(Secret)) + argon2.KnownSecret = Convert.FromBase64String(Secret); + + return argon2.GetBytes(HashSize); + } + + private static bool ConstantTimeComparison(ReadOnlySpan a, ReadOnlySpan b) + { + if (a.Length != b.Length) + return false; + + int result = 0; + for (int i = 0; i < a.Length; i++) + result |= a[i] ^ b[i]; + return result == 0; + } + + public static ReadOnlySpan GenerateRandomKeyBytes(int size) + { + var key = new byte[size]; + using var rng = System.Security.Cryptography.RandomNumberGenerator.Create(); + rng.GetNonZeroBytes(key); + return key; + } + + public static string GenerateRandomKeyStringBase64(int size) => + Convert.ToBase64String(GenerateRandomKeyBytes(size)); + + public static string GenerateRandomKeyString(int size) + { + var randomBytes = GenerateRandomKeyBytes(size); + Span utf8Bytes = new byte[Base64.GetMaxEncodedToUtf8Length(randomBytes.Length)]; + + Base64.EncodeToUtf8(randomBytes, utf8Bytes, out _, out _); + return Encoding.UTF8.GetString(utf8Bytes); + } + + public (string Salt, string Hash) HashPassword(string password) + { + var salt = GenerateRandomKeyBytes(SaltSize); + var hash = HashPassword(password, salt); + + return (Convert.ToBase64String(salt), Convert.ToBase64String(hash)); + } + + public bool VerifyPassword(string password, ReadOnlySpan salt, ReadOnlySpan hash) => + ConstantTimeComparison(HashPassword(password, salt), hash); + + public bool VerifyPassword(string password, string saltBase64, string hashBase64) => + VerifyPassword(password, Convert.FromBase64String(saltBase64), Convert.FromBase64String(hashBase64)); +} \ No newline at end of file -- 2.43.0 From 3149f50586cb54dde41731623f6e1dcb7c4ff72d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:05:18 +0300 Subject: [PATCH 129/474] refactor: move class to correct namespace --- Security/PasswordHashService.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Security/PasswordHashService.cs b/Security/PasswordHashService.cs index a299901..9ea24b3 100644 --- a/Security/PasswordHashService.cs +++ b/Security/PasswordHashService.cs @@ -1,8 +1,9 @@ -using System.Buffers.Text; +using Konscious.Security.Cryptography; +using System; +using System.Buffers.Text; using System.Text; -using Konscious.Security.Cryptography; -namespace Security; +namespace Mirea.Api.Security; public class PasswordHashService { -- 2.43.0 From e3dd0a84190cf3414e5639d912f78a8e793952e8 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:08:51 +0300 Subject: [PATCH 130/474] build: add ref for DI --- Security/Security.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Security/Security.csproj b/Security/Security.csproj index d9e1953..218d9f6 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -14,6 +14,8 @@ + + -- 2.43.0 From 656d7dca0baa92246884fe31f4b27d1e6bffb482 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:09:10 +0300 Subject: [PATCH 131/474] feat: add DI --- Security/DependencyInjection.cs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Security/DependencyInjection.cs diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs new file mode 100644 index 0000000..4dbab98 --- /dev/null +++ b/Security/DependencyInjection.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Mirea.Api.Security; + +public static class DependencyInjection +{ + public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration) + { + var saltSize = int.Parse(configuration["SECURITY_SALT_SIZE"]!); + var hashSize = int.Parse(configuration["SECURITY_HASH_SIZE"]!); + var iteration = int.Parse(configuration["SECURITY_HASH_ITERATION"]!); + var memory = int.Parse(configuration["SECURITY_HASH_MEMORY"]!); + var parallelism = int.Parse(configuration["SECURITY_HASH_PARALLELISM"]!); + + services.AddSingleton(new PasswordHashService + { + SaltSize = saltSize, + HashSize = hashSize, + Iterations = iteration, + Memory = memory, + Parallelism = parallelism, + Secret = configuration["SECURITY_HASH_TOKEN"] + }); + + return services; + } +} \ No newline at end of file -- 2.43.0 From 6029ea3c2cc20ebfa1958ce5524d5db83b82e25a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:11:04 +0300 Subject: [PATCH 132/474] refactor: move hashing to services --- Security/DependencyInjection.cs | 3 ++- Security/{ => Services}/PasswordHashService.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename Security/{ => Services}/PasswordHashService.cs (98%) diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs index 4dbab98..39e113a 100644 --- a/Security/DependencyInjection.cs +++ b/Security/DependencyInjection.cs @@ -1,11 +1,12 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.Security.Services; namespace Mirea.Api.Security; public static class DependencyInjection { - public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddSecurityServices(this IServiceCollection services, IConfiguration configuration) { var saltSize = int.Parse(configuration["SECURITY_SALT_SIZE"]!); var hashSize = int.Parse(configuration["SECURITY_HASH_SIZE"]!); diff --git a/Security/PasswordHashService.cs b/Security/Services/PasswordHashService.cs similarity index 98% rename from Security/PasswordHashService.cs rename to Security/Services/PasswordHashService.cs index 9ea24b3..b9cc41c 100644 --- a/Security/PasswordHashService.cs +++ b/Security/Services/PasswordHashService.cs @@ -3,7 +3,7 @@ using System; using System.Buffers.Text; using System.Text; -namespace Mirea.Api.Security; +namespace Mirea.Api.Security.Services; public class PasswordHashService { -- 2.43.0 From f749ed42f5ba34458a238db9befbc7a66ec4545e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:29:50 +0300 Subject: [PATCH 133/474] feat: add interface for save to cache --- Security/Common/Interfaces/ICacheService.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Security/Common/Interfaces/ICacheService.cs diff --git a/Security/Common/Interfaces/ICacheService.cs b/Security/Common/Interfaces/ICacheService.cs new file mode 100644 index 0000000..c2a419b --- /dev/null +++ b/Security/Common/Interfaces/ICacheService.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.Security.Common.Interfaces; + +public interface ICacheService +{ + Task SetAsync(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, CancellationToken cancellationToken = default); + Task GetAsync(string key, CancellationToken cancellationToken = default); + Task RemoveAsync(string key, CancellationToken cancellationToken = default); +} -- 2.43.0 From 58ceca5313fa714f0418be3142c697269a7691b7 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:30:32 +0300 Subject: [PATCH 134/474] feat: add pre-auth token structure --- Security/Common/Domain/PreAuthToken.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Security/Common/Domain/PreAuthToken.cs diff --git a/Security/Common/Domain/PreAuthToken.cs b/Security/Common/Domain/PreAuthToken.cs new file mode 100644 index 0000000..4da9f6d --- /dev/null +++ b/Security/Common/Domain/PreAuthToken.cs @@ -0,0 +1,9 @@ +namespace Mirea.Api.Security.Common.Domain; + +public class PreAuthToken +{ + public required string Fingerprint { get; set; } + public required string UserAgent { get; set; } + public required string UserId { get; set; } + public required string Token { get; set; } +} \ No newline at end of file -- 2.43.0 From e3db6b73e01fb86d983074190b3b27111beddf7c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:30:55 +0300 Subject: [PATCH 135/474] feat: add pre-auth response --- Security/Common/Dto/Responses/PreAuthTokenResponse.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Security/Common/Dto/Responses/PreAuthTokenResponse.cs diff --git a/Security/Common/Dto/Responses/PreAuthTokenResponse.cs b/Security/Common/Dto/Responses/PreAuthTokenResponse.cs new file mode 100644 index 0000000..9a7238f --- /dev/null +++ b/Security/Common/Dto/Responses/PreAuthTokenResponse.cs @@ -0,0 +1,9 @@ +using System; + +namespace Mirea.Api.Security.Common.Dto.Responses; + +public class PreAuthTokenResponse +{ + public required string Token { get; set; } + public DateTime ExpiresIn { get; set; } +} \ No newline at end of file -- 2.43.0 From 3c9694de08a9870453371f645f7616c6e8af0805 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:31:19 +0300 Subject: [PATCH 136/474] feat: add request for get token --- Security/Common/Dto/Requests/TokenRequest.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Security/Common/Dto/Requests/TokenRequest.cs diff --git a/Security/Common/Dto/Requests/TokenRequest.cs b/Security/Common/Dto/Requests/TokenRequest.cs new file mode 100644 index 0000000..8be8038 --- /dev/null +++ b/Security/Common/Dto/Requests/TokenRequest.cs @@ -0,0 +1,8 @@ +namespace Mirea.Api.Security.Common.Dto.Requests; + +public class TokenRequest +{ + public required string Fingerprint { get; set; } + public required string UserAgent { get; set; } + public required string Ip { get; set; } +} \ No newline at end of file -- 2.43.0 From b14ae26a4832ec8f41f43df58655e39f6151843d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:31:47 +0300 Subject: [PATCH 137/474] feat: add pre-auth service --- Security/Services/PreAuthService.cs | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Security/Services/PreAuthService.cs diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs new file mode 100644 index 0000000..2a1929c --- /dev/null +++ b/Security/Services/PreAuthService.cs @@ -0,0 +1,42 @@ +using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Dto.Requests; +using Mirea.Api.Security.Common.Dto.Responses; +using Mirea.Api.Security.Common.Interfaces; +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.Security.Services; + +public class PreAuthService(ICacheService cache) +{ + public TimeSpan Lifetime { private get; init; } + + private static string GenerateFirstAuthToken() => Guid.NewGuid().ToString().Replace("-", ""); + + public async Task CreateLoginTokenAsync(TokenRequest request, string userId, CancellationToken cancellation = default) + { + var firstAuthToken = GenerateFirstAuthToken(); + + var loginStructure = new PreAuthToken + { + Fingerprint = request.Fingerprint, + UserId = userId, + UserAgent = request.UserAgent, + Token = firstAuthToken + }; + + await cache.SetAsync( + request.Fingerprint, + JsonSerializer.SerializeToUtf8Bytes(loginStructure), + Lifetime, + cancellation); + + return new PreAuthTokenResponse + { + Token = firstAuthToken, + ExpiresIn = DateTime.UtcNow.Add(Lifetime) + }; + } +} \ No newline at end of file -- 2.43.0 From 8408b80c35258aceba37ae9662407779957566c3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:34:00 +0300 Subject: [PATCH 138/474] feat: add pre-auth to DI --- Security/DependencyInjection.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs index 39e113a..5441ebc 100644 --- a/Security/DependencyInjection.cs +++ b/Security/DependencyInjection.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.Security.Common.Interfaces; using Mirea.Api.Security.Services; +using System; namespace Mirea.Api.Security; @@ -24,6 +26,18 @@ public static class DependencyInjection Secret = configuration["SECURITY_HASH_TOKEN"] }); + var lifeTimeLogin = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_1_FA"]!)); + + services.AddSingleton(provider => + { + var cache = provider.GetRequiredService(); + + return new PreAuthService(cache) + { + Lifetime = lifeTimeLogin + }; + }); + return services; } } \ No newline at end of file -- 2.43.0 From 5fde5bd3967f0ce86a7490ab3321364add1feb2a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:34:39 +0300 Subject: [PATCH 139/474] build: add security to sln --- Backend.sln | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Backend.sln b/Backend.sln index 95ebfb7..5790acf 100644 --- a/Backend.sln +++ b/Backend.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiDto", "ApiDto\ApiDto.csproj", "{0335FA36-E137-453F-853B-916674C168FE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Security", "Security\Security.csproj", "{47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,10 @@ Global {0335FA36-E137-453F-853B-916674C168FE}.Debug|Any CPU.Build.0 = Debug|Any CPU {0335FA36-E137-453F-853B-916674C168FE}.Release|Any CPU.ActiveCfg = Release|Any CPU {0335FA36-E137-453F-853B-916674C168FE}.Release|Any CPU.Build.0 = Release|Any CPU + {47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE -- 2.43.0 From d05ba5349fd44eeef83335584d28bd9c2a4a08df Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:48:37 +0300 Subject: [PATCH 140/474] refactor: isolate key generation --- Security/Services/GeneratorKey.cs | 28 ++++++++++++++++++++++++ Security/Services/PasswordHashService.cs | 23 +------------------ 2 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 Security/Services/GeneratorKey.cs diff --git a/Security/Services/GeneratorKey.cs b/Security/Services/GeneratorKey.cs new file mode 100644 index 0000000..79a0430 --- /dev/null +++ b/Security/Services/GeneratorKey.cs @@ -0,0 +1,28 @@ +using System; +using System.Buffers.Text; +using System.Text; + +namespace Mirea.Api.Security.Services; + +public static class GeneratorKey +{ + public static ReadOnlySpan GenerateBytes(int size) + { + var key = new byte[size]; + using var rng = System.Security.Cryptography.RandomNumberGenerator.Create(); + rng.GetNonZeroBytes(key); + return key; + } + + public static string GenerateBase64(int size) => + Convert.ToBase64String(GenerateBytes(size)); + + public static string GenerateString(int size) + { + var randomBytes = GenerateBytes(size); + Span utf8Bytes = new byte[Base64.GetMaxEncodedToUtf8Length(randomBytes.Length)]; + + Base64.EncodeToUtf8(randomBytes, utf8Bytes, out _, out _); + return Encoding.UTF8.GetString(utf8Bytes); + } +} \ No newline at end of file diff --git a/Security/Services/PasswordHashService.cs b/Security/Services/PasswordHashService.cs index b9cc41c..8673222 100644 --- a/Security/Services/PasswordHashService.cs +++ b/Security/Services/PasswordHashService.cs @@ -1,6 +1,5 @@ using Konscious.Security.Cryptography; using System; -using System.Buffers.Text; using System.Text; namespace Mirea.Api.Security.Services; @@ -41,29 +40,9 @@ public class PasswordHashService return result == 0; } - public static ReadOnlySpan GenerateRandomKeyBytes(int size) - { - var key = new byte[size]; - using var rng = System.Security.Cryptography.RandomNumberGenerator.Create(); - rng.GetNonZeroBytes(key); - return key; - } - - public static string GenerateRandomKeyStringBase64(int size) => - Convert.ToBase64String(GenerateRandomKeyBytes(size)); - - public static string GenerateRandomKeyString(int size) - { - var randomBytes = GenerateRandomKeyBytes(size); - Span utf8Bytes = new byte[Base64.GetMaxEncodedToUtf8Length(randomBytes.Length)]; - - Base64.EncodeToUtf8(randomBytes, utf8Bytes, out _, out _); - return Encoding.UTF8.GetString(utf8Bytes); - } - public (string Salt, string Hash) HashPassword(string password) { - var salt = GenerateRandomKeyBytes(SaltSize); + var salt = GeneratorKey.GenerateBytes(SaltSize); var hash = HashPassword(password, salt); return (Convert.ToBase64String(salt), Convert.ToBase64String(hash)); -- 2.43.0 From 47a57693f86d8f64329b35dc7eef992071d47c8d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:55:34 +0300 Subject: [PATCH 141/474] sec: complicate the token --- Security/Services/PreAuthService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs index 2a1929c..21c97ff 100644 --- a/Security/Services/PreAuthService.cs +++ b/Security/Services/PreAuthService.cs @@ -13,7 +13,8 @@ public class PreAuthService(ICacheService cache) { public TimeSpan Lifetime { private get; init; } - private static string GenerateFirstAuthToken() => Guid.NewGuid().ToString().Replace("-", ""); + private static string GeneratePreAuthToken() => Guid.NewGuid().ToString().Replace("-", "") + + GeneratorKey.GenerateString(16); public async Task CreateLoginTokenAsync(TokenRequest request, string userId, CancellationToken cancellation = default) { -- 2.43.0 From ac7bbde75e07774428ce8d37d382629ffd937765 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:57:44 +0300 Subject: [PATCH 142/474] fix: add key for save pre auth token --- Security/Services/PreAuthService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs index 21c97ff..484c5a1 100644 --- a/Security/Services/PreAuthService.cs +++ b/Security/Services/PreAuthService.cs @@ -16,7 +16,7 @@ public class PreAuthService(ICacheService cache) private static string GeneratePreAuthToken() => Guid.NewGuid().ToString().Replace("-", "") + GeneratorKey.GenerateString(16); - public async Task CreateLoginTokenAsync(TokenRequest request, string userId, CancellationToken cancellation = default) + private static string GetPreAuthCacheKey(string fingerprint) => $"{fingerprint}_pre_auth_token"; { var firstAuthToken = GenerateFirstAuthToken(); @@ -29,7 +29,7 @@ public class PreAuthService(ICacheService cache) }; await cache.SetAsync( - request.Fingerprint, + GetPreAuthCacheKey(request.Fingerprint), JsonSerializer.SerializeToUtf8Bytes(loginStructure), Lifetime, cancellation); -- 2.43.0 From f4ad1518ef5cbae22c0bc4531c8a2742cdf1d40f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 04:58:21 +0300 Subject: [PATCH 143/474] style: rename variables --- Security/Services/PreAuthService.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs index 484c5a1..cc189dd 100644 --- a/Security/Services/PreAuthService.cs +++ b/Security/Services/PreAuthService.cs @@ -17,26 +17,28 @@ public class PreAuthService(ICacheService cache) GeneratorKey.GenerateString(16); private static string GetPreAuthCacheKey(string fingerprint) => $"{fingerprint}_pre_auth_token"; - { - var firstAuthToken = GenerateFirstAuthToken(); - var loginStructure = new PreAuthToken + public async Task GeneratePreAuthTokenAsync(TokenRequest request, string userId, CancellationToken cancellation = default) + { + var preAuthToken = GeneratePreAuthToken(); + + var preAuthTokenStruct = new PreAuthToken { Fingerprint = request.Fingerprint, UserId = userId, UserAgent = request.UserAgent, - Token = firstAuthToken + Token = preAuthToken }; await cache.SetAsync( GetPreAuthCacheKey(request.Fingerprint), - JsonSerializer.SerializeToUtf8Bytes(loginStructure), + JsonSerializer.SerializeToUtf8Bytes(preAuthTokenStruct), Lifetime, cancellation); return new PreAuthTokenResponse { - Token = firstAuthToken, + Token = preAuthToken, ExpiresIn = DateTime.UtcNow.Add(Lifetime) }; } -- 2.43.0 From 916b3795ed7fc62414e23d9657d76f102ea3bb1e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:27:27 +0300 Subject: [PATCH 144/474] feat: add ip to struct --- Security/Common/Domain/PreAuthToken.cs | 1 + Security/Services/PreAuthService.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Security/Common/Domain/PreAuthToken.cs b/Security/Common/Domain/PreAuthToken.cs index 4da9f6d..f1f9684 100644 --- a/Security/Common/Domain/PreAuthToken.cs +++ b/Security/Common/Domain/PreAuthToken.cs @@ -5,5 +5,6 @@ public class PreAuthToken public required string Fingerprint { get; set; } public required string UserAgent { get; set; } public required string UserId { get; set; } + public required string Ip { get; set; } public required string Token { get; set; } } \ No newline at end of file diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs index cc189dd..2bb5390 100644 --- a/Security/Services/PreAuthService.cs +++ b/Security/Services/PreAuthService.cs @@ -27,7 +27,8 @@ public class PreAuthService(ICacheService cache) Fingerprint = request.Fingerprint, UserId = userId, UserAgent = request.UserAgent, - Token = preAuthToken + Token = preAuthToken, + Ip = request.Ip }; await cache.SetAsync( -- 2.43.0 From 470031af39fb36c4068a36d87b2f46822243e27a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:27:49 +0300 Subject: [PATCH 145/474] feat: add match token --- Security/Services/PreAuthService.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs index 2bb5390..1a0443e 100644 --- a/Security/Services/PreAuthService.cs +++ b/Security/Services/PreAuthService.cs @@ -3,6 +3,7 @@ using Mirea.Api.Security.Common.Dto.Requests; using Mirea.Api.Security.Common.Dto.Responses; using Mirea.Api.Security.Common.Interfaces; using System; +using System.Security; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -43,4 +44,21 @@ public class PreAuthService(ICacheService cache) ExpiresIn = DateTime.UtcNow.Add(Lifetime) }; } + public async Task MatchToken(TokenRequest request, string preAuthToken, CancellationToken cancellation = default) + { + var preAuthTokenStruct = await cache.GetAsync(GetPreAuthCacheKey(request.Fingerprint), cancellation) + ?? throw new SecurityException($"The token was not found using fingerprint \"{request.Fingerprint}\""); + + if (preAuthTokenStruct == null || + preAuthTokenStruct.Token != preAuthToken || + (preAuthTokenStruct.UserAgent != request.UserAgent && + preAuthTokenStruct.Ip != request.Ip)) + { + throw new SecurityException("It was not possible to verify the authenticity of the token"); + } + + await cache.RemoveAsync(GetPreAuthCacheKey(request.Fingerprint), cancellation); + + return preAuthTokenStruct.UserId; + } } \ No newline at end of file -- 2.43.0 From d3a60d2a30f12d26c9212d8508d2e6f768a222dc Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:29:25 +0300 Subject: [PATCH 146/474] feat: add interface for gen access token --- Security/Common/Interfaces/IAccessToken.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Security/Common/Interfaces/IAccessToken.cs diff --git a/Security/Common/Interfaces/IAccessToken.cs b/Security/Common/Interfaces/IAccessToken.cs new file mode 100644 index 0000000..a2ebed2 --- /dev/null +++ b/Security/Common/Interfaces/IAccessToken.cs @@ -0,0 +1,9 @@ +using System; + +namespace Mirea.Api.Security.Common.Interfaces; + +public interface IAccessToken +{ + (string Token, DateTime ExpireIn) GenerateToken(string userId); + DateTimeOffset GetExpireDateTime(string token); +} \ No newline at end of file -- 2.43.0 From f55d701ff330331bc635187f8163901f28b9ebac Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:30:00 +0300 Subject: [PATCH 147/474] feat: add sliding expiration for cache --- Security/Common/Interfaces/ICacheService.cs | 6 +++++- Security/Services/PreAuthService.cs | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Security/Common/Interfaces/ICacheService.cs b/Security/Common/Interfaces/ICacheService.cs index c2a419b..c2cb1e3 100644 --- a/Security/Common/Interfaces/ICacheService.cs +++ b/Security/Common/Interfaces/ICacheService.cs @@ -6,7 +6,11 @@ namespace Mirea.Api.Security.Common.Interfaces; public interface ICacheService { - Task SetAsync(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, CancellationToken cancellationToken = default); + Task SetAsync(string key, T value, + TimeSpan? absoluteExpirationRelativeToNow = null, + TimeSpan? slidingExpiration = null, + CancellationToken cancellationToken = default); + Task GetAsync(string key, CancellationToken cancellationToken = default); Task RemoveAsync(string key, CancellationToken cancellationToken = default); } diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs index 1a0443e..948b997 100644 --- a/Security/Services/PreAuthService.cs +++ b/Security/Services/PreAuthService.cs @@ -14,7 +14,7 @@ public class PreAuthService(ICacheService cache) { public TimeSpan Lifetime { private get; init; } - private static string GeneratePreAuthToken() => Guid.NewGuid().ToString().Replace("-", "") + + private static string GeneratePreAuthToken() => Guid.NewGuid().ToString().Replace("-", "") + GeneratorKey.GenerateString(16); private static string GetPreAuthCacheKey(string fingerprint) => $"{fingerprint}_pre_auth_token"; @@ -35,8 +35,8 @@ public class PreAuthService(ICacheService cache) await cache.SetAsync( GetPreAuthCacheKey(request.Fingerprint), JsonSerializer.SerializeToUtf8Bytes(preAuthTokenStruct), - Lifetime, - cancellation); + absoluteExpirationRelativeToNow: Lifetime, + cancellationToken: cancellation); return new PreAuthTokenResponse { -- 2.43.0 From 7df4c8e4b6d5f97a32cc8099b65fc4b4a888de5e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:32:22 +0300 Subject: [PATCH 148/474] feat: add auth service --- Security/Services/AuthService.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Security/Services/AuthService.cs diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs new file mode 100644 index 0000000..97aca20 --- /dev/null +++ b/Security/Services/AuthService.cs @@ -0,0 +1,8 @@ +using System; + +namespace Mirea.Api.Security.Services; + +public class AuthService() +{ + public TimeSpan Lifetime { private get; init; } +} \ No newline at end of file -- 2.43.0 From b25be758addb7e5207be39e5e7a28a2971cbb706 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:33:55 +0300 Subject: [PATCH 149/474] feat: add auth token --- Security/Common/Domain/AuthToken.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Security/Common/Domain/AuthToken.cs diff --git a/Security/Common/Domain/AuthToken.cs b/Security/Common/Domain/AuthToken.cs new file mode 100644 index 0000000..4572e62 --- /dev/null +++ b/Security/Common/Domain/AuthToken.cs @@ -0,0 +1,13 @@ +using System; + +namespace Mirea.Api.Security.Common.Domain; + +public class AuthToken +{ + public required string RefreshToken { get; set; } + public required string UserAgent { get; set; } + public required string Ip { get; set; } + public required string UserId { get; set; } + public required string AccessToken { get; set; } + public DateTime CreatedAt { get; set; } +} \ No newline at end of file -- 2.43.0 From a3a42dd5c2fdfcadbffa51e9dfef4ac037894654 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:35:44 +0300 Subject: [PATCH 150/474] feat: add generate refresh token --- Security/Services/AuthService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 97aca20..107b7e8 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -5,4 +5,7 @@ namespace Mirea.Api.Security.Services; public class AuthService() { public TimeSpan Lifetime { private get; init; } + + private static string GenerateRefreshToken() => Guid.NewGuid().ToString().Replace("-", "") + + GeneratorKey.GenerateString(32); } \ No newline at end of file -- 2.43.0 From 4240ad8110d65c94fd908ef171749d0500a46e16 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:36:26 +0300 Subject: [PATCH 151/474] feat: add auth key for cache --- Security/Services/AuthService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 107b7e8..e13f565 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -8,4 +8,6 @@ public class AuthService() private static string GenerateRefreshToken() => Guid.NewGuid().ToString().Replace("-", "") + GeneratorKey.GenerateString(32); + + private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token"; } \ No newline at end of file -- 2.43.0 From 43011457d678e79212467c3923a146e873d9e534 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:50:47 +0300 Subject: [PATCH 152/474] feat: add wrap for save to cache --- Security/Services/AuthService.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index e13f565..90ceb14 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -1,8 +1,12 @@ -using System; +using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Dto.Requests; +using Mirea.Api.Security.Common.Dto.Responses; +using Mirea.Api.Security.Common.Interfaces; +using System; namespace Mirea.Api.Security.Services; -public class AuthService() +public class AuthService(ICacheService cache) { public TimeSpan Lifetime { private get; init; } @@ -10,4 +14,11 @@ public class AuthService() GeneratorKey.GenerateString(32); private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token"; + + private Task SetAuthTokenDataToCache(string fingerprint, AuthToken data, CancellationToken cancellation) => + cache.SetAsync( + GetAuthCacheKey(fingerprint), + JsonSerializer.SerializeToUtf8Bytes(data), + slidingExpiration: Lifetime, + cancellationToken: cancellation); } \ No newline at end of file -- 2.43.0 From f3063c53221a27b4c093891706565227469faada Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:51:03 +0300 Subject: [PATCH 153/474] feat: add generate access token --- Security/Services/AuthService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 90ceb14..c0be65c 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -6,12 +6,14 @@ using System; namespace Mirea.Api.Security.Services; -public class AuthService(ICacheService cache) +public class AuthService(ICacheService cache, IAccessToken accessTokenService) { public TimeSpan Lifetime { private get; init; } private static string GenerateRefreshToken() => Guid.NewGuid().ToString().Replace("-", "") + GeneratorKey.GenerateString(32); + private (string Token, DateTime ExpireIn) GenerateAccessToken(string userId) => + accessTokenService.GenerateToken(userId); private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token"; -- 2.43.0 From 81f2f995b051fb881b076cc5d63789594458650e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:51:32 +0300 Subject: [PATCH 154/474] feat: add generate auth token --- Security/Services/AuthService.cs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index c0be65c..471ba37 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -3,6 +3,10 @@ using Mirea.Api.Security.Common.Dto.Requests; using Mirea.Api.Security.Common.Dto.Responses; using Mirea.Api.Security.Common.Interfaces; using System; +using System.Security; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; namespace Mirea.Api.Security.Services; @@ -23,4 +27,31 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService) JsonSerializer.SerializeToUtf8Bytes(data), slidingExpiration: Lifetime, cancellationToken: cancellation); + + public async Task GenerateAuthTokensAsync(TokenRequest request, string preAuthToken, CancellationToken cancellation = default) + { + string userId = await new PreAuthService(cache).MatchToken(request, preAuthToken, cancellation); + + var refreshToken = GenerateRefreshToken(); + var accessToken = GenerateAccessToken(userId); + + var authTokenStruct = new AuthToken + { + CreatedAt = DateTime.UtcNow, + Ip = request.Ip, + RefreshToken = refreshToken, + UserAgent = request.UserAgent, + UserId = userId, + AccessToken = accessToken.Token + }; + + await SetAuthTokenDataToCache(request.Fingerprint, authTokenStruct, cancellation); + + return new AuthTokenResponse + { + AccessToken = accessToken.Token, + ExpiresIn = accessToken.ExpireIn, + RefreshToken = authTokenStruct.RefreshToken + }; + } } \ No newline at end of file -- 2.43.0 From 79fb05d428526a1b57918a3871c6ee769aaf1243 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:54:45 +0300 Subject: [PATCH 155/474] feat: add token revocation --- Security/Common/Interfaces/IRevokedToken.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Security/Common/Interfaces/IRevokedToken.cs diff --git a/Security/Common/Interfaces/IRevokedToken.cs b/Security/Common/Interfaces/IRevokedToken.cs new file mode 100644 index 0000000..d8d9edc --- /dev/null +++ b/Security/Common/Interfaces/IRevokedToken.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace Mirea.Api.Security.Common.Interfaces; + +public interface IRevokedToken +{ + Task AddTokenToRevokedAsync(string token, DateTimeOffset expiresIn); + Task IsTokenRevokedAsync(string token); +} \ No newline at end of file -- 2.43.0 From 9dd505a608de358fdf08b520c3b770b21ce63b9e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:55:13 +0300 Subject: [PATCH 156/474] feat: add auth token response --- Security/Common/Dto/Responses/AuthTokenResponse.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Security/Common/Dto/Responses/AuthTokenResponse.cs diff --git a/Security/Common/Dto/Responses/AuthTokenResponse.cs b/Security/Common/Dto/Responses/AuthTokenResponse.cs new file mode 100644 index 0000000..0c8a3d4 --- /dev/null +++ b/Security/Common/Dto/Responses/AuthTokenResponse.cs @@ -0,0 +1,10 @@ +using System; + +namespace Mirea.Api.Security.Common.Dto.Responses; + +public class AuthTokenResponse +{ + public required string AccessToken { get; set; } + public required string RefreshToken { get; set; } + public DateTime ExpiresIn { get; set; } +} \ No newline at end of file -- 2.43.0 From 4138c7000757f6b1740d4295f88e1f7998cf25be Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:55:31 +0300 Subject: [PATCH 157/474] feat: add wrap for revoke access token --- Security/Services/AuthService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 471ba37..392e721 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Security.Services; -public class AuthService(ICacheService cache, IAccessToken accessTokenService) +public class AuthService(ICacheService cache, IAccessToken accessTokenService, IRevokedToken revokedToken) { public TimeSpan Lifetime { private get; init; } @@ -28,6 +28,9 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService) slidingExpiration: Lifetime, cancellationToken: cancellation); + private Task RevokeAccessToken(string token) => + revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); + public async Task GenerateAuthTokensAsync(TokenRequest request, string preAuthToken, CancellationToken cancellation = default) { string userId = await new PreAuthService(cache).MatchToken(request, preAuthToken, cancellation); -- 2.43.0 From d84011cd71d71be0eb6e7238f99dac7c12f5140e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:55:57 +0300 Subject: [PATCH 158/474] feat: add refresh token --- Security/Services/AuthService.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 392e721..9542249 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -57,4 +57,33 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I RefreshToken = authTokenStruct.RefreshToken }; } + + public async Task RefreshTokenAsync(TokenRequest request, string refreshToken, CancellationToken cancellation = default) + { + var authToken = await cache.GetAsync(GetAuthCacheKey(request.Fingerprint), cancellation) + ?? throw new SecurityException(request.Fingerprint); + + if (authToken.RefreshToken != refreshToken || + authToken.UserAgent != request.UserAgent && + authToken.Ip != request.Ip) + { + await cache.RemoveAsync(request.Fingerprint, cancellation); + await RevokeAccessToken(authToken.AccessToken); + + throw new SecurityException(request.Fingerprint); + } + + var accessToken = GenerateAccessToken(authToken.UserId); + await RevokeAccessToken(authToken.AccessToken); + + authToken.AccessToken = accessToken.Token; + await SetAuthTokenDataToCache(request.Fingerprint, authToken, cancellation); + + return new AuthTokenResponse + { + AccessToken = accessToken.Token, + ExpiresIn = accessToken.ExpireIn, + RefreshToken = GenerateRefreshToken() + }; + } } \ No newline at end of file -- 2.43.0 From 61218c38a0acfd5d361861b18a158e289938162d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 05:56:27 +0300 Subject: [PATCH 159/474] feat: add logout --- Security/Services/AuthService.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 9542249..58d439b 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -86,4 +86,14 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I RefreshToken = GenerateRefreshToken() }; } + + public async Task LogoutAsync(string fingerprint, CancellationToken cancellation = default) + { + var authTokenStruct = await cache.GetAsync(GetAuthCacheKey(fingerprint), cancellation); + if (authTokenStruct == null) return; + + await RevokeAccessToken(authTokenStruct.AccessToken); + + await cache.RemoveAsync(fingerprint, cancellation); + } } \ No newline at end of file -- 2.43.0 From 25b6c7d69148c04d9a37abc4250fdceb6a2bc31a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:00:15 +0300 Subject: [PATCH 160/474] feat: add method if there is no pre-auth token --- Security/Services/AuthService.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 58d439b..5426532 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -31,10 +31,8 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I private Task RevokeAccessToken(string token) => revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); - public async Task GenerateAuthTokensAsync(TokenRequest request, string preAuthToken, CancellationToken cancellation = default) + public async Task GenerateAuthTokensAsync(TokenRequest request, string userId, CancellationToken cancellation = default) { - string userId = await new PreAuthService(cache).MatchToken(request, preAuthToken, cancellation); - var refreshToken = GenerateRefreshToken(); var accessToken = GenerateAccessToken(userId); @@ -58,6 +56,12 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I }; } + public async Task GenerateAuthTokensWithPreAuthAsync(TokenRequest request, string preAuthToken, + CancellationToken cancellation = default) => + await GenerateAuthTokensAsync(request, + await new PreAuthService(cache).MatchToken(request, preAuthToken, cancellation), + cancellation); + public async Task RefreshTokenAsync(TokenRequest request, string refreshToken, CancellationToken cancellation = default) { var authToken = await cache.GetAsync(GetAuthCacheKey(request.Fingerprint), cancellation) -- 2.43.0 From 2efdc6dbfe373914af7385c9d9a4a802636c9864 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:04:09 +0300 Subject: [PATCH 161/474] feat: add auth service to DI --- Security/DependencyInjection.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs index 5441ebc..ed16c5e 100644 --- a/Security/DependencyInjection.cs +++ b/Security/DependencyInjection.cs @@ -26,7 +26,7 @@ public static class DependencyInjection Secret = configuration["SECURITY_HASH_TOKEN"] }); - var lifeTimeLogin = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_1_FA"]!)); + var lifeTimePreAuthToken = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_1_FA"]!)); services.AddSingleton(provider => { @@ -34,7 +34,21 @@ public static class DependencyInjection return new PreAuthService(cache) { - Lifetime = lifeTimeLogin + Lifetime = lifeTimePreAuthToken + }; + }); + + var lifeTimeRefreshToken = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_RT"]!)); + + services.AddSingleton(provider => + { + var cacheService = provider.GetRequiredService(); + var accessTokenService = provider.GetRequiredService(); + var revokedTokenService = provider.GetRequiredService(); + + return new AuthService(cacheService, accessTokenService, revokedTokenService) + { + Lifetime = lifeTimeRefreshToken }; }); -- 2.43.0 From 9287acf7d269410ff1d981dfab29833cdad7e3ae Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:08:14 +0300 Subject: [PATCH 162/474] feat: add cache implementations depending on the type --- .../Security/DistributedCacheService.cs | 32 +++++++++++++++++ .../Services/Security/MemoryCacheService.cs | 34 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 Endpoint/Common/Services/Security/DistributedCacheService.cs create mode 100644 Endpoint/Common/Services/Security/MemoryCacheService.cs diff --git a/Endpoint/Common/Services/Security/DistributedCacheService.cs b/Endpoint/Common/Services/Security/DistributedCacheService.cs new file mode 100644 index 0000000..fa4c116 --- /dev/null +++ b/Endpoint/Common/Services/Security/DistributedCacheService.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Caching.Distributed; +using Mirea.Api.Security.Common.Interfaces; +using System.Text.Json; +using System.Threading.Tasks; +using System.Threading; +using System; + +namespace Mirea.Api.Endpoint.Common.Services.Security; + +public class DistributedCacheService(IDistributedCache cache) : ICacheService +{ + public async Task SetAsync(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default) + { + var options = new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow, + SlidingExpiration = slidingExpiration + }; + + var serializedValue = JsonSerializer.SerializeToUtf8Bytes(value); + await cache.SetAsync(key, serializedValue, options, cancellationToken); + } + + public async Task GetAsync(string key, CancellationToken cancellationToken = default) + { + var cachedValue = await cache.GetAsync(key, cancellationToken); + return cachedValue == null ? default : JsonSerializer.Deserialize(cachedValue); + } + + public Task RemoveAsync(string key, CancellationToken cancellationToken = default) => + cache.RemoveAsync(key, cancellationToken); +} \ No newline at end of file diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs new file mode 100644 index 0000000..08d0a7f --- /dev/null +++ b/Endpoint/Common/Services/Security/MemoryCacheService.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Caching.Memory; +using Mirea.Api.Security.Common.Interfaces; +using System.Threading.Tasks; +using System.Threading; +using System; + +namespace Mirea.Api.Endpoint.Common.Services.Security; + +public class MemoryCacheService(IMemoryCache cache) : ICacheService +{ + public Task SetAsync(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default) + { + var options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow, + SlidingExpiration = slidingExpiration + }; + + cache.Set(key, value, options); + return Task.CompletedTask; + } + + public Task GetAsync(string key, CancellationToken cancellationToken = default) + { + cache.TryGetValue(key, out T? value); + return Task.FromResult(value); + } + + public Task RemoveAsync(string key, CancellationToken cancellationToken = default) + { + cache.Remove(key); + return Task.CompletedTask; + } +} \ No newline at end of file -- 2.43.0 From 526bf5682b17f17995f3d2b47a867c3aa2c821f8 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:08:41 +0300 Subject: [PATCH 163/474] build: add security ref --- Endpoint/Endpoint.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index a1e08aa..3d4a2f6 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -32,6 +32,7 @@ + \ No newline at end of file -- 2.43.0 From 6f02021fe7b12173c7614e933d7be4b6ab580d8f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:11:18 +0300 Subject: [PATCH 164/474] feat: add revoked token service --- .../Security/MemoryRevokedTokenService.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Endpoint/Common/Services/Security/MemoryRevokedTokenService.cs diff --git a/Endpoint/Common/Services/Security/MemoryRevokedTokenService.cs b/Endpoint/Common/Services/Security/MemoryRevokedTokenService.cs new file mode 100644 index 0000000..94c2f75 --- /dev/null +++ b/Endpoint/Common/Services/Security/MemoryRevokedTokenService.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Caching.Memory; +using Mirea.Api.Security.Common.Interfaces; +using System; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Common.Services.Security; + +public class MemoryRevokedTokenService(IMemoryCache cache) : IRevokedToken +{ + public Task AddTokenToRevokedAsync(string token, DateTimeOffset expiresIn) + { + cache.Set(token, true, expiresIn); + return Task.CompletedTask; + } + + public Task IsTokenRevokedAsync(string token) => Task.FromResult(cache.TryGetValue(token, out _)); +} \ No newline at end of file -- 2.43.0 From 62a859b44c550627b0c92d899ac32c55b8a205a5 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:11:29 +0300 Subject: [PATCH 165/474] style: clean code --- .../Common/Services/Security/DistributedCacheService.cs | 6 +++--- Endpoint/Common/Services/Security/MemoryCacheService.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Endpoint/Common/Services/Security/DistributedCacheService.cs b/Endpoint/Common/Services/Security/DistributedCacheService.cs index fa4c116..bf3dc39 100644 --- a/Endpoint/Common/Services/Security/DistributedCacheService.cs +++ b/Endpoint/Common/Services/Security/DistributedCacheService.cs @@ -1,9 +1,9 @@ using Microsoft.Extensions.Caching.Distributed; using Mirea.Api.Security.Common.Interfaces; -using System.Text.Json; -using System.Threading.Tasks; -using System.Threading; using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Common.Services.Security; diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs index 08d0a7f..a428034 100644 --- a/Endpoint/Common/Services/Security/MemoryCacheService.cs +++ b/Endpoint/Common/Services/Security/MemoryCacheService.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.Caching.Memory; using Mirea.Api.Security.Common.Interfaces; -using System.Threading.Tasks; -using System.Threading; using System; +using System.Threading; +using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Common.Services.Security; -- 2.43.0 From f2aa274d0af0b665beb17c32d8295fdd405af8bf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:28:21 +0300 Subject: [PATCH 166/474] build: add jwt ref --- Endpoint/Endpoint.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 3d4a2f6..985a730 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -23,9 +23,10 @@ - + + -- 2.43.0 From 85802aa514a04677a1d9365de6d678a36dd246f5 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:28:42 +0300 Subject: [PATCH 167/474] feat: add jwt token service --- .../Services/Security/JwtTokenService.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Endpoint/Common/Services/Security/JwtTokenService.cs diff --git a/Endpoint/Common/Services/Security/JwtTokenService.cs b/Endpoint/Common/Services/Security/JwtTokenService.cs new file mode 100644 index 0000000..7c3225f --- /dev/null +++ b/Endpoint/Common/Services/Security/JwtTokenService.cs @@ -0,0 +1,82 @@ +using Microsoft.IdentityModel.Tokens; +using Mirea.Api.Security.Common.Interfaces; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; + +namespace Mirea.Api.Endpoint.Common.Services.Security; + +public class JwtTokenService : IAccessToken +{ + public required string Issuer { private get; init; } + public required string Audience { private get; init; } + public TimeSpan Lifetime { private get; init; } + + public ReadOnlyMemory EncryptionKey { get; init; } + public ReadOnlyMemory SigningKey { private get; init; } + + public (string Token, DateTime ExpireIn) GenerateToken(string userId) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var signingKey = new SymmetricSecurityKey(SigningKey.ToArray()); + var encryptionKey = new SymmetricSecurityKey(EncryptionKey.ToArray()); + var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha512); + + var expires = DateTime.UtcNow.Add(Lifetime); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Issuer = Issuer, + Audience = Audience, + Expires = expires, + SigningCredentials = signingCredentials, + Subject = new ClaimsIdentity( + [ + new Claim(ClaimTypes.Name, userId), + // todo: get role by userId + new Claim(ClaimTypes.Role, "") + ]), + EncryptingCredentials = new EncryptingCredentials(encryptionKey, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512) + }; + + var token = tokenHandler.CreateToken(tokenDescriptor); + + return (tokenHandler.WriteToken(token), expires); + } + + public DateTimeOffset GetExpireDateTime(string token) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var signingKey = new SymmetricSecurityKey(SigningKey.ToArray()); + var encryptionKey = new SymmetricSecurityKey(EncryptionKey.ToArray()); + + var tokenValidationParameters = new TokenValidationParameters + { + ValidIssuer = Issuer, + ValidAudience = Audience, + IssuerSigningKey = signingKey, + TokenDecryptionKey = encryptionKey, + ValidateIssuer = true, + ValidateAudience = true, + ValidateIssuerSigningKey = true, + ValidateLifetime = false + }; + + try + { + var claimsPrincipal = tokenHandler.ValidateToken(token, tokenValidationParameters, out _); + + var expClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "exp"); + + if (expClaim != null && long.TryParse(expClaim.Value, out var expUnix)) + return DateTimeOffset.FromUnixTimeSeconds(expUnix); + } + catch (SecurityTokenException) + { + return DateTimeOffset.MinValue; + } + + return DateTimeOffset.MinValue; + } +} \ No newline at end of file -- 2.43.0 From 38ec80a566fc55aef362a988d9335b61524ba9bf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:30:01 +0300 Subject: [PATCH 168/474] feat: add configuration for jwt token --- Endpoint/Program.cs | 57 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 89d98ab..adeeb99 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -6,20 +7,24 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Common.Services.Security; using Mirea.Api.Endpoint.Configuration; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Validators; using Mirea.Api.Endpoint.Configuration.Swagger; using Mirea.Api.Endpoint.Middleware; +using Mirea.Api.Security.Common.Interfaces; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.Collections; using System.IO; using System.Linq; +using System.Text; namespace Mirea.Api.Endpoint; @@ -40,6 +45,58 @@ public class Program return result.Build(); } + private static IServiceCollection ConfigureJwtToken(IServiceCollection services, IConfiguration configuration) + { + var lifeTimeJwt = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_JWT"]!)); + + var jwtDecrypt = Encoding.UTF8.GetBytes(configuration["SECURITY_ENCRYPTION_TOKEN"] ?? string.Empty); + + if (jwtDecrypt.Length != 32) + throw new InvalidOperationException("The secret token \"SECURITY_ENCRYPTION_TOKEN\" cannot be less than 32 characters long. Now the size is equal is " + jwtDecrypt.Length); + + var jwtKey = Encoding.UTF8.GetBytes(configuration["SECURITY_SIGNING_TOKEN"] ?? string.Empty); + + if (jwtKey.Length != 64) + throw new InvalidOperationException("The signature token \"SECURITY_SIGNING_TOKEN\" cannot be less than 64 characters. Now the size is " + jwtKey.Length); + + var jwtIssuer = configuration["SECURITY_JWT_ISSUER"]; + var jwtAudience = configuration["SECURITY_JWT_AUDIENCE"]; + + if (string.IsNullOrEmpty(jwtAudience) || string.IsNullOrEmpty(jwtIssuer)) + throw new InvalidOperationException("The \"SECURITY_JWT_ISSUER\" and \"SECURITY_JWT_AUDIENCE\" are not specified"); + + services.AddSingleton(_ => new JwtTokenService + { + Audience = jwtAudience, + Issuer = jwtIssuer, + Lifetime = lifeTimeJwt, + EncryptionKey = jwtDecrypt, + SigningKey = jwtKey + }); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtIssuer, + + ValidateAudience = true, + ValidAudience = jwtAudience, + + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(jwtKey), + TokenDecryptionKey = new SymmetricSecurityKey(jwtDecrypt) + }; + }); + + return services; + } public static void Main(string[] args) { Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); -- 2.43.0 From d2ef99d0b29cbfc3036b4029b52ffafdb4e82011 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 06:42:14 +0300 Subject: [PATCH 169/474] feat: add security configure --- Endpoint/Program.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index adeeb99..840504f 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -97,6 +97,14 @@ public class Program return services; } + + private static IServiceCollection ConfigureSecurity(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + + return services; + } public static void Main(string[] args) { Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); -- 2.43.0 From 081c814036f86cf8714d93159ca18eec3054e225 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 07:38:32 +0300 Subject: [PATCH 170/474] feat: return the schedule-related settings --- Endpoint/Controllers/V1/ScheduleController.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index a0d5b7b..904e3d2 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -1,11 +1,16 @@ using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using Mirea.Api.DataAccess.Application.Cqrs.Schedule.Queries.GetScheduleList; +using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Responses; using Mirea.Api.Dto.Responses.Schedule; using Mirea.Api.Endpoint.Common.Attributes; +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.General; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -13,8 +18,14 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] -public class ScheduleController(IMediator mediator) : BaseController +public class ScheduleController(IMediator mediator, IOptionsSnapshot config) : BaseController { + [HttpGet("StartTerm")] + public ActionResult GetStartTerm() => config.Value.ScheduleSettings!.StartTerm; + + [HttpGet("PairPeriod")] + public ActionResult> GetPairPeriod() => config.Value.ScheduleSettings!.PairPeriod.ConvertToDto(); + /// /// Retrieves schedules based on various filters. /// -- 2.43.0 From bf3a9d4b36ef4a8cb1a87a83c992d50510b6475a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 07:49:42 +0300 Subject: [PATCH 171/474] refactor: move database-related projects to separate folder --- .../Common/Mappings/AssemblyMappingProfile.cs | 28 ------------------- Application/Common/Mappings/IMapWith.cs | 9 ------ .../Application}/Application.csproj | 11 ++++---- .../Common/Behaviors/ValidationBehavior.cs | 0 .../Common/Exceptions/NotFoundException.cs | 0 .../CampusBasicInfoDto.cs | 0 .../CampusBasicInfoVm.cs | 0 .../GetCampusBasicInfoListQuery.cs | 0 .../GetCampusBasicInfoListQueryHandler.cs | 0 .../GetCampusDetails/CampusDetailsVm.cs | 0 .../GetCampusDetails/GetCampusDetailsQuery.cs | 0 .../GetCampusDetailsQueryHandler.cs | 0 .../GetDisciplineDetails/DisciplineInfoVm.cs | 0 .../GetDisciplineInfoQuery.cs | 0 .../GetDisciplineInfoQueryHandler.cs | 0 .../GetDisciplineList/DisciplineListVm.cs | 0 .../GetDisciplineList/DisciplineLookupDto.cs | 0 .../GetDisciplineListQuery.cs | 0 .../GetDisciplineListQueryHandler.cs | 0 .../GetFacultyDetails/FacultyInfoVm.cs | 0 .../GetFacultyDetails/GetFacultyInfoQuery.cs | 0 .../GetFacultyInfoQueryHandler.cs | 0 .../Queries/GetFacultyList/FacultyListVm.cs | 0 .../GetFacultyList/FacultyLookupDto.cs | 0 .../GetFacultyList/GetFacultyListQuery.cs | 0 .../GetFacultyListQueryHandler.cs | 0 .../GetGroupDetails/GetGroupInfoQuery.cs | 0 .../GetGroupInfoQueryHandler.cs | 0 .../Queries/GetGroupDetails/GroupInfoVm.cs | 0 .../Queries/GetGroupList/GetGroupListQuery.cs | 0 .../GetGroupList/GetGroupListQueryHandler.cs | 0 .../Group/Queries/GetGroupList/GroupListVm.cs | 0 .../Queries/GetGroupList/GroupLookupDto.cs | 0 .../GetLectureHallInfoQuery.cs | 0 .../GetLectureHallInfoQueryHandler.cs | 0 .../LectureHallInfoVm.cs | 0 .../GetLectureHallListQuery.cs | 0 .../GetLectureHallListQueryHandler.cs | 0 .../GetLectureHallList/LectureHallListVm.cs | 0 .../LectureHallLookupDto.cs | 0 .../GetProfessorInfoQuery.cs | 0 .../GetProfessorInfoQueryHandler.cs | 0 .../GetProfessorDetails/ProfessorInfoVm.cs | 0 .../GetProfessorList/GetProfessorListQuery.cs | 0 .../GetProfessorListQueryHandler.cs | 0 .../GetProfessorList/ProfessorListVm.cs | 0 .../GetProfessorList/ProfessorLookupDto.cs | 0 .../GetScheduleList/GetScheduleListQuery.cs | 0 .../GetScheduleListQueryHandler.cs | 0 .../Queries/GetScheduleList/ScheduleListVm.cs | 0 .../GetScheduleList/ScheduleLookupDto.cs | 0 .../Application}/DependencyInjection.cs | 0 .../Interfaces/DbContexts/IDbContextBase.cs | 0 .../DbContexts/Schedule/ICampusDbContext.cs | 0 .../Schedule/IDisciplineDbContext.cs | 0 .../DbContexts/Schedule/IFacultyDbContext.cs | 0 .../DbContexts/Schedule/IGroupDbContext.cs | 0 .../Schedule/ILectureHallDbContext.cs | 0 .../Schedule/ILessonAssociationDbContext.cs | 0 .../DbContexts/Schedule/ILessonDbContext.cs | 0 .../Schedule/IProfessorDbContext.cs | 0 .../Schedule/ISpecificWeekDbContext.cs | 0 .../Schedule/ITypeOfOccupationDbContext.cs | 0 {Domain => SqlData/Domain}/Domain.csproj | 0 {Domain => SqlData/Domain}/Schedule/Campus.cs | 0 .../Domain}/Schedule/Discipline.cs | 0 .../Domain}/Schedule/Faculty.cs | 0 {Domain => SqlData/Domain}/Schedule/Group.cs | 0 .../Domain}/Schedule/LectureHall.cs | 0 {Domain => SqlData/Domain}/Schedule/Lesson.cs | 0 .../Domain}/Schedule/LessonAssociation.cs | 0 .../Domain}/Schedule/Professor.cs | 0 .../Domain}/Schedule/SpecificWeek.cs | 0 .../Domain}/Schedule/TypeOfOccupation.cs | 0 74 files changed, 5 insertions(+), 43 deletions(-) delete mode 100644 Application/Common/Mappings/AssemblyMappingProfile.cs delete mode 100644 Application/Common/Mappings/IMapWith.cs rename {Application => SqlData/Application}/Application.csproj (53%) rename {Application => SqlData/Application}/Common/Behaviors/ValidationBehavior.cs (100%) rename {Application => SqlData/Application}/Common/Exceptions/NotFoundException.cs (100%) rename {Application => SqlData/Application}/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs (100%) rename {Application => SqlData/Application}/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs (100%) rename {Application => SqlData/Application}/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs (100%) rename {Application => SqlData/Application}/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs (100%) rename {Application => SqlData/Application}/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs (100%) rename {Application => SqlData/Application}/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs (100%) rename {Application => SqlData/Application}/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs (100%) rename {Application => SqlData/Application}/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs (100%) rename {Application => SqlData/Application}/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs (100%) rename {Application => SqlData/Application}/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs (100%) rename {Application => SqlData/Application}/DependencyInjection.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/IDbContextBase.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/ICampusDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/IDisciplineDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/IGroupDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/ILessonAssociationDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/ILessonDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs (100%) rename {Application => SqlData/Application}/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs (100%) rename {Domain => SqlData/Domain}/Domain.csproj (100%) rename {Domain => SqlData/Domain}/Schedule/Campus.cs (100%) rename {Domain => SqlData/Domain}/Schedule/Discipline.cs (100%) rename {Domain => SqlData/Domain}/Schedule/Faculty.cs (100%) rename {Domain => SqlData/Domain}/Schedule/Group.cs (100%) rename {Domain => SqlData/Domain}/Schedule/LectureHall.cs (100%) rename {Domain => SqlData/Domain}/Schedule/Lesson.cs (100%) rename {Domain => SqlData/Domain}/Schedule/LessonAssociation.cs (100%) rename {Domain => SqlData/Domain}/Schedule/Professor.cs (100%) rename {Domain => SqlData/Domain}/Schedule/SpecificWeek.cs (100%) rename {Domain => SqlData/Domain}/Schedule/TypeOfOccupation.cs (100%) diff --git a/Application/Common/Mappings/AssemblyMappingProfile.cs b/Application/Common/Mappings/AssemblyMappingProfile.cs deleted file mode 100644 index 68a9e8e..0000000 --- a/Application/Common/Mappings/AssemblyMappingProfile.cs +++ /dev/null @@ -1,28 +0,0 @@ -using AutoMapper; -using System; -using System.Linq; -using System.Reflection; - -namespace Mirea.Api.DataAccess.Application.Common.Mappings; - -public class AssemblyMappingProfile : Profile -{ - public AssemblyMappingProfile(Assembly assembly) => - ApplyMappingsFromAssembly(assembly); - - private void ApplyMappingsFromAssembly(Assembly assembly) - { - var types = assembly.GetExportedTypes() - .Where(type => type.GetInterfaces() - .Any(i => i.IsGenericType && - i.GetGenericTypeDefinition() == typeof(IMapWith<>))) - .ToList(); - - foreach (var type in types) - { - var instance = Activator.CreateInstance(type); - var methodInfo = type.GetMethod("Mapping"); - methodInfo?.Invoke(instance, new[] { this }); - } - } -} \ No newline at end of file diff --git a/Application/Common/Mappings/IMapWith.cs b/Application/Common/Mappings/IMapWith.cs deleted file mode 100644 index 390e4e0..0000000 --- a/Application/Common/Mappings/IMapWith.cs +++ /dev/null @@ -1,9 +0,0 @@ -using AutoMapper; - -namespace Mirea.Api.DataAccess.Application.Common.Mappings; - -public interface IMapWith -{ - void Mapping(Profile profile) => - profile.CreateMap(typeof(T), GetType()); -} \ No newline at end of file diff --git a/Application/Application.csproj b/SqlData/Application/Application.csproj similarity index 53% rename from Application/Application.csproj rename to SqlData/Application/Application.csproj index 2003517..51f04ad 100644 --- a/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -13,15 +13,14 @@ - - - - - + + + + - + \ No newline at end of file diff --git a/Application/Common/Behaviors/ValidationBehavior.cs b/SqlData/Application/Common/Behaviors/ValidationBehavior.cs similarity index 100% rename from Application/Common/Behaviors/ValidationBehavior.cs rename to SqlData/Application/Common/Behaviors/ValidationBehavior.cs diff --git a/Application/Common/Exceptions/NotFoundException.cs b/SqlData/Application/Common/Exceptions/NotFoundException.cs similarity index 100% rename from Application/Common/Exceptions/NotFoundException.cs rename to SqlData/Application/Common/Exceptions/NotFoundException.cs diff --git a/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs b/SqlData/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs similarity index 100% rename from Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs rename to SqlData/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoDto.cs diff --git a/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs b/SqlData/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs similarity index 100% rename from Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs rename to SqlData/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/CampusBasicInfoVm.cs diff --git a/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs b/SqlData/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs similarity index 100% rename from Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs rename to SqlData/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQuery.cs diff --git a/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs b/SqlData/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs similarity index 100% rename from Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs rename to SqlData/Application/Cqrs/Campus/Queries/GetCampusBasicInfoList/GetCampusBasicInfoListQueryHandler.cs diff --git a/Application/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs b/SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs similarity index 100% rename from Application/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs rename to SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/CampusDetailsVm.cs diff --git a/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs b/SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs similarity index 100% rename from Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs rename to SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQuery.cs diff --git a/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs b/SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs similarity index 100% rename from Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs rename to SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs similarity index 100% rename from Application/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs rename to SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/DisciplineInfoVm.cs diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs similarity index 100% rename from Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs rename to SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQuery.cs diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs similarity index 100% rename from Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs rename to SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs similarity index 100% rename from Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs rename to SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs similarity index 100% rename from Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs rename to SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineLookupDto.cs diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs similarity index 100% rename from Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs rename to SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQuery.cs diff --git a/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs similarity index 100% rename from Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs rename to SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/GetDisciplineListQueryHandler.cs diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs similarity index 100% rename from Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs rename to SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs similarity index 100% rename from Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs rename to SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs similarity index 100% rename from Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs rename to SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs similarity index 100% rename from Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs rename to SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs similarity index 100% rename from Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs rename to SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs similarity index 100% rename from Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs rename to SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQuery.cs diff --git a/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs similarity index 100% rename from Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs rename to SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs diff --git a/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs b/SqlData/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs similarity index 100% rename from Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs rename to SqlData/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQuery.cs diff --git a/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs b/SqlData/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs similarity index 100% rename from Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs rename to SqlData/Application/Cqrs/Group/Queries/GetGroupDetails/GetGroupInfoQueryHandler.cs diff --git a/Application/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs b/SqlData/Application/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs similarity index 100% rename from Application/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs rename to SqlData/Application/Cqrs/Group/Queries/GetGroupDetails/GroupInfoVm.cs diff --git a/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs b/SqlData/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs similarity index 100% rename from Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs rename to SqlData/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQuery.cs diff --git a/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs b/SqlData/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs similarity index 100% rename from Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs rename to SqlData/Application/Cqrs/Group/Queries/GetGroupList/GetGroupListQueryHandler.cs diff --git a/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs b/SqlData/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs similarity index 100% rename from Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs rename to SqlData/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs diff --git a/Application/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs b/SqlData/Application/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs similarity index 100% rename from Application/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs rename to SqlData/Application/Cqrs/Group/Queries/GetGroupList/GroupLookupDto.cs diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs similarity index 100% rename from Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs rename to SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQuery.cs diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs similarity index 100% rename from Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs rename to SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/GetLectureHallInfoQueryHandler.cs diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs similarity index 100% rename from Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs rename to SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallDetails/LectureHallInfoVm.cs diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs similarity index 100% rename from Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs rename to SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQuery.cs diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs similarity index 100% rename from Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs rename to SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/GetLectureHallListQueryHandler.cs diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs similarity index 100% rename from Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs rename to SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs diff --git a/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs similarity index 100% rename from Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs rename to SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallLookupDto.cs diff --git a/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs similarity index 100% rename from Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs rename to SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs diff --git a/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs similarity index 100% rename from Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs rename to SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs diff --git a/Application/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs similarity index 100% rename from Application/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs rename to SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/ProfessorInfoVm.cs diff --git a/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs similarity index 100% rename from Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs rename to SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQuery.cs diff --git a/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs similarity index 100% rename from Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs rename to SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/GetProfessorListQueryHandler.cs diff --git a/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs similarity index 100% rename from Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs rename to SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs diff --git a/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs similarity index 100% rename from Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs rename to SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorLookupDto.cs diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs similarity index 100% rename from Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs rename to SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs similarity index 100% rename from Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs rename to SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs similarity index 100% rename from Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs rename to SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs diff --git a/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs similarity index 100% rename from Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs rename to SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleLookupDto.cs diff --git a/Application/DependencyInjection.cs b/SqlData/Application/DependencyInjection.cs similarity index 100% rename from Application/DependencyInjection.cs rename to SqlData/Application/DependencyInjection.cs diff --git a/Application/Interfaces/DbContexts/IDbContextBase.cs b/SqlData/Application/Interfaces/DbContexts/IDbContextBase.cs similarity index 100% rename from Application/Interfaces/DbContexts/IDbContextBase.cs rename to SqlData/Application/Interfaces/DbContexts/IDbContextBase.cs diff --git a/Application/Interfaces/DbContexts/Schedule/ICampusDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/ICampusDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/ICampusDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/ICampusDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/IDisciplineDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/IDisciplineDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/IDisciplineDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/IDisciplineDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/IFacultyDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/IGroupDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/IGroupDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/IGroupDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/IGroupDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/ILectureHallDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/ILessonAssociationDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/ILessonAssociationDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/ILessonAssociationDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/ILessonAssociationDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/ILessonDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/ILessonDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/ILessonDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/ILessonDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/IProfessorDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/ISpecificWeekDbContext.cs diff --git a/Application/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs b/SqlData/Application/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs similarity index 100% rename from Application/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs rename to SqlData/Application/Interfaces/DbContexts/Schedule/ITypeOfOccupationDbContext.cs diff --git a/Domain/Domain.csproj b/SqlData/Domain/Domain.csproj similarity index 100% rename from Domain/Domain.csproj rename to SqlData/Domain/Domain.csproj diff --git a/Domain/Schedule/Campus.cs b/SqlData/Domain/Schedule/Campus.cs similarity index 100% rename from Domain/Schedule/Campus.cs rename to SqlData/Domain/Schedule/Campus.cs diff --git a/Domain/Schedule/Discipline.cs b/SqlData/Domain/Schedule/Discipline.cs similarity index 100% rename from Domain/Schedule/Discipline.cs rename to SqlData/Domain/Schedule/Discipline.cs diff --git a/Domain/Schedule/Faculty.cs b/SqlData/Domain/Schedule/Faculty.cs similarity index 100% rename from Domain/Schedule/Faculty.cs rename to SqlData/Domain/Schedule/Faculty.cs diff --git a/Domain/Schedule/Group.cs b/SqlData/Domain/Schedule/Group.cs similarity index 100% rename from Domain/Schedule/Group.cs rename to SqlData/Domain/Schedule/Group.cs diff --git a/Domain/Schedule/LectureHall.cs b/SqlData/Domain/Schedule/LectureHall.cs similarity index 100% rename from Domain/Schedule/LectureHall.cs rename to SqlData/Domain/Schedule/LectureHall.cs diff --git a/Domain/Schedule/Lesson.cs b/SqlData/Domain/Schedule/Lesson.cs similarity index 100% rename from Domain/Schedule/Lesson.cs rename to SqlData/Domain/Schedule/Lesson.cs diff --git a/Domain/Schedule/LessonAssociation.cs b/SqlData/Domain/Schedule/LessonAssociation.cs similarity index 100% rename from Domain/Schedule/LessonAssociation.cs rename to SqlData/Domain/Schedule/LessonAssociation.cs diff --git a/Domain/Schedule/Professor.cs b/SqlData/Domain/Schedule/Professor.cs similarity index 100% rename from Domain/Schedule/Professor.cs rename to SqlData/Domain/Schedule/Professor.cs diff --git a/Domain/Schedule/SpecificWeek.cs b/SqlData/Domain/Schedule/SpecificWeek.cs similarity index 100% rename from Domain/Schedule/SpecificWeek.cs rename to SqlData/Domain/Schedule/SpecificWeek.cs diff --git a/Domain/Schedule/TypeOfOccupation.cs b/SqlData/Domain/Schedule/TypeOfOccupation.cs similarity index 100% rename from Domain/Schedule/TypeOfOccupation.cs rename to SqlData/Domain/Schedule/TypeOfOccupation.cs -- 2.43.0 From 164d575d9872460da9d47e9bfb824fa28e3ddc1e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 29 May 2024 08:08:58 +0300 Subject: [PATCH 172/474] refactor: move database-related projects to a separate folder --- .../Contexts/Schedule/CampusDbContext.cs | 0 .../Contexts/Schedule/DisciplineDbContext.cs | 0 .../Contexts/Schedule/FacultyDbContext.cs | 0 .../Persistence}/Contexts/Schedule/GroupDbContext.cs | 0 .../Contexts/Schedule/LectureHallDbContext.cs | 0 .../Contexts/Schedule/LessonAssociationDbContext.cs | 0 .../Contexts/Schedule/LessonDbContext.cs | 0 .../Contexts/Schedule/ProfessorDbContext.cs | 0 .../Contexts/Schedule/SpecificWeekDbContext.cs | 0 .../Contexts/Schedule/TypeOfOccupationDbContext.cs | 0 .../Persistence}/DbInitializer.cs | 0 .../Persistence}/DependencyInjection.cs | 0 .../Schedule/CampusConfiguration.cs | 0 .../Schedule/DisciplineConfiguration.cs | 0 .../Schedule/FacultyConfiguration.cs | 0 .../Schedule/GroupConfiguration.cs | 0 .../Schedule/LectureHallConfiguration.cs | 0 .../Schedule/LessonAssociationConfiguration.cs | 0 .../Schedule/LessonConfiguration.cs | 0 .../Schedule/ProfessorConfiguration.cs | 0 .../Schedule/SpecificWeekConfiguration.cs | 0 .../Schedule/TypeOfOccupationConfiguration.cs | 0 .../Persistence}/Persistence.csproj | 12 ++++-------- .../Persistence}/UberDbContext.cs | 0 24 files changed, 4 insertions(+), 8 deletions(-) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/CampusDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/DisciplineDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/FacultyDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/GroupDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/LectureHallDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/LessonAssociationDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/LessonDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/ProfessorDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/SpecificWeekDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/Contexts/Schedule/TypeOfOccupationDbContext.cs (100%) rename {Persistence => SqlData/Persistence}/DbInitializer.cs (100%) rename {Persistence => SqlData/Persistence}/DependencyInjection.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/CampusConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/GroupConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/LessonConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs (100%) rename {Persistence => SqlData/Persistence}/Persistence.csproj (85%) rename {Persistence => SqlData/Persistence}/UberDbContext.cs (100%) diff --git a/Persistence/Contexts/Schedule/CampusDbContext.cs b/SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/CampusDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs diff --git a/Persistence/Contexts/Schedule/DisciplineDbContext.cs b/SqlData/Persistence/Contexts/Schedule/DisciplineDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/DisciplineDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/DisciplineDbContext.cs diff --git a/Persistence/Contexts/Schedule/FacultyDbContext.cs b/SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/FacultyDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs diff --git a/Persistence/Contexts/Schedule/GroupDbContext.cs b/SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/GroupDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs diff --git a/Persistence/Contexts/Schedule/LectureHallDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LectureHallDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/LectureHallDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/LectureHallDbContext.cs diff --git a/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/LessonAssociationDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs diff --git a/Persistence/Contexts/Schedule/LessonDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/LessonDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs diff --git a/Persistence/Contexts/Schedule/ProfessorDbContext.cs b/SqlData/Persistence/Contexts/Schedule/ProfessorDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/ProfessorDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/ProfessorDbContext.cs diff --git a/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs b/SqlData/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/SpecificWeekDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs diff --git a/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs b/SqlData/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs similarity index 100% rename from Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs diff --git a/Persistence/DbInitializer.cs b/SqlData/Persistence/DbInitializer.cs similarity index 100% rename from Persistence/DbInitializer.cs rename to SqlData/Persistence/DbInitializer.cs diff --git a/Persistence/DependencyInjection.cs b/SqlData/Persistence/DependencyInjection.cs similarity index 100% rename from Persistence/DependencyInjection.cs rename to SqlData/Persistence/DependencyInjection.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs diff --git a/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs similarity index 100% rename from Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs diff --git a/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj similarity index 85% rename from Persistence/Persistence.csproj rename to SqlData/Persistence/Persistence.csproj index 37f0a7f..d191edd 100644 --- a/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -13,11 +13,11 @@ - - + + - - + + @@ -25,8 +25,4 @@ - - - - \ No newline at end of file diff --git a/Persistence/UberDbContext.cs b/SqlData/Persistence/UberDbContext.cs similarity index 100% rename from Persistence/UberDbContext.cs rename to SqlData/Persistence/UberDbContext.cs -- 2.43.0 From 0a9c98cbf9321dc5ef4cb6f84450d797aa5a86ed Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:07:20 +0300 Subject: [PATCH 173/474] refactor: change GetService to GetRequiredService --- SqlData/Persistence/DependencyInjection.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/SqlData/Persistence/DependencyInjection.cs b/SqlData/Persistence/DependencyInjection.cs index f98052c..2ebae49 100644 --- a/SqlData/Persistence/DependencyInjection.cs +++ b/SqlData/Persistence/DependencyInjection.cs @@ -22,16 +22,16 @@ public static class DependencyInjection services.AddDbContext(DbConfig); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); - services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); + services.AddScoped(provider => provider.GetRequiredService()); return services; -- 2.43.0 From 78a242f4c3fbd2da56c42ff649b68d2bd5aec238 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:07:59 +0300 Subject: [PATCH 174/474] feat: add providers database for presistence --- SqlData/Persistence/Common/DatabaseProvider.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 SqlData/Persistence/Common/DatabaseProvider.cs diff --git a/SqlData/Persistence/Common/DatabaseProvider.cs b/SqlData/Persistence/Common/DatabaseProvider.cs new file mode 100644 index 0000000..c3d15cf --- /dev/null +++ b/SqlData/Persistence/Common/DatabaseProvider.cs @@ -0,0 +1,8 @@ +namespace Mirea.Api.DataAccess.Persistence.Common; + +public enum DatabaseProvider +{ + Mysql, + Sqlite, + Postgresql +} \ No newline at end of file -- 2.43.0 From 53a0439edb6b4653ee3f143fdce5473beb47bed3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:10:13 +0300 Subject: [PATCH 175/474] feat: add wrap DbContext for OnModelCreating --- SqlData/Persistence/Common/BaseDbContext.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 SqlData/Persistence/Common/BaseDbContext.cs diff --git a/SqlData/Persistence/Common/BaseDbContext.cs b/SqlData/Persistence/Common/BaseDbContext.cs new file mode 100644 index 0000000..c3dd911 --- /dev/null +++ b/SqlData/Persistence/Common/BaseDbContext.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; + +namespace Mirea.Api.DataAccess.Persistence.Common; + +public abstract class BaseDbContext(DbContextOptions options) : DbContext(options) where TContext : DbContext +{ + public void ApplyConfigurations(ModelBuilder modelBuilder) => + base.OnModelCreating(modelBuilder); +} \ No newline at end of file -- 2.43.0 From f79c7c7db9e6693aa78858a7ad89c1a72c9cb273 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:11:18 +0300 Subject: [PATCH 176/474] refactor: use wrap DbContext --- SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs | 7 ++++--- .../Persistence/Contexts/Schedule/DisciplineDbContext.cs | 7 ++++--- SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs | 7 ++++--- SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs | 7 ++++--- .../Persistence/Contexts/Schedule/LectureHallDbContext.cs | 7 ++++--- .../Contexts/Schedule/LessonAssociationDbContext.cs | 7 ++++--- SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs | 7 ++++--- .../Persistence/Contexts/Schedule/ProfessorDbContext.cs | 7 ++++--- .../Persistence/Contexts/Schedule/SpecificWeekDbContext.cs | 7 ++++--- .../Contexts/Schedule/TypeOfOccupationDbContext.cs | 7 ++++--- 10 files changed, 40 insertions(+), 30 deletions(-) diff --git a/SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs b/SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs index 30a9d56..d93e3d9 100644 --- a/SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class CampusDbContext(DbContextOptions options) : DbContext(options), ICampusDbContext +public class CampusDbContext(DbContextOptions options) : BaseDbContext(options), ICampusDbContext { public DbSet Campuses { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new CampusConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/DisciplineDbContext.cs b/SqlData/Persistence/Contexts/Schedule/DisciplineDbContext.cs index d0a5612..d9ec9e1 100644 --- a/SqlData/Persistence/Contexts/Schedule/DisciplineDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/DisciplineDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class DisciplineDbContext(DbContextOptions options) : DbContext(options), IDisciplineDbContext +public sealed class DisciplineDbContext(DbContextOptions options) : BaseDbContext(options), IDisciplineDbContext { public DbSet Disciplines { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new DisciplineConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs b/SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs index ab6a45c..98323a6 100644 --- a/SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class FacultyDbContext(DbContextOptions options) : DbContext(options), IFacultyDbContext +public sealed class FacultyDbContext(DbContextOptions options) : BaseDbContext(options), IFacultyDbContext { public DbSet Faculties { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new FacultyConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs b/SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs index 5d61c70..e6bcc5e 100644 --- a/SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class GroupDbContext(DbContextOptions options) : DbContext(options), IGroupDbContext +public sealed class GroupDbContext(DbContextOptions options) : BaseDbContext(options), IGroupDbContext { public DbSet Groups { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new GroupConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/LectureHallDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LectureHallDbContext.cs index 67841a1..4253309 100644 --- a/SqlData/Persistence/Contexts/Schedule/LectureHallDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/LectureHallDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class LectureHallDbContext(DbContextOptions options) : DbContext(options), ILectureHallDbContext +public sealed class LectureHallDbContext(DbContextOptions options) : BaseDbContext(options), ILectureHallDbContext { public DbSet LectureHalls { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new LectureHallConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs index 773bc8a..ae6be39 100644 --- a/SqlData/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class LessonAssociationDbContext(DbContextOptions options) : DbContext(options), ILessonAssociationDbContext +public sealed class LessonAssociationDbContext(DbContextOptions options) : BaseDbContext(options), ILessonAssociationDbContext { public DbSet LessonAssociations { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new LessonAssociationConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs index d2dfcae..e727041 100644 --- a/SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class LessonDbContext(DbContextOptions options) : DbContext(options), ILessonDbContext +public sealed class LessonDbContext(DbContextOptions options) : BaseDbContext(options), ILessonDbContext { public DbSet Lessons { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new LessonConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/ProfessorDbContext.cs b/SqlData/Persistence/Contexts/Schedule/ProfessorDbContext.cs index fcd51c6..75924ff 100644 --- a/SqlData/Persistence/Contexts/Schedule/ProfessorDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/ProfessorDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class ProfessorDbContext(DbContextOptions options) : DbContext(options), IProfessorDbContext +public sealed class ProfessorDbContext(DbContextOptions options) : BaseDbContext(options), IProfessorDbContext { public DbSet Professors { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new ProfessorConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs b/SqlData/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs index b3601bd..20bbab7 100644 --- a/SqlData/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class SpecificWeekDbContext(DbContextOptions options) : DbContext(options), ISpecificWeekDbContext +public sealed class SpecificWeekDbContext(DbContextOptions options) : BaseDbContext(options), ISpecificWeekDbContext { public DbSet SpecificWeeks { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new SpecificWeekConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/SqlData/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs b/SqlData/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs index b47ac61..ae49d35 100644 --- a/SqlData/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs +++ b/SqlData/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs @@ -1,17 +1,18 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule; -public class TypeOfOccupationDbContext(DbContextOptions options) : DbContext(options), ITypeOfOccupationDbContext +public sealed class TypeOfOccupationDbContext(DbContextOptions options) : BaseDbContext(options), ITypeOfOccupationDbContext { public DbSet TypeOfOccupations { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new TypeOfOccupationConfiguration()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file -- 2.43.0 From 43b6ab79343aec823eb69622010f7c0a04929416 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:12:04 +0300 Subject: [PATCH 177/474] feat: add sealed class for Mark configuration namespace --- SqlData/Persistence/EntityTypeConfigurations/Mark.cs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mark.cs diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mark.cs b/SqlData/Persistence/EntityTypeConfigurations/Mark.cs new file mode 100644 index 0000000..734dd03 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mark.cs @@ -0,0 +1,3 @@ +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations; + +public sealed class Mark; \ No newline at end of file -- 2.43.0 From 31c36443e17335461f951f6faf1d157c9529027f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:12:36 +0300 Subject: [PATCH 178/474] refactor: use wrap DbContext for UberDbContext --- SqlData/Persistence/UberDbContext.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/SqlData/Persistence/UberDbContext.cs b/SqlData/Persistence/UberDbContext.cs index ccd1877..3777ef2 100644 --- a/SqlData/Persistence/UberDbContext.cs +++ b/SqlData/Persistence/UberDbContext.cs @@ -1,10 +1,11 @@ using Microsoft.EntityFrameworkCore; using Mirea.Api.DataAccess.Domain.Schedule; -using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence; -public class UberDbContext(DbContextOptions options) : DbContext(options) +public class UberDbContext(DbContextOptions options) : BaseDbContext(options) { public DbSet Campuses { get; set; } = null!; public DbSet Disciplines { get; set; } = null!; @@ -19,17 +20,7 @@ public class UberDbContext(DbContextOptions options) : DbContext( protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new CampusConfiguration()); - modelBuilder.ApplyConfiguration(new DisciplineConfiguration()); - modelBuilder.ApplyConfiguration(new FacultyConfiguration()); - modelBuilder.ApplyConfiguration(new GroupConfiguration()); - modelBuilder.ApplyConfiguration(new LectureHallConfiguration()); - modelBuilder.ApplyConfiguration(new LessonConfiguration()); - modelBuilder.ApplyConfiguration(new ProfessorConfiguration()); - modelBuilder.ApplyConfiguration(new LessonAssociationConfiguration()); - modelBuilder.ApplyConfiguration(new TypeOfOccupationConfiguration()); - modelBuilder.ApplyConfiguration(new SpecificWeekConfiguration()); - + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } } \ No newline at end of file -- 2.43.0 From 1bdf40f31fc74dc1b9835a0077c7eae34c67afd0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:13:06 +0300 Subject: [PATCH 179/474] build: move projects --- Backend.sln | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/Backend.sln b/Backend.sln index 5790acf..f24d8d8 100644 --- a/Backend.sln +++ b/Backend.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "Domain\Domain.csproj", "{C27FB5CD-6A70-4FB2-847A-847B34806902}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Endpoint", "Endpoint\Endpoint.csproj", "{F3A1D12E-F5B2-4339-9966-DBF869E78357}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", "Elements of the solution", "{3E087889-A4A0-4A55-A07D-7D149A5BC928}" @@ -19,13 +17,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", .gitea\workflows\test.yaml = .gitea\workflows\test.yaml EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\Persistence.csproj", "{4C1E558F-633F-438E-AC3A-61CDDED917C5}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiDto", "ApiDto\ApiDto.csproj", "{0335FA36-E137-453F-853B-916674C168FE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Security", "Security\Security.csproj", "{47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Security", "Security\Security.csproj", "{47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SqlData", "SqlData", "{7E7A63CD-547B-4FB4-A383-EB75298020A1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "SqlData\Domain\Domain.csproj", "{3BFD6180-7CA7-4E85-A379-225B872439A1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "SqlData\Application\Application.csproj", "{0B1F3656-E5B3-440C-961F-A7D004FBE9A8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "SqlData\Persistence\Persistence.csproj", "{48C9998C-ECE2-407F-835F-1A7255A5C99E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,22 +35,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C27FB5CD-6A70-4FB2-847A-847B34806902}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C27FB5CD-6A70-4FB2-847A-847B34806902}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C27FB5CD-6A70-4FB2-847A-847B34806902}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C27FB5CD-6A70-4FB2-847A-847B34806902}.Release|Any CPU.Build.0 = Release|Any CPU {F3A1D12E-F5B2-4339-9966-DBF869E78357}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F3A1D12E-F5B2-4339-9966-DBF869E78357}.Debug|Any CPU.Build.0 = Debug|Any CPU {F3A1D12E-F5B2-4339-9966-DBF869E78357}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3A1D12E-F5B2-4339-9966-DBF869E78357}.Release|Any CPU.Build.0 = Release|Any CPU - {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Release|Any CPU.Build.0 = Release|Any CPU - {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C1E558F-633F-438E-AC3A-61CDDED917C5}.Release|Any CPU.Build.0 = Release|Any CPU {0335FA36-E137-453F-853B-916674C168FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0335FA36-E137-453F-853B-916674C168FE}.Debug|Any CPU.Build.0 = Debug|Any CPU {0335FA36-E137-453F-853B-916674C168FE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -57,10 +47,27 @@ Global {47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Release|Any CPU.Build.0 = Release|Any CPU + {3BFD6180-7CA7-4E85-A379-225B872439A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BFD6180-7CA7-4E85-A379-225B872439A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BFD6180-7CA7-4E85-A379-225B872439A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BFD6180-7CA7-4E85-A379-225B872439A1}.Release|Any CPU.Build.0 = Release|Any CPU + {0B1F3656-E5B3-440C-961F-A7D004FBE9A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B1F3656-E5B3-440C-961F-A7D004FBE9A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B1F3656-E5B3-440C-961F-A7D004FBE9A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B1F3656-E5B3-440C-961F-A7D004FBE9A8}.Release|Any CPU.Build.0 = Release|Any CPU + {48C9998C-ECE2-407F-835F-1A7255A5C99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48C9998C-ECE2-407F-835F-1A7255A5C99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48C9998C-ECE2-407F-835F-1A7255A5C99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48C9998C-ECE2-407F-835F-1A7255A5C99E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3BFD6180-7CA7-4E85-A379-225B872439A1} = {7E7A63CD-547B-4FB4-A383-EB75298020A1} + {0B1F3656-E5B3-440C-961F-A7D004FBE9A8} = {7E7A63CD-547B-4FB4-A383-EB75298020A1} + {48C9998C-ECE2-407F-835F-1A7255A5C99E} = {7E7A63CD-547B-4FB4-A383-EB75298020A1} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E80A1224-87F5-4FEB-82AE-89006BE98B12} EndGlobalSection -- 2.43.0 From 4c93ed282d1429f6f4486a5d9b30cf8e18d87671 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:14:15 +0300 Subject: [PATCH 180/474] refactor: move default configuration to sqlite --- .../{ => Sqlite}/Schedule/CampusConfiguration.cs | 6 +++--- .../{ => Sqlite}/Schedule/DisciplineConfiguration.cs | 2 +- .../{ => Sqlite}/Schedule/FacultyConfiguration.cs | 2 +- .../{ => Sqlite}/Schedule/GroupConfiguration.cs | 2 +- .../{ => Sqlite}/Schedule/LectureHallConfiguration.cs | 2 +- .../{ => Sqlite}/Schedule/LessonAssociationConfiguration.cs | 2 +- .../{ => Sqlite}/Schedule/LessonConfiguration.cs | 2 +- .../{ => Sqlite}/Schedule/ProfessorConfiguration.cs | 2 +- .../{ => Sqlite}/Schedule/SpecificWeekConfiguration.cs | 2 +- .../{ => Sqlite}/Schedule/TypeOfOccupationConfiguration.cs | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/CampusConfiguration.cs (83%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/DisciplineConfiguration.cs (96%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/FacultyConfiguration.cs (97%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/GroupConfiguration.cs (97%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/LectureHallConfiguration.cs (97%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/LessonAssociationConfiguration.cs (98%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/LessonConfiguration.cs (98%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/ProfessorConfiguration.cs (97%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/SpecificWeekConfiguration.cs (97%) rename SqlData/Persistence/EntityTypeConfigurations/{ => Sqlite}/Schedule/TypeOfOccupationConfiguration.cs (96%) diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs similarity index 83% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs index 410d6c2..99f9e83 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs @@ -2,13 +2,13 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; -public class CampusConfiguration : IEntityTypeConfiguration +public sealed class CampusConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { - builder.ToTable(nameof(Campus)); + builder.ToTable("MyCampusName"); builder.HasKey(c => c.Id); builder.HasIndex(c => c.Id).IsUnique(); builder.Property(c => c.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/DisciplineConfiguration.cs similarity index 96% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/DisciplineConfiguration.cs index f92ac02..94356f8 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/DisciplineConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class DisciplineConfiguration : IEntityTypeConfiguration { diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs similarity index 97% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs index d61fce5..c7cde69 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class FacultyConfiguration : IEntityTypeConfiguration { diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/GroupConfiguration.cs similarity index 97% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/GroupConfiguration.cs index 5938bb3..e4b49d0 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/GroupConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class GroupConfiguration : IEntityTypeConfiguration { diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LectureHallConfiguration.cs similarity index 97% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LectureHallConfiguration.cs index 2d10805..1896d55 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LectureHallConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class LectureHallConfiguration : IEntityTypeConfiguration { diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonAssociationConfiguration.cs similarity index 98% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonAssociationConfiguration.cs index c9ecb8c..65fb513 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonAssociationConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class LessonAssociationConfiguration : IEntityTypeConfiguration { diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonConfiguration.cs similarity index 98% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonConfiguration.cs index 8d9c8cd..099c7d6 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class LessonConfiguration : IEntityTypeConfiguration { diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/ProfessorConfiguration.cs similarity index 97% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/ProfessorConfiguration.cs index 7b95ab8..37352cc 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/ProfessorConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class ProfessorConfiguration : IEntityTypeConfiguration { diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/SpecificWeekConfiguration.cs similarity index 97% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/SpecificWeekConfiguration.cs index f4fc8a3..cdab80d 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/SpecificWeekConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class SpecificWeekConfiguration : IEntityTypeConfiguration { diff --git a/SqlData/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/TypeOfOccupationConfiguration.cs similarity index 96% rename from SqlData/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/TypeOfOccupationConfiguration.cs index 5341a5a..6d7a2da 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/TypeOfOccupationConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Mirea.Api.DataAccess.Domain.Schedule; -namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule; +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Sqlite.Schedule; public class TypeOfOccupationConfiguration : IEntityTypeConfiguration { -- 2.43.0 From 348b78b84e30070f3cfac5a7e6d3cabe01d9068d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:14:46 +0300 Subject: [PATCH 181/474] feat: add resolver for getting configuration Type --- .../Common/ConfigurationResolver.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 SqlData/Persistence/Common/ConfigurationResolver.cs diff --git a/SqlData/Persistence/Common/ConfigurationResolver.cs b/SqlData/Persistence/Common/ConfigurationResolver.cs new file mode 100644 index 0000000..907b8ca --- /dev/null +++ b/SqlData/Persistence/Common/ConfigurationResolver.cs @@ -0,0 +1,23 @@ +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations; +using System; +using System.Linq; +using System.Reflection; + +namespace Mirea.Api.DataAccess.Persistence.Common; + +public static class ConfigurationResolver +{ + public static Type GetConfigurationType(DatabaseProvider provider) + where TEntity : class + { + var entityType = typeof(TEntity); + var providerNamespace = typeof(Mark).Namespace + "." + Enum.GetName(provider); + + var assembly = Assembly.GetExecutingAssembly(); + var configurationType = assembly.GetTypes() + .FirstOrDefault(t => t.Namespace != null && t.Namespace.StartsWith(providerNamespace) && t.Name == $"{entityType.Name}Configuration"); + + return configurationType ?? + throw new InvalidOperationException($"Configuration type not found for entity {entityType.Name} and provider {provider}."); + } +} \ No newline at end of file -- 2.43.0 From 7db4dc2c869e31a4b558397c7387f6032fa59466 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:15:40 +0300 Subject: [PATCH 182/474] feat: add factory for DbContext --- .../Persistence/Common/DbContextFactory.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 SqlData/Persistence/Common/DbContextFactory.cs diff --git a/SqlData/Persistence/Common/DbContextFactory.cs b/SqlData/Persistence/Common/DbContextFactory.cs new file mode 100644 index 0000000..d8ea52d --- /dev/null +++ b/SqlData/Persistence/Common/DbContextFactory.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using System; + +namespace Mirea.Api.DataAccess.Persistence.Common; + +public static class DbContextFactory +{ + public static DbContextOptionsBuilder CreateDbContext(this DbContextOptionsBuilder options, DatabaseProvider provider) + where TDbContext : BaseDbContext + where TEntity : class + { + var dbContext = (TDbContext)Activator.CreateInstance(typeof(TDbContext), (DbContextOptions)options.Options)!; + var configurationType = ConfigurationResolver.GetConfigurationType(provider); + var configurationInstance = (IEntityTypeConfiguration)Activator.CreateInstance(configurationType)!; + + var modelBuilder = new ModelBuilder(); + modelBuilder.ApplyConfiguration(configurationInstance); + dbContext.ApplyConfigurations(modelBuilder); + + return options; + } +} \ No newline at end of file -- 2.43.0 From b8728cd4907fac550f71a2649fe5dccf40d3bdd4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:17:36 +0300 Subject: [PATCH 183/474] feat: add factory DbContext for configuration by provider --- SqlData/Persistence/DependencyInjection.cs | 50 ++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/SqlData/Persistence/DependencyInjection.cs b/SqlData/Persistence/DependencyInjection.cs index 2ebae49..d200bfe 100644 --- a/SqlData/Persistence/DependencyInjection.cs +++ b/SqlData/Persistence/DependencyInjection.cs @@ -1,24 +1,41 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence.Common; using Mirea.Api.DataAccess.Persistence.Contexts.Schedule; +using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations; +using System; +using System.Linq; +using System.Reflection; namespace Mirea.Api.DataAccess.Persistence; public static class DependencyInjection { - public static IServiceCollection AddPersistence(this IServiceCollection services, string connection) + public static IServiceCollection AddPersistence(this IServiceCollection services, DatabaseProvider dbProvider, string connection) { - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); - services.AddDbContext(DbConfig); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(DbConfig); @@ -35,6 +52,15 @@ public static class DependencyInjection return services; - void DbConfig(DbContextOptionsBuilder options) => options.UseSqlite(connection); + DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder options) + { + return dbProvider switch + { + DatabaseProvider.Sqlite => options.UseSqlite(connection), + DatabaseProvider.Mysql => options.UseMySql(connection, ServerVersion.AutoDetect(connection)), + DatabaseProvider.Postgresql => options.UseNpgsql(connection), + _ => throw new ArgumentException("Unsupported database provider", Enum.GetName(dbProvider)) + }; + } } } \ No newline at end of file -- 2.43.0 From 7c79f7d8402dc82bbce99878bd8932b42f747283 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:19:58 +0300 Subject: [PATCH 184/474] feat: add wrap for generic configuration --- .../Common/ModelBuilderExtensions.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 SqlData/Persistence/Common/ModelBuilderExtensions.cs diff --git a/SqlData/Persistence/Common/ModelBuilderExtensions.cs b/SqlData/Persistence/Common/ModelBuilderExtensions.cs new file mode 100644 index 0000000..2198808 --- /dev/null +++ b/SqlData/Persistence/Common/ModelBuilderExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using System.Linq; + +namespace Mirea.Api.DataAccess.Persistence.Common; + +public static class ModelBuilderExtensions +{ + public static void ApplyConfiguration(this ModelBuilder modelBuilder, object configuration) + { + var applyGenericMethod = typeof(ModelBuilder) + .GetMethods() + .First(m => m.Name == "ApplyConfiguration" && + m.GetParameters().Any(p => p.ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>))); + + var entityType = configuration.GetType().GetInterfaces() + .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)) + .GetGenericArguments()[0]; + + var applyConcreteMethod = applyGenericMethod.MakeGenericMethod(entityType); + applyConcreteMethod.Invoke(modelBuilder, [configuration]); + } +} -- 2.43.0 From 271df127a6eb80923e49b53d5dd24d04bc1bf109 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:20:20 +0300 Subject: [PATCH 185/474] feat: add DbContext for UberDbContext --- SqlData/Persistence/DependencyInjection.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/SqlData/Persistence/DependencyInjection.cs b/SqlData/Persistence/DependencyInjection.cs index d200bfe..ad7c511 100644 --- a/SqlData/Persistence/DependencyInjection.cs +++ b/SqlData/Persistence/DependencyInjection.cs @@ -36,8 +36,28 @@ public static class DependencyInjection services.AddDbContext(options => UseDatabase(options).CreateDbContext(dbProvider)); + services.AddDbContext(options => + { + var providerNamespace = typeof(Mark).Namespace + "." + Enum.GetName(dbProvider); - services.AddDbContext(DbConfig); + var assembly = Assembly.GetExecutingAssembly(); + var configurationTypes = assembly.GetTypes() + .Where(t => + t is { IsNested: false, IsAbstract: false, Namespace: not null } && + t.Namespace.StartsWith(providerNamespace) && + t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>))); + + var modelBuilder = new ModelBuilder(); + + foreach (var configurationType in configurationTypes) + { + var configurationInstance = Activator.CreateInstance(configurationType)!; + modelBuilder.ApplyConfiguration(configurationInstance); + } + + var dbContext = (UberDbContext)Activator.CreateInstance(typeof(UberDbContext), (DbContextOptions)UseDatabase(options).Options)!; + dbContext.ApplyConfigurations(modelBuilder); + }); services.AddScoped(provider => provider.GetRequiredService()); services.AddScoped(provider => provider.GetRequiredService()); -- 2.43.0 From 62ccf942229f767cc951bfe0320ba4a98cc1fb53 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:25:21 +0300 Subject: [PATCH 186/474] feat: add converter DatabaseEnum to DatabaseProvider --- .../General/Settings/DbSettings.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Endpoint/Configuration/General/Settings/DbSettings.cs b/Endpoint/Configuration/General/Settings/DbSettings.cs index f8ae35e..2fa5ff9 100644 --- a/Endpoint/Configuration/General/Settings/DbSettings.cs +++ b/Endpoint/Configuration/General/Settings/DbSettings.cs @@ -1,4 +1,6 @@ -using Mirea.Api.Endpoint.Configuration.General.Attributes; +using System; +using Mirea.Api.DataAccess.Persistence.Common; +using Mirea.Api.Endpoint.Configuration.General.Attributes; using Mirea.Api.Endpoint.Configuration.General.Interfaces; namespace Mirea.Api.Endpoint.Configuration.General.Settings; @@ -15,8 +17,15 @@ public class DbSettings : IIsConfigured public DatabaseEnum TypeDatabase { get; set; } public required string ConnectionStringSql { get; set; } - public bool IsConfigured() - { - return !string.IsNullOrEmpty(ConnectionStringSql); - } + public DatabaseProvider DatabaseProvider => + TypeDatabase switch + { + DatabaseEnum.PostgresSql => DatabaseProvider.Postgresql, + DatabaseEnum.Mysql => DatabaseProvider.Mysql, + DatabaseEnum.Sqlite => DatabaseProvider.Sqlite, + _ => throw new ArgumentOutOfRangeException() + }; + + public bool IsConfigured() => + !string.IsNullOrEmpty(ConnectionStringSql); } \ No newline at end of file -- 2.43.0 From 8a103831ebfe77b27aa525f2c709305950466464 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:26:42 +0300 Subject: [PATCH 187/474] refactor: send provider --- Endpoint/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 840504f..3c39eed 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -115,7 +115,7 @@ public class Program builder.Services.Configure(builder.Configuration); builder.Services.AddApplication(); - builder.Services.AddPersistence(builder.Configuration.Get()?.DbSettings?.ConnectionStringSql ?? string.Empty); + builder.Services.AddPersistence(generalConfig?.DbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, generalConfig?.DbSettings?.ConnectionStringSql ?? string.Empty); builder.Services.AddControllers(); builder.Services.AddSingleton(); -- 2.43.0 From b81fe6d8c17b212b14a8094213fcbc163c9b3ba0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 20:29:11 +0300 Subject: [PATCH 188/474] fix: rename table --- .../Sqlite/Schedule/CampusConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs index 99f9e83..3a85a0a 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs @@ -8,7 +8,7 @@ public sealed class CampusConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { - builder.ToTable("MyCampusName"); + builder.ToTable(nameof(Campus)); builder.HasKey(c => c.Id); builder.HasIndex(c => c.Id).IsUnique(); builder.Property(c => c.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); -- 2.43.0 From a353b4c3f8cd711513234ed139a8af01bed0c8e9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 21:19:54 +0300 Subject: [PATCH 189/474] test: upgrade test --- .gitea/workflows/test.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml index 49484d4..9d784b5 100644 --- a/.gitea/workflows/test.yaml +++ b/.gitea/workflows/test.yaml @@ -2,17 +2,19 @@ name: .NET Test Pipeline on: pull_request: - branches: [master, 'release/*'] + push: + branches: + [master, 'release/*'] jobs: build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x -- 2.43.0 From aa1e1000fac528b0dd92f1f6dca6aab5fbde0981 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 21:45:27 +0300 Subject: [PATCH 190/474] fix: set new path to projects --- Endpoint/Endpoint.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 985a730..2aad3c8 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -31,8 +31,8 @@ - - + + -- 2.43.0 From c9c6a99fe98b16a8f8f210504561873950a5f32d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 21:45:45 +0300 Subject: [PATCH 191/474] test: trying to set up tests to test application in different databases --- .gitea/workflows/Settings.json | 51 +++++++++++++++++++ .gitea/workflows/database_test.yaml | 77 +++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 .gitea/workflows/Settings.json create mode 100644 .gitea/workflows/database_test.yaml diff --git a/.gitea/workflows/Settings.json b/.gitea/workflows/Settings.json new file mode 100644 index 0000000..c1b7848 --- /dev/null +++ b/.gitea/workflows/Settings.json @@ -0,0 +1,51 @@ +{ + "DbSettings": { + "TypeDatabase": 1, + "ConnectionStringSql": "Data Source=database.db3" + }, + "CacheSettings": { + "TypeDatabase": 0, + "ConnectionString": null + }, + "ScheduleSettings": { + "CronUpdateSchedule": "0 */6 * * *", + "StartTerm": "2024-02-05", + "PairPeriod": { + "1": { + "Start": "09:00:00", + "End": "10:30:00" + }, + "2": { + "Start": "10:40:00", + "End": "12:10:00" + }, + "3": { + "Start": "12:40:00", + "End": "14:10:00" + }, + "4": { + "Start": "14:20:00", + "End": "15:50:00" + }, + "5": { + "Start": "16:20:00", + "End": "17:50:00" + }, + "6": { + "Start": "18:00:00", + "End": "19:30:00" + }, + "7": { + "Start": "19:40:00", + "End": "21:10:00" + } + } + }, + "EmailSettings": { + }, + "LogSettings": { + "EnableLogToFile": false, + "LogFilePath": null, + "LogFileName": null + } +} \ No newline at end of file diff --git a/.gitea/workflows/database_test.yaml b/.gitea/workflows/database_test.yaml new file mode 100644 index 0000000..1a1c5d0 --- /dev/null +++ b/.gitea/workflows/database_test.yaml @@ -0,0 +1,77 @@ +name: Test with Different Databases + +on: + pull_request: + push: + branches: + [master, 'release/*'] + +jobs: + test: + strategy: + matrix: + db-provider: [sqlite, mysql, postgresql] + runs-on: ubuntu-latest + + services: + mysql: + image: mysql:latest + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: testdb + options: >- + --health-cmd "mysqladmin ping --silent" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 3306:3306 + + postgres: + image: postgres:latest + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: testdb + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build the solution + run: dotnet build --configuration Release + + - name: Install jq + run: sudo apt-get install -y jq + + - name: Modify configuration for SQLite + if: matrix.db-provider == 'sqlite' + run: | + jq '.DbSettings.TypeDatabase = 1 | .DbSettings.ConnectionStringSql = "Data Source=test.db3"' Settings.json > temp.json && mv temp.json Settings.json + + - name: Modify configuration for MySQL + if: matrix.db-provider == 'mysql' + run: | + jq '.DbSettings.TypeDatabase = 0 | .DbSettings.ConnectionStringSql = "Server=127.0.0.1;Port=3306;Database=testdb;Uid=root;Pwd=root;"' Settings.json > temp.json && mv temp.json Settings.json + + - name: Modify configuration for PostgreSQL + if: matrix.db-provider == 'postgresql' + run: | + jq '.DbSettings.TypeDatabase = 2 | .DbSettings.ConnectionStringSql = "Host=127.0.0.1;Port=5432;Database=testdb;Username=postgres;Password=postgres"' Settings.json > temp.json && mv temp.json Settings.json + + - name: Run tests + run: dotnet test --configuration Release --no-build --no-restore --verbosity normal \ No newline at end of file -- 2.43.0 From f0544ff42e2381283cdc93b615b78993d5252776 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 23:30:11 +0300 Subject: [PATCH 192/474] fix: error CS0103 --- Endpoint/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 3c39eed..adef11a 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -15,6 +15,7 @@ using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Common.Services.Security; using Mirea.Api.Endpoint.Configuration; using Mirea.Api.Endpoint.Configuration.General; +using Mirea.Api.Endpoint.Configuration.General.Settings; using Mirea.Api.Endpoint.Configuration.General.Validators; using Mirea.Api.Endpoint.Configuration.Swagger; using Mirea.Api.Endpoint.Middleware; @@ -114,6 +115,7 @@ public class Program builder.Configuration.AddJsonFile(PathBuilder.Combine(GeneralConfig.FilePath), optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); + var generalConfig = builder.Configuration.Get(); builder.Services.AddApplication(); builder.Services.AddPersistence(generalConfig?.DbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, generalConfig?.DbSettings?.ConnectionStringSql ?? string.Empty); builder.Services.AddControllers(); -- 2.43.0 From 815c860dc08b925e132436415a3e656fc923ffc2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 30 May 2024 23:58:24 +0300 Subject: [PATCH 193/474] fix: error CS0103 --- Endpoint/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index adef11a..87756d9 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Mirea.Api.DataAccess.Application; 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.Common.Services.Security; -- 2.43.0 From b67f0a82ede82dfc22a41977a177bef0ae02d31b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 31 May 2024 01:16:13 +0300 Subject: [PATCH 194/474] test: fix port --- .gitea/workflows/database_test.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/database_test.yaml b/.gitea/workflows/database_test.yaml index 1a1c5d0..35c03db 100644 --- a/.gitea/workflows/database_test.yaml +++ b/.gitea/workflows/database_test.yaml @@ -25,7 +25,7 @@ jobs: --health-timeout 5s --health-retries 5 ports: - - 3306:3306 + - 5555:3306 postgres: image: postgres:latest @@ -39,7 +39,7 @@ jobs: --health-timeout 5s --health-retries 5 ports: - - 5432:5432 + - 6666:5432 steps: - uses: actions/checkout@v4 @@ -66,12 +66,12 @@ jobs: - name: Modify configuration for MySQL if: matrix.db-provider == 'mysql' run: | - jq '.DbSettings.TypeDatabase = 0 | .DbSettings.ConnectionStringSql = "Server=127.0.0.1;Port=3306;Database=testdb;Uid=root;Pwd=root;"' Settings.json > temp.json && mv temp.json Settings.json + jq '.DbSettings.TypeDatabase = 0 | .DbSettings.ConnectionStringSql = "Server=127.0.0.1;Port=5555;Database=testdb;Uid=root;Pwd=root;"' Settings.json > temp.json && mv temp.json Settings.json - name: Modify configuration for PostgreSQL if: matrix.db-provider == 'postgresql' run: | - jq '.DbSettings.TypeDatabase = 2 | .DbSettings.ConnectionStringSql = "Host=127.0.0.1;Port=5432;Database=testdb;Username=postgres;Password=postgres"' Settings.json > temp.json && mv temp.json Settings.json + jq '.DbSettings.TypeDatabase = 2 | .DbSettings.ConnectionStringSql = "Host=127.0.0.1;Port=6666;Database=testdb;Username=postgres;Password=postgres"' Settings.json > temp.json && mv temp.json Settings.json - name: Run tests run: dotnet test --configuration Release --no-build --no-restore --verbosity normal \ No newline at end of file -- 2.43.0 From f17ee43805ab7855809b379e9f70f284d004f97a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 31 May 2024 01:25:22 +0300 Subject: [PATCH 195/474] test: change docker to apt --- .gitea/workflows/database_test.yaml | 48 ++++++++++++----------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/.gitea/workflows/database_test.yaml b/.gitea/workflows/database_test.yaml index 35c03db..adf2637 100644 --- a/.gitea/workflows/database_test.yaml +++ b/.gitea/workflows/database_test.yaml @@ -12,35 +12,7 @@ jobs: matrix: db-provider: [sqlite, mysql, postgresql] runs-on: ubuntu-latest - - services: - mysql: - image: mysql:latest - env: - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: testdb - options: >- - --health-cmd "mysqladmin ping --silent" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5555:3306 - - postgres: - image: postgres:latest - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: testdb - options: >- - --health-cmd "pg_isready -U postgres" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 6666:5432 - + steps: - uses: actions/checkout@v4 @@ -68,10 +40,28 @@ jobs: run: | jq '.DbSettings.TypeDatabase = 0 | .DbSettings.ConnectionStringSql = "Server=127.0.0.1;Port=5555;Database=testdb;Uid=root;Pwd=root;"' Settings.json > temp.json && mv temp.json Settings.json + - name: Install MySQL + if: matrix.db-provider == 'mysql' + run: | + sudo apt-get update + sudo apt-get install -y mysql-server + sudo service mysql start + sudo mysql -e "CREATE DATABASE testdb; CREATE USER 'root'@'localhost' IDENTIFIED BY 'root'; GRANT ALL PRIVILEGES ON testdb.* TO 'root'@'localhost'; FLUSH PRIVILEGES;" + - name: Modify configuration for PostgreSQL if: matrix.db-provider == 'postgresql' run: | jq '.DbSettings.TypeDatabase = 2 | .DbSettings.ConnectionStringSql = "Host=127.0.0.1;Port=6666;Database=testdb;Username=postgres;Password=postgres"' Settings.json > temp.json && mv temp.json Settings.json + - name: Install PostgreSQL + if: matrix.db-provider == 'postgresql' + run: | + sudo apt-get update + sudo apt-get install -y postgresql postgresql-contrib + sudo service postgresql start + sudo -u postgres psql -c "CREATE DATABASE testdb;" + sudo -u postgres psql -c "CREATE USER postgres WITH PASSWORD 'postgres';" + sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE testdb TO postgres;" + - name: Run tests run: dotnet test --configuration Release --no-build --no-restore --verbosity normal \ No newline at end of file -- 2.43.0 From 7a741d7783462b3dedeff3d263485aecde144e67 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 31 May 2024 01:37:03 +0300 Subject: [PATCH 196/474] test: remove testing --- .gitea/workflows/Settings.json | 51 ---------------------- .gitea/workflows/database_test.yaml | 67 ----------------------------- 2 files changed, 118 deletions(-) delete mode 100644 .gitea/workflows/Settings.json delete mode 100644 .gitea/workflows/database_test.yaml diff --git a/.gitea/workflows/Settings.json b/.gitea/workflows/Settings.json deleted file mode 100644 index c1b7848..0000000 --- a/.gitea/workflows/Settings.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "DbSettings": { - "TypeDatabase": 1, - "ConnectionStringSql": "Data Source=database.db3" - }, - "CacheSettings": { - "TypeDatabase": 0, - "ConnectionString": null - }, - "ScheduleSettings": { - "CronUpdateSchedule": "0 */6 * * *", - "StartTerm": "2024-02-05", - "PairPeriod": { - "1": { - "Start": "09:00:00", - "End": "10:30:00" - }, - "2": { - "Start": "10:40:00", - "End": "12:10:00" - }, - "3": { - "Start": "12:40:00", - "End": "14:10:00" - }, - "4": { - "Start": "14:20:00", - "End": "15:50:00" - }, - "5": { - "Start": "16:20:00", - "End": "17:50:00" - }, - "6": { - "Start": "18:00:00", - "End": "19:30:00" - }, - "7": { - "Start": "19:40:00", - "End": "21:10:00" - } - } - }, - "EmailSettings": { - }, - "LogSettings": { - "EnableLogToFile": false, - "LogFilePath": null, - "LogFileName": null - } -} \ No newline at end of file diff --git a/.gitea/workflows/database_test.yaml b/.gitea/workflows/database_test.yaml deleted file mode 100644 index adf2637..0000000 --- a/.gitea/workflows/database_test.yaml +++ /dev/null @@ -1,67 +0,0 @@ -name: Test with Different Databases - -on: - pull_request: - push: - branches: - [master, 'release/*'] - -jobs: - test: - strategy: - matrix: - db-provider: [sqlite, mysql, postgresql] - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up .NET Core - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - - name: Restore dependencies - run: dotnet restore - - - name: Build the solution - run: dotnet build --configuration Release - - - name: Install jq - run: sudo apt-get install -y jq - - - name: Modify configuration for SQLite - if: matrix.db-provider == 'sqlite' - run: | - jq '.DbSettings.TypeDatabase = 1 | .DbSettings.ConnectionStringSql = "Data Source=test.db3"' Settings.json > temp.json && mv temp.json Settings.json - - - name: Modify configuration for MySQL - if: matrix.db-provider == 'mysql' - run: | - jq '.DbSettings.TypeDatabase = 0 | .DbSettings.ConnectionStringSql = "Server=127.0.0.1;Port=5555;Database=testdb;Uid=root;Pwd=root;"' Settings.json > temp.json && mv temp.json Settings.json - - - name: Install MySQL - if: matrix.db-provider == 'mysql' - run: | - sudo apt-get update - sudo apt-get install -y mysql-server - sudo service mysql start - sudo mysql -e "CREATE DATABASE testdb; CREATE USER 'root'@'localhost' IDENTIFIED BY 'root'; GRANT ALL PRIVILEGES ON testdb.* TO 'root'@'localhost'; FLUSH PRIVILEGES;" - - - name: Modify configuration for PostgreSQL - if: matrix.db-provider == 'postgresql' - run: | - jq '.DbSettings.TypeDatabase = 2 | .DbSettings.ConnectionStringSql = "Host=127.0.0.1;Port=6666;Database=testdb;Username=postgres;Password=postgres"' Settings.json > temp.json && mv temp.json Settings.json - - - name: Install PostgreSQL - if: matrix.db-provider == 'postgresql' - run: | - sudo apt-get update - sudo apt-get install -y postgresql postgresql-contrib - sudo service postgresql start - sudo -u postgres psql -c "CREATE DATABASE testdb;" - sudo -u postgres psql -c "CREATE USER postgres WITH PASSWORD 'postgres';" - sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE testdb TO postgres;" - - - name: Run tests - run: dotnet test --configuration Release --no-build --no-restore --verbosity normal \ No newline at end of file -- 2.43.0 From 04b66871815f75fd1e342f3bee05dbfe427b7a9f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 05:39:02 +0300 Subject: [PATCH 197/474] feat: add mysql configurations --- .../Mysql/Schedule/CampusConfiguration.cs | 20 ++++++++ .../Mysql/Schedule/DisciplineConfiguration.cs | 18 +++++++ .../Mysql/Schedule/FacultyConfiguration.cs | 26 ++++++++++ .../Mysql/Schedule/GroupConfiguration.cs | 25 ++++++++++ .../Schedule/LectureHallConfiguration.cs | 25 ++++++++++ .../LessonAssociationConfiguration.cs | 48 +++++++++++++++++++ .../Mysql/Schedule/LessonConfiguration.cs | 36 ++++++++++++++ .../Mysql/Schedule/ProfessorConfiguration.cs | 19 ++++++++ .../Schedule/SpecificWeekConfiguration.cs | 26 ++++++++++ .../Schedule/TypeOfOccupationConfiguration.cs | 18 +++++++ 10 files changed, 261 insertions(+) create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/CampusConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/DisciplineConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/FacultyConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/GroupConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LectureHallConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LessonAssociationConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LessonConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/ProfessorConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/SpecificWeekConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/TypeOfOccupationConfiguration.cs diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/CampusConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/CampusConfiguration.cs new file mode 100644 index 0000000..a6a961d --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/CampusConfiguration.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public sealed class CampusConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Campus)); + builder.HasKey(c => c.Id); + builder.HasIndex(c => c.Id).IsUnique(); + builder.Property(c => c.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(c => c.Address).HasColumnType("VARCHAR(512)").HasMaxLength(512); + builder.Property(c => c.CodeName).HasColumnType("VARCHAR(16)").IsRequired().HasMaxLength(16); + builder.Property(c => c.FullName).HasColumnType("VARCHAR(256)").HasMaxLength(256); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/DisciplineConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/DisciplineConfiguration.cs new file mode 100644 index 0000000..ababf1f --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/DisciplineConfiguration.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class DisciplineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Discipline)); + builder.HasKey(d => d.Id); + builder.HasIndex(d => d.Id).IsUnique(); + builder.Property(d => d.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(d => d.Name).HasColumnType("VARCHAR(256)").HasMaxLength(256).IsRequired(); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/FacultyConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/FacultyConfiguration.cs new file mode 100644 index 0000000..3e2e24d --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/FacultyConfiguration.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class FacultyConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Faculty)); + builder.HasKey(f => f.Id); + builder.HasIndex(f => f.Id).IsUnique(); + builder.Property(f => f.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(f => f.Name).HasColumnType("VARCHAR(256)").IsRequired().HasMaxLength(256); + + builder.Property(f => f.CampusId).HasColumnType("INT"); + + builder + .HasOne(f => f.Campus) + .WithMany(c => c.Faculties) + .HasForeignKey(c => c.CampusId) + .OnDelete(DeleteBehavior.SetNull); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/GroupConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/GroupConfiguration.cs new file mode 100644 index 0000000..a0e1645 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/GroupConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class GroupConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Group)); + builder.HasKey(g => g.Id); + builder.HasIndex(g => g.Id).IsUnique(); + builder.Property(g => g.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(g => g.FacultyId).HasColumnType("INT"); + builder.Property(g => g.Name).HasColumnType("VARCHAR(64)").IsRequired().HasMaxLength(64); + + builder + .HasOne(g => g.Faculty) + .WithMany(u => u.Groups) + .HasForeignKey(d => d.FacultyId) + .OnDelete(DeleteBehavior.SetNull); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LectureHallConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LectureHallConfiguration.cs new file mode 100644 index 0000000..ec9ab8c --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LectureHallConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class LectureHallConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(LectureHall)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.CampusId).HasColumnType("INT").IsRequired(); + builder.Property(l => l.Name).HasColumnType("VARCHAR(64)").IsRequired().HasMaxLength(64); + + builder + .HasOne(l => l.Campus) + .WithMany(c => c.LectureHalls) + .HasForeignKey(d => d.CampusId) + .OnDelete(DeleteBehavior.Restrict); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LessonAssociationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LessonAssociationConfiguration.cs new file mode 100644 index 0000000..f35317d --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LessonAssociationConfiguration.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class LessonAssociationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(LessonAssociation)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.LinkToMeet).HasColumnType("VARCHAR(512)").HasMaxLength(512); + + builder.Property(l => l.LessonId).HasColumnType("INT").IsRequired(); + builder.Property(l => l.ProfessorId).HasColumnType("INT"); + builder.Property(l => l.LectureHallId).HasColumnType("INT"); + builder.Property(l => l.TypeOfOccupationId).HasColumnType("INT").IsRequired(); + + + builder + .HasOne(l => l.Lesson) + .WithMany(d => d.LessonAssociations) + .HasForeignKey(l => l.LessonId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(l => l.Professor) + .WithMany(p => p.LessonAssociations) + .HasForeignKey(l => l.ProfessorId) + .OnDelete(DeleteBehavior.SetNull); + + builder + .HasOne(l => l.LectureHall) + .WithMany(l => l.LessonAssociations) + .HasForeignKey(l => l.LectureHallId) + .OnDelete(DeleteBehavior.SetNull); + + builder + .HasOne(l => l.TypeOfOccupation) + .WithMany(t => t.Lessons) + .HasForeignKey(d => d.TypeOfOccupationId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LessonConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LessonConfiguration.cs new file mode 100644 index 0000000..66e76d8 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/LessonConfiguration.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class LessonConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Lesson)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.IsEven).HasColumnType("BIT(1)").IsRequired(); + builder.Property(l => l.DayOfWeek).HasColumnType("INT").IsRequired(); + builder.Property(l => l.PairNumber).HasColumnType("INT").IsRequired(); + builder.Property(l => l.IsExcludedWeeks).HasColumnType("BIT(1)"); + + builder.Property(l => l.GroupId).HasColumnType("INT").IsRequired(); + builder.Property(l => l.DisciplineId).HasColumnType("INT").IsRequired(); + + builder + .HasOne(l => l.Group) + .WithMany(g => g.Lessons) + .HasForeignKey(d => d.GroupId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(l => l.Discipline) + .WithMany(d => d.Lessons) + .HasForeignKey(l => l.DisciplineId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/ProfessorConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/ProfessorConfiguration.cs new file mode 100644 index 0000000..a7e8f02 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/ProfessorConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class ProfessorConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Professor)); + builder.HasKey(p => p.Id); + builder.HasIndex(p => p.Id).IsUnique(); + builder.Property(p => p.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(p => p.Name).HasColumnType("VARCHAR").IsRequired(); + builder.Property(p => p.AltName).HasColumnType("VARCHAR"); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/SpecificWeekConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/SpecificWeekConfiguration.cs new file mode 100644 index 0000000..e7b0529 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/SpecificWeekConfiguration.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class SpecificWeekConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(SpecificWeek)); + builder.HasKey(s => s.Id); + builder.HasIndex(s => s.Id).IsUnique(); + builder.Property(s => s.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(s => s.WeekNumber).HasColumnType("INT").IsRequired(); + + builder.Property(s => s.LessonId).HasColumnType("INT").IsRequired(); + + builder + .HasOne(s => s.Lesson) + .WithMany(l => l.SpecificWeeks) + .HasForeignKey(s => s.LessonId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/TypeOfOccupationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/TypeOfOccupationConfiguration.cs new file mode 100644 index 0000000..90e92e5 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/TypeOfOccupationConfiguration.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Mysql.Schedule; + +public class TypeOfOccupationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(TypeOfOccupation)); + builder.HasKey(t => t.Id); + builder.HasIndex(t => t.Id).IsUnique(); + builder.Property(t => t.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(t => t.ShortName).HasColumnType("VARCHAR").IsRequired().HasMaxLength(16); + } +} \ No newline at end of file -- 2.43.0 From 7aa37618e0c36814cb8f7c3de29b2c902005ec6f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 05:39:25 +0300 Subject: [PATCH 198/474] feat: add postgresql configurations --- .../Schedule/CampusConfiguration.cs | 20 ++++++++ .../Schedule/DisciplineConfiguration.cs | 18 +++++++ .../Schedule/FacultyConfiguration.cs | 26 ++++++++++ .../Postgresql/Schedule/GroupConfiguration.cs | 25 ++++++++++ .../Schedule/LectureHallConfiguration.cs | 25 ++++++++++ .../LessonAssociationConfiguration.cs | 48 +++++++++++++++++++ .../Schedule/LessonConfiguration.cs | 36 ++++++++++++++ .../Schedule/ProfessorConfiguration.cs | 19 ++++++++ .../Schedule/SpecificWeekConfiguration.cs | 26 ++++++++++ .../Schedule/TypeOfOccupationConfiguration.cs | 18 +++++++ 10 files changed, 261 insertions(+) create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/CampusConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/DisciplineConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/FacultyConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/GroupConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LectureHallConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LessonAssociationConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LessonConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/ProfessorConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/SpecificWeekConfiguration.cs create mode 100644 SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/TypeOfOccupationConfiguration.cs diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/CampusConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/CampusConfiguration.cs new file mode 100644 index 0000000..055be55 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/CampusConfiguration.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public sealed class CampusConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Campus)); + builder.HasKey(c => c.Id); + builder.HasIndex(c => c.Id).IsUnique(); + builder.Property(c => c.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(c => c.Address).HasColumnType("text").HasMaxLength(512); + builder.Property(c => c.CodeName).HasColumnType("text").IsRequired().HasMaxLength(16); + builder.Property(c => c.FullName).HasColumnType("text").HasMaxLength(256); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/DisciplineConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/DisciplineConfiguration.cs new file mode 100644 index 0000000..01ff876 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/DisciplineConfiguration.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class DisciplineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Discipline)); + builder.HasKey(d => d.Id); + builder.HasIndex(d => d.Id).IsUnique(); + builder.Property(d => d.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(d => d.Name).HasColumnType("text").HasMaxLength(256).IsRequired(); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/FacultyConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/FacultyConfiguration.cs new file mode 100644 index 0000000..05f622f --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/FacultyConfiguration.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class FacultyConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Faculty)); + builder.HasKey(f => f.Id); + builder.HasIndex(f => f.Id).IsUnique(); + builder.Property(f => f.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(f => f.Name).HasColumnType("text").IsRequired().HasMaxLength(256); + + builder.Property(f => f.CampusId).HasColumnType("serial"); + + builder + .HasOne(f => f.Campus) + .WithMany(c => c.Faculties) + .HasForeignKey(c => c.CampusId) + .OnDelete(DeleteBehavior.SetNull); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/GroupConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/GroupConfiguration.cs new file mode 100644 index 0000000..1ee501d --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/GroupConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class GroupConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Group)); + builder.HasKey(g => g.Id); + builder.HasIndex(g => g.Id).IsUnique(); + builder.Property(g => g.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(g => g.FacultyId).HasColumnType("serial"); + builder.Property(g => g.Name).HasColumnType("text").IsRequired().HasMaxLength(64); + + builder + .HasOne(g => g.Faculty) + .WithMany(u => u.Groups) + .HasForeignKey(d => d.FacultyId) + .OnDelete(DeleteBehavior.SetNull); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LectureHallConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LectureHallConfiguration.cs new file mode 100644 index 0000000..039df3d --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LectureHallConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class LectureHallConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(LectureHall)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.CampusId).HasColumnType("INT").IsRequired(); + builder.Property(l => l.Name).HasColumnType("VARCHAR(64)").IsRequired().HasMaxLength(64); + + builder + .HasOne(l => l.Campus) + .WithMany(c => c.LectureHalls) + .HasForeignKey(d => d.CampusId) + .OnDelete(DeleteBehavior.Restrict); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LessonAssociationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LessonAssociationConfiguration.cs new file mode 100644 index 0000000..aef538a --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LessonAssociationConfiguration.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class LessonAssociationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(LessonAssociation)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.LinkToMeet).HasColumnType("text").HasMaxLength(512); + + builder.Property(l => l.LessonId).HasColumnType("serial").IsRequired(); + builder.Property(l => l.ProfessorId).HasColumnType("serial"); + builder.Property(l => l.LectureHallId).HasColumnType("serial"); + builder.Property(l => l.TypeOfOccupationId).HasColumnType("serial").IsRequired(); + + + builder + .HasOne(l => l.Lesson) + .WithMany(d => d.LessonAssociations) + .HasForeignKey(l => l.LessonId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(l => l.Professor) + .WithMany(p => p.LessonAssociations) + .HasForeignKey(l => l.ProfessorId) + .OnDelete(DeleteBehavior.SetNull); + + builder + .HasOne(l => l.LectureHall) + .WithMany(l => l.LessonAssociations) + .HasForeignKey(l => l.LectureHallId) + .OnDelete(DeleteBehavior.SetNull); + + builder + .HasOne(l => l.TypeOfOccupation) + .WithMany(t => t.Lessons) + .HasForeignKey(d => d.TypeOfOccupationId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LessonConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LessonConfiguration.cs new file mode 100644 index 0000000..a47076d --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/LessonConfiguration.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class LessonConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Lesson)); + builder.HasKey(l => l.Id); + builder.HasIndex(l => l.Id).IsUnique(); + builder.Property(l => l.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(l => l.IsEven).HasColumnType("boolean").IsRequired(); + builder.Property(l => l.DayOfWeek).HasColumnType("serial").IsRequired(); + builder.Property(l => l.PairNumber).HasColumnType("serial").IsRequired(); + builder.Property(l => l.IsExcludedWeeks).HasColumnType("boolean"); + + builder.Property(l => l.GroupId).HasColumnType("serial").IsRequired(); + builder.Property(l => l.DisciplineId).HasColumnType("serial").IsRequired(); + + builder + .HasOne(l => l.Group) + .WithMany(g => g.Lessons) + .HasForeignKey(d => d.GroupId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(l => l.Discipline) + .WithMany(d => d.Lessons) + .HasForeignKey(l => l.DisciplineId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/ProfessorConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/ProfessorConfiguration.cs new file mode 100644 index 0000000..9779cde --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/ProfessorConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class ProfessorConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Professor)); + builder.HasKey(p => p.Id); + builder.HasIndex(p => p.Id).IsUnique(); + builder.Property(p => p.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(p => p.Name).HasColumnType("text").IsRequired(); + builder.Property(p => p.AltName).HasColumnType("text"); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/SpecificWeekConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/SpecificWeekConfiguration.cs new file mode 100644 index 0000000..6415c7b --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/SpecificWeekConfiguration.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class SpecificWeekConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(SpecificWeek)); + builder.HasKey(s => s.Id); + builder.HasIndex(s => s.Id).IsUnique(); + builder.Property(s => s.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(s => s.WeekNumber).HasColumnType("serial").IsRequired(); + + builder.Property(s => s.LessonId).HasColumnType("serial").IsRequired(); + + builder + .HasOne(s => s.Lesson) + .WithMany(l => l.SpecificWeeks) + .HasForeignKey(s => s.LessonId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/TypeOfOccupationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/TypeOfOccupationConfiguration.cs new file mode 100644 index 0000000..af53715 --- /dev/null +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/TypeOfOccupationConfiguration.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Mirea.Api.DataAccess.Domain.Schedule; + +namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Postgresql.Schedule; + +public class TypeOfOccupationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(TypeOfOccupation)); + builder.HasKey(t => t.Id); + builder.HasIndex(t => t.Id).IsUnique(); + builder.Property(t => t.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); + + builder.Property(t => t.ShortName).HasColumnType("text").IsRequired().HasMaxLength(16); + } +} \ No newline at end of file -- 2.43.0 From ae4682368546a737d8981ad71438cfec5ae3e238 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 05:40:09 +0300 Subject: [PATCH 199/474] build: upgrade dependencies --- SqlData/Persistence/Persistence.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index d191edd..07ca710 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -13,8 +13,8 @@ - - + + -- 2.43.0 From 8cd8277c22defc9edf93113fbbb52893bc5eddc7 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 05:40:48 +0300 Subject: [PATCH 200/474] feat: add assembly for migration --- .../MysqlMigrations/MysqlMigrations.csproj | 12 ++++++++++++ .../Migrations/PsqlMigrations/PsqlMigrations.csproj | 12 ++++++++++++ .../SqliteMigrations/SqliteMigrations.csproj | 12 ++++++++++++ SqlData/Persistence/DependencyInjection.cs | 9 ++++++--- 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 SqlData/Migrations/MysqlMigrations/MysqlMigrations.csproj create mode 100644 SqlData/Migrations/PsqlMigrations/PsqlMigrations.csproj create mode 100644 SqlData/Migrations/SqliteMigrations/SqliteMigrations.csproj diff --git a/SqlData/Migrations/MysqlMigrations/MysqlMigrations.csproj b/SqlData/Migrations/MysqlMigrations/MysqlMigrations.csproj new file mode 100644 index 0000000..d9d83f2 --- /dev/null +++ b/SqlData/Migrations/MysqlMigrations/MysqlMigrations.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + ..\..\Persistence\bin\ + + + + + + + diff --git a/SqlData/Migrations/PsqlMigrations/PsqlMigrations.csproj b/SqlData/Migrations/PsqlMigrations/PsqlMigrations.csproj new file mode 100644 index 0000000..d9d83f2 --- /dev/null +++ b/SqlData/Migrations/PsqlMigrations/PsqlMigrations.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + ..\..\Persistence\bin\ + + + + + + + diff --git a/SqlData/Migrations/SqliteMigrations/SqliteMigrations.csproj b/SqlData/Migrations/SqliteMigrations/SqliteMigrations.csproj new file mode 100644 index 0000000..d9d83f2 --- /dev/null +++ b/SqlData/Migrations/SqliteMigrations/SqliteMigrations.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + ..\..\Persistence\bin\ + + + + + + + diff --git a/SqlData/Persistence/DependencyInjection.cs b/SqlData/Persistence/DependencyInjection.cs index ad7c511..f86cd87 100644 --- a/SqlData/Persistence/DependencyInjection.cs +++ b/SqlData/Persistence/DependencyInjection.cs @@ -76,9 +76,12 @@ public static class DependencyInjection { return dbProvider switch { - DatabaseProvider.Sqlite => options.UseSqlite(connection), - DatabaseProvider.Mysql => options.UseMySql(connection, ServerVersion.AutoDetect(connection)), - DatabaseProvider.Postgresql => options.UseNpgsql(connection), + DatabaseProvider.Mysql => options.UseMySql(connection, ServerVersion.AutoDetect(connection), + x => x.MigrationsAssembly("MysqlMigrations")), + DatabaseProvider.Sqlite => options.UseSqlite(connection, + x => x.MigrationsAssembly("SqliteMigrations")), + DatabaseProvider.Postgresql => options.UseNpgsql(connection, + x => x.MigrationsAssembly("PsqlMigrations")), _ => throw new ArgumentException("Unsupported database provider", Enum.GetName(dbProvider)) }; } -- 2.43.0 From 79118c5283fa4d0108d5fca30df2abcf4796612c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 05:42:05 +0300 Subject: [PATCH 201/474] build: add ref to projects for migrations --- Backend.sln | 33 +++++++++++++++++++++++++++++++++ Endpoint/Endpoint.csproj | 3 +++ 2 files changed, 36 insertions(+) diff --git a/Backend.sln b/Backend.sln index f24d8d8..81a8dbe 100644 --- a/Backend.sln +++ b/Backend.sln @@ -29,6 +29,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "SqlData\Appl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "SqlData\Persistence\Persistence.csproj", "{48C9998C-ECE2-407F-835F-1A7255A5C99E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Migrations", "Migrations", "{79639CD4-7A16-4AB4-BBE8-672B9ACCB3F5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqliteMigrations", "SqlData\Migrations\SqliteMigrations\SqliteMigrations.csproj", "{EF5530BD-4BF4-4DD8-80BB-04C6B6623DA7}" + ProjectSection(ProjectDependencies) = postProject + {48C9998C-ECE2-407F-835F-1A7255A5C99E} = {48C9998C-ECE2-407F-835F-1A7255A5C99E} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MysqlMigrations", "SqlData\Migrations\MysqlMigrations\MysqlMigrations.csproj", "{5861915B-9574-4D5D-872F-D54A09651697}" + ProjectSection(ProjectDependencies) = postProject + {48C9998C-ECE2-407F-835F-1A7255A5C99E} = {48C9998C-ECE2-407F-835F-1A7255A5C99E} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PsqlMigrations", "SqlData\Migrations\PsqlMigrations\PsqlMigrations.csproj", "{E9E238CD-6DD8-4B29-8C36-C61F1168FCCD}" + ProjectSection(ProjectDependencies) = postProject + {48C9998C-ECE2-407F-835F-1A7255A5C99E} = {48C9998C-ECE2-407F-835F-1A7255A5C99E} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,6 +76,18 @@ Global {48C9998C-ECE2-407F-835F-1A7255A5C99E}.Debug|Any CPU.Build.0 = Debug|Any CPU {48C9998C-ECE2-407F-835F-1A7255A5C99E}.Release|Any CPU.ActiveCfg = Release|Any CPU {48C9998C-ECE2-407F-835F-1A7255A5C99E}.Release|Any CPU.Build.0 = Release|Any CPU + {EF5530BD-4BF4-4DD8-80BB-04C6B6623DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF5530BD-4BF4-4DD8-80BB-04C6B6623DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF5530BD-4BF4-4DD8-80BB-04C6B6623DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF5530BD-4BF4-4DD8-80BB-04C6B6623DA7}.Release|Any CPU.Build.0 = Release|Any CPU + {5861915B-9574-4D5D-872F-D54A09651697}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5861915B-9574-4D5D-872F-D54A09651697}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5861915B-9574-4D5D-872F-D54A09651697}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5861915B-9574-4D5D-872F-D54A09651697}.Release|Any CPU.Build.0 = Release|Any CPU + {E9E238CD-6DD8-4B29-8C36-C61F1168FCCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9E238CD-6DD8-4B29-8C36-C61F1168FCCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9E238CD-6DD8-4B29-8C36-C61F1168FCCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9E238CD-6DD8-4B29-8C36-C61F1168FCCD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -67,6 +96,10 @@ Global {3BFD6180-7CA7-4E85-A379-225B872439A1} = {7E7A63CD-547B-4FB4-A383-EB75298020A1} {0B1F3656-E5B3-440C-961F-A7D004FBE9A8} = {7E7A63CD-547B-4FB4-A383-EB75298020A1} {48C9998C-ECE2-407F-835F-1A7255A5C99E} = {7E7A63CD-547B-4FB4-A383-EB75298020A1} + {79639CD4-7A16-4AB4-BBE8-672B9ACCB3F5} = {7E7A63CD-547B-4FB4-A383-EB75298020A1} + {EF5530BD-4BF4-4DD8-80BB-04C6B6623DA7} = {79639CD4-7A16-4AB4-BBE8-672B9ACCB3F5} + {5861915B-9574-4D5D-872F-D54A09651697} = {79639CD4-7A16-4AB4-BBE8-672B9ACCB3F5} + {E9E238CD-6DD8-4B29-8C36-C61F1168FCCD} = {79639CD4-7A16-4AB4-BBE8-672B9ACCB3F5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E80A1224-87F5-4FEB-82AE-89006BE98B12} diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 2aad3c8..bd7ee16 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -34,6 +34,9 @@ + + + \ No newline at end of file -- 2.43.0 From d2ba2d982c60197c902b9099f87928b326b4d30b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 05:42:23 +0300 Subject: [PATCH 202/474] feat: add migrations --- ...0240601023106_InitialMigration.Designer.cs | 442 ++++++++++++++++++ .../20240601023106_InitialMigration.cs | 389 +++++++++++++++ .../Migrations/UberDbContextModelSnapshot.cs | 439 +++++++++++++++++ ...0240601021702_InitialMigration.Designer.cs | 442 ++++++++++++++++++ .../20240601021702_InitialMigration.cs | 365 +++++++++++++++ .../Migrations/UberDbContextModelSnapshot.cs | 439 +++++++++++++++++ ...0240601015714_InitialMigration.Designer.cs | 417 +++++++++++++++++ .../20240601015714_InitialMigration.cs | 364 +++++++++++++++ .../Migrations/UberDbContextModelSnapshot.cs | 414 ++++++++++++++++ 9 files changed, 3711 insertions(+) create mode 100644 SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.Designer.cs create mode 100644 SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.cs create mode 100644 SqlData/Migrations/MysqlMigrations/Migrations/UberDbContextModelSnapshot.cs create mode 100644 SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.Designer.cs create mode 100644 SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.cs create mode 100644 SqlData/Migrations/PsqlMigrations/Migrations/UberDbContextModelSnapshot.cs create mode 100644 SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.Designer.cs create mode 100644 SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.cs create mode 100644 SqlData/Migrations/SqliteMigrations/Migrations/UberDbContextModelSnapshot.cs diff --git a/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.Designer.cs b/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.Designer.cs new file mode 100644 index 0000000..d4c0d32 --- /dev/null +++ b/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.Designer.cs @@ -0,0 +1,442 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; + +#nullable disable + +namespace MysqlMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + [Migration("20240601023106_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BIT(1)"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BIT(1)"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("Faculties") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("Faculties"); + + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.cs b/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.cs new file mode 100644 index 0000000..573b78f --- /dev/null +++ b/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.cs @@ -0,0 +1,389 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MysqlMigrations.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Campus", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + CodeName = table.Column(type: "TEXT", maxLength: 16, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + FullName = table.Column(type: "TEXT", maxLength: 256, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Address = table.Column(type: "TEXT", maxLength: 512, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_Campus", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Discipline", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_Discipline", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Professor", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "TEXT", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + AltName = table.Column(type: "TEXT", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_Professor", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "TypeOfOccupation", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ShortName = table.Column(type: "TEXT", maxLength: 16, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_TypeOfOccupation", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Faculty", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CampusId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Faculty", x => x.Id); + table.ForeignKey( + name: "FK_Faculty_Campus_CampusId", + column: x => x.CampusId, + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "LectureHall", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CampusId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LectureHall", x => x.Id); + table.ForeignKey( + name: "FK_LectureHall_Campus_CampusId", + column: x => x.CampusId, + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Group", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + FacultyId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Group", x => x.Id); + table.ForeignKey( + name: "FK_Group_Faculty_FacultyId", + column: x => x.FacultyId, + principalTable: "Faculty", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Lesson", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + IsEven = table.Column(type: "BIT(1)", nullable: false), + DayOfWeek = table.Column(type: "INTEGER", nullable: false), + PairNumber = table.Column(type: "INTEGER", nullable: false), + IsExcludedWeeks = table.Column(type: "BIT(1)", nullable: true), + GroupId = table.Column(type: "INTEGER", nullable: false), + DisciplineId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Lesson", x => x.Id); + table.ForeignKey( + name: "FK_Lesson_Discipline_DisciplineId", + column: x => x.DisciplineId, + principalTable: "Discipline", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Lesson_Group_GroupId", + column: x => x.GroupId, + principalTable: "Group", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "LessonAssociation", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + LinkToMeet = table.Column(type: "TEXT", maxLength: 512, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + TypeOfOccupationId = table.Column(type: "INTEGER", nullable: false), + LessonId = table.Column(type: "INTEGER", nullable: false), + ProfessorId = table.Column(type: "INTEGER", nullable: true), + LectureHallId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_LessonAssociation", x => x.Id); + table.ForeignKey( + name: "FK_LessonAssociation_LectureHall_LectureHallId", + column: x => x.LectureHallId, + principalTable: "LectureHall", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_LessonAssociation_Lesson_LessonId", + column: x => x.LessonId, + principalTable: "Lesson", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LessonAssociation_Professor_ProfessorId", + column: x => x.ProfessorId, + principalTable: "Professor", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_LessonAssociation_TypeOfOccupation_TypeOfOccupationId", + column: x => x.TypeOfOccupationId, + principalTable: "TypeOfOccupation", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "SpecificWeek", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + WeekNumber = table.Column(type: "INTEGER", nullable: false), + LessonId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SpecificWeek", x => x.Id); + table.ForeignKey( + name: "FK_SpecificWeek_Lesson_LessonId", + column: x => x.LessonId, + principalTable: "Lesson", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_Campus_Id", + table: "Campus", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Discipline_Id", + table: "Discipline", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_CampusId", + table: "Faculty", + column: "CampusId"); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_Id", + table: "Faculty", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Group_FacultyId", + table: "Group", + column: "FacultyId"); + + migrationBuilder.CreateIndex( + name: "IX_Group_Id", + table: "Group", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LectureHall_CampusId", + table: "LectureHall", + column: "CampusId"); + + migrationBuilder.CreateIndex( + name: "IX_LectureHall_Id", + table: "LectureHall", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_DisciplineId", + table: "Lesson", + column: "DisciplineId"); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_GroupId", + table: "Lesson", + column: "GroupId"); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_Id", + table: "Lesson", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_Id", + table: "LessonAssociation", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_LectureHallId", + table: "LessonAssociation", + column: "LectureHallId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_LessonId", + table: "LessonAssociation", + column: "LessonId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_ProfessorId", + table: "LessonAssociation", + column: "ProfessorId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_TypeOfOccupationId", + table: "LessonAssociation", + column: "TypeOfOccupationId"); + + migrationBuilder.CreateIndex( + name: "IX_Professor_Id", + table: "Professor", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SpecificWeek_Id", + table: "SpecificWeek", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SpecificWeek_LessonId", + table: "SpecificWeek", + column: "LessonId"); + + migrationBuilder.CreateIndex( + name: "IX_TypeOfOccupation_Id", + table: "TypeOfOccupation", + column: "Id", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LessonAssociation"); + + migrationBuilder.DropTable( + name: "SpecificWeek"); + + migrationBuilder.DropTable( + name: "LectureHall"); + + migrationBuilder.DropTable( + name: "Professor"); + + migrationBuilder.DropTable( + name: "TypeOfOccupation"); + + migrationBuilder.DropTable( + name: "Lesson"); + + migrationBuilder.DropTable( + name: "Discipline"); + + migrationBuilder.DropTable( + name: "Group"); + + migrationBuilder.DropTable( + name: "Faculty"); + + migrationBuilder.DropTable( + name: "Campus"); + } + } +} diff --git a/SqlData/Migrations/MysqlMigrations/Migrations/UberDbContextModelSnapshot.cs b/SqlData/Migrations/MysqlMigrations/Migrations/UberDbContextModelSnapshot.cs new file mode 100644 index 0000000..5e0b48f --- /dev/null +++ b/SqlData/Migrations/MysqlMigrations/Migrations/UberDbContextModelSnapshot.cs @@ -0,0 +1,439 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; + +#nullable disable + +namespace MysqlMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + partial class UberDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BIT(1)"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BIT(1)"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("Faculties") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("Faculties"); + + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.Designer.cs b/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.Designer.cs new file mode 100644 index 0000000..1bea549 --- /dev/null +++ b/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.Designer.cs @@ -0,0 +1,442 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace PsqlMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + [Migration("20240601021702_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BOOLEAN"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BOOLEAN"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("Faculties") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("Faculties"); + + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.cs b/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.cs new file mode 100644 index 0000000..9015b9f --- /dev/null +++ b/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.cs @@ -0,0 +1,365 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace PsqlMigrations.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Campus", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CodeName = table.Column(type: "TEXT", maxLength: 16, nullable: false), + FullName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Address = table.Column(type: "TEXT", maxLength: 512, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Campus", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Discipline", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Discipline", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Professor", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "TEXT", nullable: false), + AltName = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Professor", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TypeOfOccupation", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ShortName = table.Column(type: "TEXT", maxLength: 16, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TypeOfOccupation", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Faculty", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: false), + CampusId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Faculty", x => x.Id); + table.ForeignKey( + name: "FK_Faculty_Campus_CampusId", + column: x => x.CampusId, + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "LectureHall", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false), + CampusId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LectureHall", x => x.Id); + table.ForeignKey( + name: "FK_LectureHall_Campus_CampusId", + column: x => x.CampusId, + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Group", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false), + FacultyId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Group", x => x.Id); + table.ForeignKey( + name: "FK_Group_Faculty_FacultyId", + column: x => x.FacultyId, + principalTable: "Faculty", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "Lesson", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + IsEven = table.Column(type: "BOOLEAN", nullable: false), + DayOfWeek = table.Column(type: "INTEGER", nullable: false), + PairNumber = table.Column(type: "INTEGER", nullable: false), + IsExcludedWeeks = table.Column(type: "BOOLEAN", nullable: true), + GroupId = table.Column(type: "INTEGER", nullable: false), + DisciplineId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Lesson", x => x.Id); + table.ForeignKey( + name: "FK_Lesson_Discipline_DisciplineId", + column: x => x.DisciplineId, + principalTable: "Discipline", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Lesson_Group_GroupId", + column: x => x.GroupId, + principalTable: "Group", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LessonAssociation", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + LinkToMeet = table.Column(type: "TEXT", maxLength: 512, nullable: true), + TypeOfOccupationId = table.Column(type: "INTEGER", nullable: false), + LessonId = table.Column(type: "INTEGER", nullable: false), + ProfessorId = table.Column(type: "INTEGER", nullable: true), + LectureHallId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_LessonAssociation", x => x.Id); + table.ForeignKey( + name: "FK_LessonAssociation_LectureHall_LectureHallId", + column: x => x.LectureHallId, + principalTable: "LectureHall", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_LessonAssociation_Lesson_LessonId", + column: x => x.LessonId, + principalTable: "Lesson", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LessonAssociation_Professor_ProfessorId", + column: x => x.ProfessorId, + principalTable: "Professor", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_LessonAssociation_TypeOfOccupation_TypeOfOccupationId", + column: x => x.TypeOfOccupationId, + principalTable: "TypeOfOccupation", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SpecificWeek", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + WeekNumber = table.Column(type: "INTEGER", nullable: false), + LessonId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SpecificWeek", x => x.Id); + table.ForeignKey( + name: "FK_SpecificWeek_Lesson_LessonId", + column: x => x.LessonId, + principalTable: "Lesson", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Campus_Id", + table: "Campus", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Discipline_Id", + table: "Discipline", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_CampusId", + table: "Faculty", + column: "CampusId"); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_Id", + table: "Faculty", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Group_FacultyId", + table: "Group", + column: "FacultyId"); + + migrationBuilder.CreateIndex( + name: "IX_Group_Id", + table: "Group", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LectureHall_CampusId", + table: "LectureHall", + column: "CampusId"); + + migrationBuilder.CreateIndex( + name: "IX_LectureHall_Id", + table: "LectureHall", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_DisciplineId", + table: "Lesson", + column: "DisciplineId"); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_GroupId", + table: "Lesson", + column: "GroupId"); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_Id", + table: "Lesson", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_Id", + table: "LessonAssociation", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_LectureHallId", + table: "LessonAssociation", + column: "LectureHallId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_LessonId", + table: "LessonAssociation", + column: "LessonId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_ProfessorId", + table: "LessonAssociation", + column: "ProfessorId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_TypeOfOccupationId", + table: "LessonAssociation", + column: "TypeOfOccupationId"); + + migrationBuilder.CreateIndex( + name: "IX_Professor_Id", + table: "Professor", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SpecificWeek_Id", + table: "SpecificWeek", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SpecificWeek_LessonId", + table: "SpecificWeek", + column: "LessonId"); + + migrationBuilder.CreateIndex( + name: "IX_TypeOfOccupation_Id", + table: "TypeOfOccupation", + column: "Id", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LessonAssociation"); + + migrationBuilder.DropTable( + name: "SpecificWeek"); + + migrationBuilder.DropTable( + name: "LectureHall"); + + migrationBuilder.DropTable( + name: "Professor"); + + migrationBuilder.DropTable( + name: "TypeOfOccupation"); + + migrationBuilder.DropTable( + name: "Lesson"); + + migrationBuilder.DropTable( + name: "Discipline"); + + migrationBuilder.DropTable( + name: "Group"); + + migrationBuilder.DropTable( + name: "Faculty"); + + migrationBuilder.DropTable( + name: "Campus"); + } + } +} diff --git a/SqlData/Migrations/PsqlMigrations/Migrations/UberDbContextModelSnapshot.cs b/SqlData/Migrations/PsqlMigrations/Migrations/UberDbContextModelSnapshot.cs new file mode 100644 index 0000000..6039a03 --- /dev/null +++ b/SqlData/Migrations/PsqlMigrations/Migrations/UberDbContextModelSnapshot.cs @@ -0,0 +1,439 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace PsqlMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + partial class UberDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BOOLEAN"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BOOLEAN"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("Faculties") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("Faculties"); + + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.Designer.cs b/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.Designer.cs new file mode 100644 index 0000000..e100297 --- /dev/null +++ b/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.Designer.cs @@ -0,0 +1,417 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; + +#nullable disable + +namespace SqliteMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + [Migration("20240601015714_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BOOLEAN"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BOOLEAN"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("Faculties") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("Faculties"); + + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.cs b/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.cs new file mode 100644 index 0000000..9afa7f2 --- /dev/null +++ b/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.cs @@ -0,0 +1,364 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SqliteMigrations.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Campus", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CodeName = table.Column(type: "TEXT", maxLength: 16, nullable: false), + FullName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Address = table.Column(type: "TEXT", maxLength: 512, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Campus", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Discipline", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Discipline", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Professor", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + AltName = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Professor", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TypeOfOccupation", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ShortName = table.Column(type: "TEXT", maxLength: 16, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TypeOfOccupation", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Faculty", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: false), + CampusId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Faculty", x => x.Id); + table.ForeignKey( + name: "FK_Faculty_Campus_CampusId", + column: x => x.CampusId, + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "LectureHall", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false), + CampusId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LectureHall", x => x.Id); + table.ForeignKey( + name: "FK_LectureHall_Campus_CampusId", + column: x => x.CampusId, + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Group", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false), + FacultyId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Group", x => x.Id); + table.ForeignKey( + name: "FK_Group_Faculty_FacultyId", + column: x => x.FacultyId, + principalTable: "Faculty", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "Lesson", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + IsEven = table.Column(type: "BOOLEAN", nullable: false), + DayOfWeek = table.Column(type: "INTEGER", nullable: false), + PairNumber = table.Column(type: "INTEGER", nullable: false), + IsExcludedWeeks = table.Column(type: "BOOLEAN", nullable: true), + GroupId = table.Column(type: "INTEGER", nullable: false), + DisciplineId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Lesson", x => x.Id); + table.ForeignKey( + name: "FK_Lesson_Discipline_DisciplineId", + column: x => x.DisciplineId, + principalTable: "Discipline", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Lesson_Group_GroupId", + column: x => x.GroupId, + principalTable: "Group", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LessonAssociation", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + LinkToMeet = table.Column(type: "TEXT", maxLength: 512, nullable: true), + TypeOfOccupationId = table.Column(type: "INTEGER", nullable: false), + LessonId = table.Column(type: "INTEGER", nullable: false), + ProfessorId = table.Column(type: "INTEGER", nullable: true), + LectureHallId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_LessonAssociation", x => x.Id); + table.ForeignKey( + name: "FK_LessonAssociation_LectureHall_LectureHallId", + column: x => x.LectureHallId, + principalTable: "LectureHall", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_LessonAssociation_Lesson_LessonId", + column: x => x.LessonId, + principalTable: "Lesson", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LessonAssociation_Professor_ProfessorId", + column: x => x.ProfessorId, + principalTable: "Professor", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_LessonAssociation_TypeOfOccupation_TypeOfOccupationId", + column: x => x.TypeOfOccupationId, + principalTable: "TypeOfOccupation", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SpecificWeek", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + WeekNumber = table.Column(type: "INTEGER", nullable: false), + LessonId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SpecificWeek", x => x.Id); + table.ForeignKey( + name: "FK_SpecificWeek_Lesson_LessonId", + column: x => x.LessonId, + principalTable: "Lesson", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Campus_Id", + table: "Campus", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Discipline_Id", + table: "Discipline", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_CampusId", + table: "Faculty", + column: "CampusId"); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_Id", + table: "Faculty", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Group_FacultyId", + table: "Group", + column: "FacultyId"); + + migrationBuilder.CreateIndex( + name: "IX_Group_Id", + table: "Group", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LectureHall_CampusId", + table: "LectureHall", + column: "CampusId"); + + migrationBuilder.CreateIndex( + name: "IX_LectureHall_Id", + table: "LectureHall", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_DisciplineId", + table: "Lesson", + column: "DisciplineId"); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_GroupId", + table: "Lesson", + column: "GroupId"); + + migrationBuilder.CreateIndex( + name: "IX_Lesson_Id", + table: "Lesson", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_Id", + table: "LessonAssociation", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_LectureHallId", + table: "LessonAssociation", + column: "LectureHallId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_LessonId", + table: "LessonAssociation", + column: "LessonId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_ProfessorId", + table: "LessonAssociation", + column: "ProfessorId"); + + migrationBuilder.CreateIndex( + name: "IX_LessonAssociation_TypeOfOccupationId", + table: "LessonAssociation", + column: "TypeOfOccupationId"); + + migrationBuilder.CreateIndex( + name: "IX_Professor_Id", + table: "Professor", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SpecificWeek_Id", + table: "SpecificWeek", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SpecificWeek_LessonId", + table: "SpecificWeek", + column: "LessonId"); + + migrationBuilder.CreateIndex( + name: "IX_TypeOfOccupation_Id", + table: "TypeOfOccupation", + column: "Id", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LessonAssociation"); + + migrationBuilder.DropTable( + name: "SpecificWeek"); + + migrationBuilder.DropTable( + name: "LectureHall"); + + migrationBuilder.DropTable( + name: "Professor"); + + migrationBuilder.DropTable( + name: "TypeOfOccupation"); + + migrationBuilder.DropTable( + name: "Lesson"); + + migrationBuilder.DropTable( + name: "Discipline"); + + migrationBuilder.DropTable( + name: "Group"); + + migrationBuilder.DropTable( + name: "Faculty"); + + migrationBuilder.DropTable( + name: "Campus"); + } + } +} diff --git a/SqlData/Migrations/SqliteMigrations/Migrations/UberDbContextModelSnapshot.cs b/SqlData/Migrations/SqliteMigrations/Migrations/UberDbContextModelSnapshot.cs new file mode 100644 index 0000000..c463963 --- /dev/null +++ b/SqlData/Migrations/SqliteMigrations/Migrations/UberDbContextModelSnapshot.cs @@ -0,0 +1,414 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; + +#nullable disable + +namespace SqliteMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + partial class UberDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BOOLEAN"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BOOLEAN"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("Faculties") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("Faculties"); + + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} -- 2.43.0 From 827cdaf9f9b8544493f4e694cf28dce1c9d1289a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 05:43:00 +0300 Subject: [PATCH 203/474] refactor: change create database to migrate --- SqlData/Persistence/DbInitializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqlData/Persistence/DbInitializer.cs b/SqlData/Persistence/DbInitializer.cs index 87f3a9b..7bedd91 100644 --- a/SqlData/Persistence/DbInitializer.cs +++ b/SqlData/Persistence/DbInitializer.cs @@ -6,6 +6,6 @@ public static class DbInitializer { public static void Initialize(DbContext dbContext) { - dbContext.Database.EnsureCreated(); + dbContext.Database.Migrate(); } } \ No newline at end of file -- 2.43.0 From d09011d25acea1eff021e98c9af35ae273b24f78 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 06:27:49 +0300 Subject: [PATCH 204/474] feat: add create admin --- .../Controllers/Configuration/SetupController.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 7abd5ee..ac55b56 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -28,7 +28,9 @@ namespace Mirea.Api.Endpoint.Controllers.Configuration; [MaintenanceModeIgnore] public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache) : BaseController { - private const string CacheGeneralKey = "config_part"; + private const string CacheGeneralKey = "config_general"; + private const string CacheAdminKey = "config_admin"; + private GeneralConfig GeneralConfig { get => cache.Get(CacheGeneralKey) ?? new GeneralConfig(); @@ -190,6 +192,16 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur return Ok(true); } + [HttpPost("CreateAdmin")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult CreateAdmin([FromBody] CreateUserRequest user) + { + // todo: change CreateUserRequest to Domain entity + cache.Set(CacheAdminKey, user); + return Ok(true); + } + [HttpPost("SetLogging")] [TokenAuthentication] [BadRequestResponse] -- 2.43.0 From 1fd6c8657a0f3a1a1413297d6017eac40fa8287f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 06:28:13 +0300 Subject: [PATCH 205/474] fix: change connction string for mysql --- Endpoint/Controllers/Configuration/SetupController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index ac55b56..1e9bacd 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -114,11 +114,11 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur [BadRequestResponse] public ActionResult SetMysql([FromBody] DatabaseRequest request) { - string connectionString = $"Server={request.Server}:{request.Port};Uid={request.User};Database={request.Database}"; + string connectionString = $"Server={request.Server}:{request.Port};Uid={request.User};Database={request.Database};"; if (request.Password != null) - connectionString += $";Pwd={request.Password}"; + connectionString += $"Pwd={request.Password};"; if (request.Ssl) - connectionString += ";SslMode=Require;"; + connectionString += "SslMode=Require;"; return SetDatabase(connectionString, DbSettings.DatabaseEnum.Mysql); } -- 2.43.0 From 5400e0c8737704f04de2afd03c28bbe520266a90 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 06:29:16 +0300 Subject: [PATCH 206/474] feat: create submit configuration --- .../Configuration/SetupController.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 1e9bacd..bc96cb1 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -11,6 +11,7 @@ using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Settings; +using Mirea.Api.Endpoint.Configuration.General.Validators; using MySqlConnector; using Npgsql; using StackExchange.Redis; @@ -32,7 +33,7 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur private const string CacheAdminKey = "config_admin"; private GeneralConfig GeneralConfig -{ + { get => cache.Get(CacheGeneralKey) ?? new GeneralConfig(); set => cache.Set(CacheGeneralKey, value); } @@ -87,8 +88,8 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur }; GeneralConfig = general; - return Ok(true); - } + return Ok(true); + } catch (TException ex) { throw new ControllerArgumentException($"Error when connecting: {ex.Message}"); @@ -278,5 +279,31 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur return true; } + [HttpPost("Submit")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult Submit() + { + if (!new SettingsRequiredValidator(GeneralConfig).AreSettingsValid()) + throw new ControllerArgumentException("The necessary data has not been configured."); + // todo: change CreateUserRequest to Domain entity + if (!cache.TryGetValue(CacheAdminKey, out CreateUserRequest? user) || user == null) + throw new ControllerArgumentException("The administrator's data was not set."); + + if (System.IO.File.Exists(PathBuilder.Combine(GeneralConfig.FilePath))) + System.IO.File.Delete(PathBuilder.Combine(GeneralConfig.FilePath)); + + System.IO.File.WriteAllText(PathBuilder.Combine("admin.json"), JsonSerializer.Serialize(user)); + + System.IO.File.WriteAllText( + PathBuilder.Combine(GeneralConfig.FilePath), + JsonSerializer.Serialize(GeneralConfig, new JsonSerializerOptions + { + WriteIndented = true + }) + ); + + return true; + } } \ No newline at end of file -- 2.43.0 From fdf0ecc9ef43bb52c5237350668500074bbef501 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 07:25:51 +0300 Subject: [PATCH 207/474] feat: add is default path --- Endpoint/Common/Services/PathBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Common/Services/PathBuilder.cs b/Endpoint/Common/Services/PathBuilder.cs index 7df60d8..799f582 100644 --- a/Endpoint/Common/Services/PathBuilder.cs +++ b/Endpoint/Common/Services/PathBuilder.cs @@ -6,6 +6,7 @@ namespace Mirea.Api.Endpoint.Common.Services; public static class PathBuilder { + public static bool IsDefaultPath => Environment.GetEnvironmentVariable("PATH_TO_SAVE") == null; public static string PathToSave => Environment.GetEnvironmentVariable("PATH_TO_SAVE") ?? Directory.GetCurrentDirectory(); public static string Combine(params string[] paths) => Path.Combine([.. paths.Prepend(PathToSave)]); } \ No newline at end of file -- 2.43.0 From 32621515db59776b2bb20d46731eeab960a3e553 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 07:26:22 +0300 Subject: [PATCH 208/474] fix: default value is null for optional body --- Endpoint/Controllers/Configuration/SetupController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index bc96cb1..de0a0be 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -206,7 +206,7 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur [HttpPost("SetLogging")] [TokenAuthentication] [BadRequestResponse] - public ActionResult SetLogging([FromBody] LoggingRequest? request) + public ActionResult SetLogging([FromBody] LoggingRequest? request = null) { var settings = (request == null) switch { @@ -234,7 +234,7 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur [HttpPost("SetEmail")] [TokenAuthentication] [BadRequestResponse] - public ActionResult SetEmail([FromBody] EmailRequest? request) + public ActionResult SetEmail([FromBody] EmailRequest? request = null) { var settings = (request == null) switch { -- 2.43.0 From ded577f40acb67424b150fbf35cb117ec1b75d0e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 07:27:18 +0300 Subject: [PATCH 209/474] feat: add path depending on OS --- Endpoint/Controllers/Configuration/SetupController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index de0a0be..5d98699 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -213,8 +213,10 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur true => new LogSettings { EnableLogToFile = true, - LogFileName = "logging-", - LogFilePath = "logs" + LogFileName = "log-", + LogFilePath = OperatingSystem.IsWindows() || PathBuilder.IsDefaultPath ? + PathBuilder.Combine("logs") : + "/var/log/mirea" }, false => new LogSettings { -- 2.43.0 From e088374b1450c7ba27e1306fae734d7127d5490b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 07:31:14 +0300 Subject: [PATCH 210/474] feat: add request for create user --- ApiDto/Requests/CreateUserRequest.cs | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 ApiDto/Requests/CreateUserRequest.cs diff --git a/ApiDto/Requests/CreateUserRequest.cs b/ApiDto/Requests/CreateUserRequest.cs new file mode 100644 index 0000000..3bcf70c --- /dev/null +++ b/ApiDto/Requests/CreateUserRequest.cs @@ -0,0 +1,36 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Requests; + +/// +/// Request model for creating a user. +/// +public class CreateUserRequest +{ + /// + /// Gets or sets the email address of the user. + /// + /// + /// The email address is a required field. + /// + [Required] + public required string Email { get; set; } + + /// + /// Gets or sets the username of the user. + /// + /// + /// The username is a required field. + /// + [Required] + public required string Username { get; set; } + + /// + /// Gets or sets the password of the user. + /// + /// + /// The password is a required field. + /// + [Required] + public required string Password { get; set; } +} -- 2.43.0 From 63216f3b66ad1af077fa771a7e0966f04f404d99 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 07:33:08 +0300 Subject: [PATCH 211/474] feat: comment this for show controller in swagger --- Endpoint/Controllers/Configuration/SetupController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 5d98699..1c9d275 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -27,6 +27,7 @@ namespace Mirea.Api.Endpoint.Controllers.Configuration; [ApiVersion("1.0")] [ApiController] [MaintenanceModeIgnore] +[ApiExplorerSettings(IgnoreApi = true)] public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache) : BaseController { private const string CacheGeneralKey = "config_general"; -- 2.43.0 From 6fb5a83183ae58bc731e3da8c6dda823ac6e2f87 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 08:18:27 +0300 Subject: [PATCH 212/474] feat: add model to endpoint --- Endpoint/Common/Model/Admin.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Endpoint/Common/Model/Admin.cs diff --git a/Endpoint/Common/Model/Admin.cs b/Endpoint/Common/Model/Admin.cs new file mode 100644 index 0000000..59e4b60 --- /dev/null +++ b/Endpoint/Common/Model/Admin.cs @@ -0,0 +1,10 @@ +namespace Mirea.Api.Endpoint.Common.Model; + +public class Admin +{ + public const string PathToSave = "admin.json"; + public required string Username { get; set; } + public required string Email { get; set; } + public required string PasswordHash { get; set; } + public required string Salt { get; set; } +} \ No newline at end of file -- 2.43.0 From bba943173354d3aeba0f2dece4c3524ec730a015 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 08:19:26 +0300 Subject: [PATCH 213/474] feat: add data checks --- Endpoint/Controllers/Configuration/SetupController.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 1c9d275..adc1695 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -8,19 +8,23 @@ using Mirea.Api.Dto.Requests.Configuration; using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.Interfaces; +using Mirea.Api.Endpoint.Common.Model; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Settings; using Mirea.Api.Endpoint.Configuration.General.Validators; +using Mirea.Api.Security.Services; using MySqlConnector; using Npgsql; using StackExchange.Redis; using System; using System.Data; using System.IO; +using System.Net.Mail; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text.Json; +using System.Text.RegularExpressions; namespace Mirea.Api.Endpoint.Controllers.Configuration; @@ -199,8 +203,11 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur [BadRequestResponse] public ActionResult CreateAdmin([FromBody] CreateUserRequest user) { - // todo: change CreateUserRequest to Domain entity - cache.Set(CacheAdminKey, user); + if (user.Password.Length < 8 || !Regex.IsMatch(user.Password, "[A-Z]+") || !Regex.IsMatch(user.Password, "[!@#$%^&*]+")) + throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character."); + + if (!MailAddress.TryCreate(user.Email, out _)) + throw new ControllerArgumentException("The email address is incorrect."); return Ok(true); } -- 2.43.0 From 2addd2aa786fbcfb582d74376701085f58deaaee Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 08:20:27 +0300 Subject: [PATCH 214/474] feat: use Admin model --- .../Configuration/SetupController.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index adc1695..9bda318 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -32,7 +32,11 @@ namespace Mirea.Api.Endpoint.Controllers.Configuration; [ApiController] [MaintenanceModeIgnore] [ApiExplorerSettings(IgnoreApi = true)] -public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache) : BaseController +public class SetupController( + ISetupToken setupToken, + IMaintenanceModeNotConfigureService notConfigureService, + IMemoryCache cache, + PasswordHashService passwordHashService) : BaseController { private const string CacheGeneralKey = "config_general"; private const string CacheAdminKey = "config_admin"; @@ -208,6 +212,18 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur if (!MailAddress.TryCreate(user.Email, out _)) throw new ControllerArgumentException("The email address is incorrect."); + + var (salt, hash) = passwordHashService.HashPassword(user.Password); + + var admin = new Admin + { + Username = user.Username, + Email = user.Email, + PasswordHash = hash, + Salt = salt + }; + + cache.Set(CacheAdminKey, admin); return Ok(true); } @@ -297,14 +313,13 @@ public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigur if (!new SettingsRequiredValidator(GeneralConfig).AreSettingsValid()) throw new ControllerArgumentException("The necessary data has not been configured."); - // todo: change CreateUserRequest to Domain entity - if (!cache.TryGetValue(CacheAdminKey, out CreateUserRequest? user) || user == null) + if (!cache.TryGetValue(CacheAdminKey, out Admin? admin) || admin == null) throw new ControllerArgumentException("The administrator's data was not set."); if (System.IO.File.Exists(PathBuilder.Combine(GeneralConfig.FilePath))) System.IO.File.Delete(PathBuilder.Combine(GeneralConfig.FilePath)); - System.IO.File.WriteAllText(PathBuilder.Combine("admin.json"), JsonSerializer.Serialize(user)); + System.IO.File.WriteAllText(PathBuilder.Combine(Admin.PathToSave), JsonSerializer.Serialize(admin)); System.IO.File.WriteAllText( PathBuilder.Combine(GeneralConfig.FilePath), -- 2.43.0 From 34addd930fbf8be62720e5af820a5cc65f770b18 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 10:50:38 +0300 Subject: [PATCH 215/474] feat: add serilog for logging --- .../AppConfig/LoggerConfiguration.cs | 79 +++++++++++++++++++ Endpoint/Endpoint.csproj | 4 + 2 files changed, 83 insertions(+) create mode 100644 Endpoint/Configuration/AppConfig/LoggerConfiguration.cs diff --git a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs new file mode 100644 index 0000000..d10e5a4 --- /dev/null +++ b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.General; +using Serilog; +using Serilog.Events; +using Serilog.Filters; +using Serilog.Formatting.Compact; +using System.IO; + +namespace Mirea.Api.Endpoint.Configuration.AppConfig; + +public static class LoggerConfiguration +{ + public static IHostBuilder AddCustomSerilog(this IHostBuilder hostBuilder) + { + hostBuilder.UseSerilog((context, _, configuration) => + { + var generalConfig = context.Configuration.Get(); + configuration + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console( + outputTemplate: + "[{Level:u3}] [{Timestamp:dd.MM.yyyy HH:mm:ss}] {Message:lj}{NewLine}{Exception}"); + + if (generalConfig?.LogSettings?.EnableLogToFile == true) + { + if (!string.IsNullOrEmpty(generalConfig.LogSettings.LogFilePath) && Directory.Exists(PathBuilder.Combine(generalConfig.LogSettings.LogFilePath))) + Directory.CreateDirectory(generalConfig.LogSettings.LogFilePath); + + configuration.WriteTo.File( + new CompactJsonFormatter(), + PathBuilder.Combine( + generalConfig.LogSettings.LogFilePath!, + generalConfig.LogSettings.LogFileName + ".json" + ), + LogEventLevel.Debug, + rollingInterval: RollingInterval.Day); + } + + configuration + .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning); + + configuration.Filter.ByExcluding(Matching.WithProperty("SourceContext", sc => + sc.Contains("Microsoft.EntityFrameworkCore.Database.Command"))); + }); + + return hostBuilder; + } + + public static IApplicationBuilder UseCustomSerilog(this IApplicationBuilder app) + { + app.UseSerilogRequestLogging(options => + { + options.MessageTemplate = "Handled {RequestPath} in {Elapsed:0.0000} ms"; + + options.GetLevel = (_, elapsed, ex) => elapsed >= 2500 || ex != null + ? LogEventLevel.Warning + : elapsed >= 1000 + ? LogEventLevel.Information + : LogEventLevel.Debug; + + options.EnrichDiagnosticContext = (diagnosticContext, httpContext) => + { + diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value); + diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme); + diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent); + diagnosticContext.Set("RemoteIPAddress", httpContext.Connection.RemoteIpAddress?.ToString()); + }; + }); + + return app; + } +} diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index bd7ee16..26fc0a3 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -25,6 +25,10 @@ + + + + -- 2.43.0 From f3a757d33d6128d210504c9aceb56f2e1fb7d6e1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 10:53:21 +0300 Subject: [PATCH 216/474] feat: add redis --- .../AppConfig/CacheConfiguration.cs | 23 +++++++++++++++++++ Endpoint/Endpoint.csproj | 1 + 2 files changed, 24 insertions(+) create mode 100644 Endpoint/Configuration/AppConfig/CacheConfiguration.cs diff --git a/Endpoint/Configuration/AppConfig/CacheConfiguration.cs b/Endpoint/Configuration/AppConfig/CacheConfiguration.cs new file mode 100644 index 0000000..7a1954c --- /dev/null +++ b/Endpoint/Configuration/AppConfig/CacheConfiguration.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.Endpoint.Configuration.General.Settings; + +namespace Mirea.Api.Endpoint.Configuration.AppConfig; + +public static class CacheConfiguration +{ + public static IServiceCollection AddCustomRedis(this IServiceCollection services, IConfiguration configuration) + { + var cache = configuration.Get(); + if (cache?.TypeDatabase == CacheSettings.CacheEnum.Redis) + { + services.AddStackExchangeRedisCache(options => + { + options.Configuration = cache.ConnectionString; + options.InstanceName = "mirea_"; + }); + } + + return services; + } +} \ No newline at end of file diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 26fc0a3..bb33b99 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -29,6 +29,7 @@ + -- 2.43.0 From ca02509b976a578aec21c6a2205fcd082b7e294b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 10:55:43 +0300 Subject: [PATCH 217/474] build: add needed reference --- Endpoint/Endpoint.csproj | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index bb33b99..6def168 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -24,12 +24,17 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + -- 2.43.0 From 78f589bb18ac6e9673a195d73df4aec0cdf1472e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 10:57:52 +0300 Subject: [PATCH 218/474] refactor: distribute configurations by classes --- .../AppConfig/ApiVersioningConfiguration.cs | 27 +++ .../AppConfig/EnvironmentConfiguration.cs | 68 ++++++++ .../AppConfig/JwtConfiguration.cs | 66 +++++++ .../AppConfig/SecureConfiguration.cs | 26 +++ .../AppConfig/SwaggerConfiguration.cs | 48 ++++++ Endpoint/Configuration/EnvironmentManager.cs | 36 ---- .../Swagger/SwaggerExampleFilter.cs | 16 ++ Endpoint/Program.cs | 161 +++--------------- 8 files changed, 275 insertions(+), 173 deletions(-) create mode 100644 Endpoint/Configuration/AppConfig/ApiVersioningConfiguration.cs create mode 100644 Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs create mode 100644 Endpoint/Configuration/AppConfig/JwtConfiguration.cs create mode 100644 Endpoint/Configuration/AppConfig/SecureConfiguration.cs create mode 100644 Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs delete mode 100644 Endpoint/Configuration/EnvironmentManager.cs create mode 100644 Endpoint/Configuration/Swagger/SwaggerExampleFilter.cs diff --git a/Endpoint/Configuration/AppConfig/ApiVersioningConfiguration.cs b/Endpoint/Configuration/AppConfig/ApiVersioningConfiguration.cs new file mode 100644 index 0000000..dd35369 --- /dev/null +++ b/Endpoint/Configuration/AppConfig/ApiVersioningConfiguration.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.Extensions.DependencyInjection; + +namespace Mirea.Api.Endpoint.Configuration.AppConfig; + +public static class ApiVersioningConfiguration +{ + public static void AddCustomApiVersioning(this IServiceCollection services) + { + services.AddApiVersioning(options => + { + options.DefaultApiVersion = new ApiVersion(1, 0); + options.AssumeDefaultVersionWhenUnspecified = true; + options.ReportApiVersions = true; + options.ApiVersionReader = new UrlSegmentApiVersionReader(); + }); + + services.AddVersionedApiExplorer(options => + { + options.GroupNameFormat = "'v'VVV"; + options.SubstituteApiVersionInUrl = true; + }); + + services.AddEndpointsApiExplorer(); + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs new file mode 100644 index 0000000..80f2fb0 --- /dev/null +++ b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs @@ -0,0 +1,68 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Mirea.Api.Endpoint.Configuration.AppConfig; + +public static class EnvironmentConfiguration +{ + private static IDictionary LoadEnvironment(string envFile) + { + Dictionary environment = new(); + + if (!File.Exists(envFile)) return environment; + + foreach (var line in File.ReadAllLines(envFile)) + { + if (string.IsNullOrEmpty(line)) continue; + + var commentIndex = line.IndexOf('#', StringComparison.Ordinal); + + string arg = line; + + if (commentIndex != -1) + arg = arg.Remove(commentIndex, arg.Length - commentIndex); + + var parts = arg.Split( + '=', + StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length > 2) + parts = [parts[0], string.Join("=", parts[1..])]; + + if (parts.Length != 2) + continue; + + environment.Add(parts[0].Trim(), parts[1].Trim()); + } + + return environment; + } + + public static IConfigurationRoot GetEnvironment() + { + var variablesFromFile = LoadEnvironment(".env"); + + var environmentVariables = Environment.GetEnvironmentVariables() + .OfType() + .ToDictionary( + entry => entry.Key.ToString() ?? string.Empty, + entry => entry.Value?.ToString() ?? string.Empty + ); + + var result = new ConfigurationBuilder() + .AddInMemoryCollection(environmentVariables!) + .AddInMemoryCollection(variablesFromFile!); + +#if DEBUG + result.AddInMemoryCollection(LoadEnvironment(".env.develop")!); +#endif + + Environment.SetEnvironmentVariable("PATH_TO_SAVE", variablesFromFile["PATH_TO_SAVE"]); + + return result.Build(); + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/AppConfig/JwtConfiguration.cs b/Endpoint/Configuration/AppConfig/JwtConfiguration.cs new file mode 100644 index 0000000..213e244 --- /dev/null +++ b/Endpoint/Configuration/AppConfig/JwtConfiguration.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using Mirea.Api.Endpoint.Common.Services.Security; +using Mirea.Api.Security.Common.Interfaces; +using System; +using System.Text; + +namespace Mirea.Api.Endpoint.Configuration.AppConfig; + +public static class JwtConfiguration +{ + public static IServiceCollection AddJwtToken(this IServiceCollection services, IConfiguration configuration) + { + var lifeTimeJwt = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_JWT"]!)); + + var jwtDecrypt = Encoding.UTF8.GetBytes(configuration["SECURITY_ENCRYPTION_TOKEN"] ?? string.Empty); + + if (jwtDecrypt.Length != 32) + throw new InvalidOperationException("The secret token \"SECURITY_ENCRYPTION_TOKEN\" cannot be less than 32 characters long. Now the size is equal is " + jwtDecrypt.Length); + + var jwtKey = Encoding.UTF8.GetBytes(configuration["SECURITY_SIGNING_TOKEN"] ?? string.Empty); + + if (jwtKey.Length != 64) + throw new InvalidOperationException("The signature token \"SECURITY_SIGNING_TOKEN\" cannot be less than 64 characters. Now the size is " + jwtKey.Length); + + var jwtIssuer = configuration["SECURITY_JWT_ISSUER"]; + var jwtAudience = configuration["SECURITY_JWT_AUDIENCE"]; + + if (string.IsNullOrEmpty(jwtAudience) || string.IsNullOrEmpty(jwtIssuer)) + throw new InvalidOperationException("The \"SECURITY_JWT_ISSUER\" and \"SECURITY_JWT_AUDIENCE\" are not specified"); + + services.AddSingleton(_ => new JwtTokenService + { + Audience = jwtAudience, + Issuer = jwtIssuer, + Lifetime = lifeTimeJwt, + EncryptionKey = jwtDecrypt, + SigningKey = jwtKey + }); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtIssuer, + + ValidateAudience = true, + ValidAudience = jwtAudience, + + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(jwtKey), + TokenDecryptionKey = new SymmetricSecurityKey(jwtDecrypt) + }; + }); + + return services; + } +} diff --git a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs b/Endpoint/Configuration/AppConfig/SecureConfiguration.cs new file mode 100644 index 0000000..fb8cd00 --- /dev/null +++ b/Endpoint/Configuration/AppConfig/SecureConfiguration.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.Endpoint.Common.Services.Security; +using Mirea.Api.Endpoint.Configuration.General.Settings; +using Mirea.Api.Security; +using Mirea.Api.Security.Common.Interfaces; + +namespace Mirea.Api.Endpoint.Configuration.AppConfig; + +public static class SecureConfiguration +{ + public static IServiceCollection AddSecurity(this IServiceCollection services, IConfiguration configuration) + { + services.AddSecurityServices(configuration); + + services.AddSingleton(); + services.AddSingleton(); + + if (configuration.Get()?.TypeDatabase == CacheSettings.CacheEnum.Redis) + services.AddSingleton(); + else + services.AddSingleton(); + + return services; + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs new file mode 100644 index 0000000..13f8e1f --- /dev/null +++ b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Mirea.Api.Endpoint.Configuration.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.IO; + +namespace Mirea.Api.Endpoint.Configuration.AppConfig; + +public static class SwaggerConfiguration +{ + public static IServiceCollection AddCustomSwagger(this IServiceCollection services) + { + services.AddSwaggerGen(options => + { + options.SchemaFilter(); + options.OperationFilter(); + var basePath = AppDomain.CurrentDomain.BaseDirectory; + + options.IncludeXmlComments(Path.Combine(basePath, "docs.xml")); + options.IncludeXmlComments(Path.Combine(basePath, "ApiDtoDocs.xml")); + }); + + services.AddTransient, ConfigureSwaggerOptions>(); + + return services; + } + + public static IApplicationBuilder UseCustomSwagger(this IApplicationBuilder app, IServiceProvider services) + { + app.UseSwagger(); + app.UseSwaggerUI(options => + { + var provider = services.GetService(); + + foreach (var description in provider!.ApiVersionDescriptions) + { + var url = $"/swagger/{description.GroupName}/swagger.json"; + var name = description.GroupName.ToUpperInvariant(); + options.SwaggerEndpoint(url, name); + } + }); + + return app; + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/EnvironmentManager.cs b/Endpoint/Configuration/EnvironmentManager.cs deleted file mode 100644 index c21d59e..0000000 --- a/Endpoint/Configuration/EnvironmentManager.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.IO; - -namespace Mirea.Api.Endpoint.Configuration; - -internal static class EnvironmentManager -{ - public static void LoadEnvironment(string envFile) - { - if (!File.Exists(envFile)) return; - - foreach (var line in File.ReadAllLines(envFile)) - { - if (string.IsNullOrEmpty(line)) continue; - - var commentIndex = line.IndexOf('#', StringComparison.Ordinal); - - string arg = line; - - if (commentIndex != -1) - arg = arg.Remove(commentIndex, arg.Length - commentIndex); - - var parts = arg.Split( - '=', - StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length > 2) - parts = [parts[0], string.Join("=", parts[1..])]; - - if (parts.Length != 2) - continue; - - Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1].Trim()); - } - } -} \ No newline at end of file diff --git a/Endpoint/Configuration/Swagger/SwaggerExampleFilter.cs b/Endpoint/Configuration/Swagger/SwaggerExampleFilter.cs new file mode 100644 index 0000000..2af76f4 --- /dev/null +++ b/Endpoint/Configuration/Swagger/SwaggerExampleFilter.cs @@ -0,0 +1,16 @@ +using Microsoft.OpenApi.Models; +using Mirea.Api.Endpoint.Common.Attributes; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Reflection; + +namespace Mirea.Api.Endpoint.Configuration.Swagger; + +public class SwaggerExampleFilter : ISchemaFilter +{ + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + var att = context.ParameterInfo?.GetCustomAttribute(); + if (att != null) + schema.Example = new Microsoft.OpenApi.Any.OpenApiString(att.Value); + } +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 87756d9..4dab4df 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -1,173 +1,77 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; using Mirea.Api.DataAccess.Application; 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.Common.Services.Security; -using Mirea.Api.Endpoint.Configuration; +using Mirea.Api.Endpoint.Configuration.AppConfig; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Settings; using Mirea.Api.Endpoint.Configuration.General.Validators; -using Mirea.Api.Endpoint.Configuration.Swagger; using Mirea.Api.Endpoint.Middleware; -using Mirea.Api.Security.Common.Interfaces; -using Swashbuckle.AspNetCore.SwaggerGen; using System; -using System.Collections; using System.IO; -using System.Linq; -using System.Text; namespace Mirea.Api.Endpoint; public class Program { - private static IConfigurationRoot ConfigureEnvironment() + public static IServiceCollection AddDatabase(IServiceCollection services, IConfiguration configuration) { - EnvironmentManager.LoadEnvironment(".env"); - var environmentVariables = Environment.GetEnvironmentVariables() - .OfType() - .ToDictionary( - entry => entry.Key.ToString() ?? string.Empty, - entry => entry.Value?.ToString() ?? string.Empty - ); - - var result = new ConfigurationBuilder().AddInMemoryCollection(environmentVariables!); - - return result.Build(); - } - - private static IServiceCollection ConfigureJwtToken(IServiceCollection services, IConfiguration configuration) - { - var lifeTimeJwt = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_JWT"]!)); - - var jwtDecrypt = Encoding.UTF8.GetBytes(configuration["SECURITY_ENCRYPTION_TOKEN"] ?? string.Empty); - - if (jwtDecrypt.Length != 32) - throw new InvalidOperationException("The secret token \"SECURITY_ENCRYPTION_TOKEN\" cannot be less than 32 characters long. Now the size is equal is " + jwtDecrypt.Length); - - var jwtKey = Encoding.UTF8.GetBytes(configuration["SECURITY_SIGNING_TOKEN"] ?? string.Empty); - - if (jwtKey.Length != 64) - throw new InvalidOperationException("The signature token \"SECURITY_SIGNING_TOKEN\" cannot be less than 64 characters. Now the size is " + jwtKey.Length); - - var jwtIssuer = configuration["SECURITY_JWT_ISSUER"]; - var jwtAudience = configuration["SECURITY_JWT_AUDIENCE"]; - - if (string.IsNullOrEmpty(jwtAudience) || string.IsNullOrEmpty(jwtIssuer)) - throw new InvalidOperationException("The \"SECURITY_JWT_ISSUER\" and \"SECURITY_JWT_AUDIENCE\" are not specified"); - - services.AddSingleton(_ => new JwtTokenService - { - Audience = jwtAudience, - Issuer = jwtIssuer, - Lifetime = lifeTimeJwt, - EncryptionKey = jwtDecrypt, - SigningKey = jwtKey - }); - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }).AddJwtBearer(options => - { - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = jwtIssuer, - - ValidateAudience = true, - ValidAudience = jwtAudience, - - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(jwtKey), - TokenDecryptionKey = new SymmetricSecurityKey(jwtDecrypt) - }; - }); + var dbSettings = configuration.Get(); + services.AddApplication(); + services.AddPersistence( + dbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, + dbSettings?.ConnectionStringSql ?? string.Empty); return services; } - private static IServiceCollection ConfigureSecurity(IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(); - - return services; - } public static void Main(string[] args) { Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); var builder = WebApplication.CreateBuilder(args); - builder.Configuration.AddConfiguration(ConfigureEnvironment()); + builder.Configuration.AddConfiguration(EnvironmentConfiguration.GetEnvironment()); builder.Configuration.AddJsonFile(PathBuilder.Combine(GeneralConfig.FilePath), optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); - var generalConfig = builder.Configuration.Get(); - builder.Services.AddApplication(); - builder.Services.AddPersistence(generalConfig?.DbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, generalConfig?.DbSettings?.ConnectionStringSql ?? string.Empty); + builder.Host.AddCustomSerilog(); + AddDatabase(builder.Services, builder.Configuration); + builder.Services.AddControllers(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + + builder.Services.AddMemoryCache(); + builder.Services.AddCustomRedis(builder.Configuration); + builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policy => { policy.AllowAnyHeader(); policy.AllowAnyMethod(); - policy.AllowAnyOrigin(); + policy.WithOrigins("http://localhost:4200"); + policy.AllowCredentials(); }); }); - builder.Services.AddApiVersioning(options => - { - options.DefaultApiVersion = new ApiVersion(1, 0); - options.AssumeDefaultVersionWhenUnspecified = true; - options.ReportApiVersions = true; - options.ApiVersionReader = new UrlSegmentApiVersionReader(); - }); + builder.Services.AddCustomApiVersioning(); + builder.Services.AddCustomSwagger(); - builder.Services.AddVersionedApiExplorer(options => - { - options.GroupNameFormat = "'v'VVV"; - options.SubstituteApiVersionInUrl = true; - }); - - builder.Services.AddEndpointsApiExplorer(); - - builder.Services.AddSwaggerGen(options => - { - options.OperationFilter(); - var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory); - - var xmlPath = Path.Combine(basePath, "docs.xml"); - options.IncludeXmlComments(xmlPath); - }); - - builder.Services.AddTransient, ConfigureSwaggerOptions>(); + builder.Services.AddJwtToken(builder.Configuration); + builder.Services.AddSecurity(builder.Configuration); var app = builder.Build(); -#if DEBUG - // Write configurations - foreach (var item in app.Configuration.AsEnumerable()) - Console.WriteLine($"{item.Key}:{item.Value}"); -#endif + app.UseCors("AllowAll"); + app.UseCustomSerilog(); using (var scope = app.Services.CreateScope()) { @@ -184,27 +88,11 @@ public class Program maintenanceModeService.DisableMaintenanceMode(); DbInitializer.Initialize(uberDbContext); - - // todo: if admin not found } } - // Configure the HTTP request pipeline. - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(options => - { - var provider = app.Services.GetService(); + app.UseCustomSwagger(app.Services); - foreach (var description in provider!.ApiVersionDescriptions) - { - var url = $"/swagger/{description.GroupName}/swagger.json"; - var name = description.GroupName.ToUpperInvariant(); - options.SwaggerEndpoint(url, name); - } - }); - } app.UseMiddleware(); app.UseMiddleware(); @@ -212,7 +100,6 @@ public class Program app.UseAuthorization(); - app.MapControllers(); app.Run(); -- 2.43.0 From b2a0a6dd7ca493ff78b96277a3838c33d3324f63 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 10:58:11 +0300 Subject: [PATCH 219/474] feat: add default value attribute --- Endpoint/Common/Attributes/SwaggerDefaultAttribute.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Endpoint/Common/Attributes/SwaggerDefaultAttribute.cs diff --git a/Endpoint/Common/Attributes/SwaggerDefaultAttribute.cs b/Endpoint/Common/Attributes/SwaggerDefaultAttribute.cs new file mode 100644 index 0000000..4eeadfa --- /dev/null +++ b/Endpoint/Common/Attributes/SwaggerDefaultAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Mirea.Api.Endpoint.Common.Attributes; + +[AttributeUsage(AttributeTargets.Parameter)] +public class SwaggerDefaultAttribute(string value) : Attribute +{ + public string Value { get; } = value; +} \ No newline at end of file -- 2.43.0 From 9c56fa582be2d413dc202acf96052185926e2baf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 10:58:43 +0300 Subject: [PATCH 220/474] build: upgrade reference --- SqlData/Application/Application.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 51f04ad..3f82d6c 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -16,7 +16,7 @@ - + -- 2.43.0 From 2e64caf6ea5194071da6c5b0e37758ad30d840e5 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 10:59:23 +0300 Subject: [PATCH 221/474] refactor: code cleaning --- ApiDto/Responses/Schedule/GroupScheduleResponse.cs | 2 +- Endpoint/Backend.http | 6 ------ Endpoint/Configuration/General/Settings/DbSettings.cs | 4 ++-- 3 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 Endpoint/Backend.http diff --git a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs index c0a7f42..f3ee2f2 100644 --- a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs +++ b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs @@ -50,8 +50,8 @@ public class GroupScheduleInfo /// If is , then there are no specific /// /// - public bool? IsExcludedWeeks { get; set; } + /// /// The week numbers required for the correct display of the schedule. ///
diff --git a/Endpoint/Backend.http b/Endpoint/Backend.http deleted file mode 100644 index a93dbd0..0000000 --- a/Endpoint/Backend.http +++ /dev/null @@ -1,6 +0,0 @@ -@Backend_HostAddress = http://localhost:5269 - -GET {{Backend_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/Endpoint/Configuration/General/Settings/DbSettings.cs b/Endpoint/Configuration/General/Settings/DbSettings.cs index 2fa5ff9..e8a8bb1 100644 --- a/Endpoint/Configuration/General/Settings/DbSettings.cs +++ b/Endpoint/Configuration/General/Settings/DbSettings.cs @@ -1,7 +1,7 @@ -using System; -using Mirea.Api.DataAccess.Persistence.Common; +using Mirea.Api.DataAccess.Persistence.Common; using Mirea.Api.Endpoint.Configuration.General.Attributes; using Mirea.Api.Endpoint.Configuration.General.Interfaces; +using System; namespace Mirea.Api.Endpoint.Configuration.General.Settings; -- 2.43.0 From 8e58c835267a5cf5292360e4c4353b3db33b17e4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 11:01:08 +0300 Subject: [PATCH 222/474] build: ignore files --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9491a2f..f7085cc 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,6 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd +/ApiDto/ApiDtoDocs.xml +/Endpoint/docs.xml -- 2.43.0 From 6797adac4fec69fc50c420d10d8328c74d4349ea Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 11:10:42 +0300 Subject: [PATCH 223/474] fix: get GeneralConfig --- .../Configuration/AppConfig/CacheConfiguration.cs | 3 ++- .../Configuration/AppConfig/LoggerConfiguration.cs | 12 ++++++------ .../Configuration/AppConfig/SecureConfiguration.cs | 3 ++- Endpoint/Program.cs | 3 +-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Endpoint/Configuration/AppConfig/CacheConfiguration.cs b/Endpoint/Configuration/AppConfig/CacheConfiguration.cs index 7a1954c..bab6483 100644 --- a/Endpoint/Configuration/AppConfig/CacheConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/CacheConfiguration.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Settings; namespace Mirea.Api.Endpoint.Configuration.AppConfig; @@ -8,7 +9,7 @@ public static class CacheConfiguration { public static IServiceCollection AddCustomRedis(this IServiceCollection services, IConfiguration configuration) { - var cache = configuration.Get(); + var cache = configuration.Get()?.CacheSettings; if (cache?.TypeDatabase == CacheSettings.CacheEnum.Redis) { services.AddStackExchangeRedisCache(options => diff --git a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs index d10e5a4..06e9f53 100644 --- a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs @@ -17,7 +17,7 @@ public static class LoggerConfiguration { hostBuilder.UseSerilog((context, _, configuration) => { - var generalConfig = context.Configuration.Get(); + var generalConfig = context.Configuration.Get()?.LogSettings; configuration .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Information) @@ -26,16 +26,16 @@ public static class LoggerConfiguration outputTemplate: "[{Level:u3}] [{Timestamp:dd.MM.yyyy HH:mm:ss}] {Message:lj}{NewLine}{Exception}"); - if (generalConfig?.LogSettings?.EnableLogToFile == true) + if (generalConfig?.EnableLogToFile == true) { - if (!string.IsNullOrEmpty(generalConfig.LogSettings.LogFilePath) && Directory.Exists(PathBuilder.Combine(generalConfig.LogSettings.LogFilePath))) - Directory.CreateDirectory(generalConfig.LogSettings.LogFilePath); + if (!string.IsNullOrEmpty(generalConfig.LogFilePath) && Directory.Exists(PathBuilder.Combine(generalConfig.LogFilePath))) + Directory.CreateDirectory(generalConfig.LogFilePath); configuration.WriteTo.File( new CompactJsonFormatter(), PathBuilder.Combine( - generalConfig.LogSettings.LogFilePath!, - generalConfig.LogSettings.LogFileName + ".json" + generalConfig.LogFilePath!, + generalConfig.LogFileName + ".json" ), LogEventLevel.Debug, rollingInterval: RollingInterval.Day); diff --git a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs b/Endpoint/Configuration/AppConfig/SecureConfiguration.cs index fb8cd00..fd84f06 100644 --- a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SecureConfiguration.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Mirea.Api.Endpoint.Common.Services.Security; +using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Settings; using Mirea.Api.Security; using Mirea.Api.Security.Common.Interfaces; @@ -16,7 +17,7 @@ public static class SecureConfiguration services.AddSingleton(); services.AddSingleton(); - if (configuration.Get()?.TypeDatabase == CacheSettings.CacheEnum.Redis) + if (configuration.Get()?.CacheSettings?.TypeDatabase == CacheSettings.CacheEnum.Redis) services.AddSingleton(); else services.AddSingleton(); diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 4dab4df..2a9da94 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -9,7 +9,6 @@ using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.AppConfig; using Mirea.Api.Endpoint.Configuration.General; -using Mirea.Api.Endpoint.Configuration.General.Settings; using Mirea.Api.Endpoint.Configuration.General.Validators; using Mirea.Api.Endpoint.Middleware; using System; @@ -21,7 +20,7 @@ public class Program { public static IServiceCollection AddDatabase(IServiceCollection services, IConfiguration configuration) { - var dbSettings = configuration.Get(); + var dbSettings = configuration.Get()?.DbSettings; services.AddApplication(); services.AddPersistence( dbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, -- 2.43.0 From 993e66a0841c416cfcff22d05b5e98f4effc3aed Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Jun 2024 11:11:21 +0300 Subject: [PATCH 224/474] fix: add JsonIgnore to calculated property --- Endpoint/Configuration/General/Settings/DbSettings.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Endpoint/Configuration/General/Settings/DbSettings.cs b/Endpoint/Configuration/General/Settings/DbSettings.cs index e8a8bb1..09eace8 100644 --- a/Endpoint/Configuration/General/Settings/DbSettings.cs +++ b/Endpoint/Configuration/General/Settings/DbSettings.cs @@ -2,6 +2,7 @@ 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.General.Settings; @@ -17,6 +18,7 @@ public class DbSettings : IIsConfigured public DatabaseEnum TypeDatabase { get; set; } public required string ConnectionStringSql { get; set; } + [JsonIgnore] public DatabaseProvider DatabaseProvider => TypeDatabase switch { -- 2.43.0 From 4222e4702fc72f28136796412f52b8fd111f9b5b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 10 Jun 2024 21:59:34 +0300 Subject: [PATCH 225/474] refactor: provide single response for schedule --- .../Schedule/DisciplineScheduleResponse.cs | 106 ---------- .../Schedule/GroupScheduleResponse.cs | 129 ------------ .../Schedule/LectureHallScheduleResponse.cs | 128 ------------ .../Schedule/ProfessorScheduleResponse.cs | 131 ------------ .../{Schedule => }/ScheduleResponse.cs | 4 +- Endpoint/Controllers/V1/ScheduleController.cs | 186 +++--------------- 6 files changed, 33 insertions(+), 651 deletions(-) delete mode 100644 ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs delete mode 100644 ApiDto/Responses/Schedule/GroupScheduleResponse.cs delete mode 100644 ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs delete mode 100644 ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs rename ApiDto/Responses/{Schedule => }/ScheduleResponse.cs (98%) diff --git a/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs b/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs deleted file mode 100644 index ffc5578..0000000 --- a/ApiDto/Responses/Schedule/DisciplineScheduleResponse.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Mirea.Api.Dto.Responses.Schedule; - -/// -/// Represents information about a specific schedule entry for a professor. -/// -public class DisciplineScheduleInfo -{ - /// - /// Gets or sets the day of the week for the schedule entry. - /// - [Required] - public DayOfWeek DayOfWeek { get; set; } - - /// - /// Gets or sets the pair number for the schedule entry. - /// - [Required] - public int PairNumber { get; set; } - - /// - /// Gets or sets a value indicating whether the pair is on an even week. - /// - [Required] - public bool IsEven { get; set; } - - /// - /// Gets or sets the type of occupation for the schedule entry. - /// - [Required] - public required IEnumerable TypeOfOccupation { get; set; } - - /// - /// Gets or sets the names of the group for the schedule entry. - /// - [Required] - - public required string Group { get; set; } - /// - /// Gets or sets the IDs of the group for the schedule entry. - /// - [Required] - public required int GroupId { get; set; } - - /// - /// Gets or sets the names of the lecture halls for the schedule entry. - /// - public required IEnumerable LectureHalls { get; set; } - - /// - /// Gets or sets the IDs of the lecture halls for the schedule entry. - /// - public required IEnumerable LectureHallsId { get; set; } - - /// - /// Gets or sets the names of the professors for the schedule entry. - /// - public required IEnumerable Professors { get; set; } - - /// - /// Gets or sets the IDs of the professors for the schedule entry. - /// - public required IEnumerable ProfessorsId { get; set; } - - /// - /// Gets or sets the names of the campuses for the schedule entry. - /// - public required IEnumerable Campus { get; set; } - - /// - /// Gets or sets the IDs of the campuses for the schedule entry. - /// - public required IEnumerable CampusId { get; set; } - - /// - /// Gets or sets the links to online meetings for the schedule entry. - /// - public required IEnumerable LinkToMeet { get; set; } -} - -/// -/// Represents a response containing schedule information for a professor. -/// -public class DisciplineScheduleResponse -{ - /// - /// Gets or sets the name of the discipline. - /// - [Required] - public required string Discipline { get; set; } - - /// - /// Gets or sets the ID of the discipline. - /// - [Required] - public required int DisciplineId { get; set; } - - /// - /// Gets or sets the schedules for the professor. - /// - [Required] - public required IEnumerable Schedules { get; set; } -} diff --git a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs b/ApiDto/Responses/Schedule/GroupScheduleResponse.cs deleted file mode 100644 index f3ee2f2..0000000 --- a/ApiDto/Responses/Schedule/GroupScheduleResponse.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Mirea.Api.Dto.Responses.Schedule; - -/// -/// Represents information about a specific schedule entry for a group. -/// -public class GroupScheduleInfo -{ - /// - /// Gets or sets the day of the week for the schedule entry. - /// - [Required] - public DayOfWeek DayOfWeek { get; set; } - - /// - /// Gets or sets the pair number for the schedule entry. - /// - [Required] - public int PairNumber { get; set; } - - /// - /// Gets or sets a value indicating whether the pair is on an even week. - /// - [Required] - public bool IsEven { get; set; } - - /// - /// Gets or sets the name of the discipline for the schedule entry. - /// - [Required] - public required string Discipline { get; set; } - - /// - /// Gets or sets the ID of the discipline for the schedule entry. - /// - [Required] - public required int DisciplineId { get; set; } - - /// - /// Gets or sets exclude or include weeks for a specific discipline. - /// - /// - /// If is , then the values in show the weeks when there will be no discipline. - ///
- /// If is , then the values in indicate the weeks during which a particular discipline will be studied. - ///
- /// If is , then there are no specific - ///
- /// - public bool? IsExcludedWeeks { get; set; } - - /// - /// The week numbers required for the correct display of the schedule. - ///
- /// Whether there will be during the week or not depends on the property. - ///
- /// - /// To get the current week's number, use other queries. - /// - public IEnumerable? Weeks { get; set; } - - /// - /// Gets or sets the type of occupation for the schedule entry. - /// - [Required] - public required IEnumerable TypeOfOccupations { get; set; } - - /// - /// Gets or sets the names of the lecture halls for the schedule entry. - /// - public required IEnumerable LectureHalls { get; set; } - - /// - /// Gets or sets the IDs of the lecture halls for the schedule entry. - /// - public required IEnumerable LectureHallsId { get; set; } - - /// - /// Gets or sets the names of the professors for the schedule entry. - /// - public required IEnumerable Professors { get; set; } - - /// - /// Gets or sets the IDs of the professors for the schedule entry. - /// - public required IEnumerable ProfessorsId { get; set; } - - /// - /// Gets or sets the names of the campuses for the schedule entry. - /// - public required IEnumerable Campus { get; set; } - - /// - /// Gets or sets the IDs of the campuses for the schedule entry. - /// - public required IEnumerable CampusId { get; set; } - - /// - /// Gets or sets the links to online meetings for the schedule entry. - /// - public required IEnumerable LinkToMeet { get; set; } -} - -/// -/// Represents a response containing schedule information for a group. -/// -public class GroupScheduleResponse -{ - /// - /// Gets or sets the name of the group. - /// - [Required] - public required string Group { get; set; } - - /// - /// Gets or sets the ID of the group. - /// - [Required] - public required int GroupId { get; set; } - - /// - /// Gets or sets the schedules for the group. - /// - [Required] - public required IEnumerable Schedules { get; set; } -} diff --git a/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs b/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs deleted file mode 100644 index 84adb3c..0000000 --- a/ApiDto/Responses/Schedule/LectureHallScheduleResponse.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Mirea.Api.Dto.Responses.Schedule; - -/// -/// Represents information about a specific schedule entry for a lecture hall. -/// -public class LectureHallScheduleInfo -{ - /// - /// Gets or sets the day of the week for the schedule entry. - /// - [Required] - public DayOfWeek DayOfWeek { get; set; } - - /// - /// Gets or sets the pair number for the schedule entry. - /// - [Required] - public int PairNumber { get; set; } - - /// - /// Gets or sets a value indicating whether the pair is on an even week. - /// - [Required] - public bool IsEven { get; set; } - - /// - /// Gets or sets the name of the discipline for the schedule entry. - /// - [Required] - public required string Discipline { get; set; } - - /// - /// Gets or sets the ID of the discipline for the schedule entry. - /// - [Required] - public required int DisciplineId { get; set; } - - /// - /// Gets or sets exclude or include weeks for a specific discipline. - /// - /// - /// If is , then the values in show the weeks when there will be no discipline. - ///
- /// If is , then the values in indicate the weeks during which a particular discipline will be studied. - ///
- /// If is , then there are no specific - ///
- /// - - public bool? IsExcludedWeeks { get; set; } - /// - /// The week numbers required for the correct display of the schedule. - ///
- /// Whether there will be during the week or not depends on the property. - ///
- /// - /// To get the current week's number, use other queries. - /// - public IEnumerable? Weeks { get; set; } - - /// - /// Gets or sets the type of occupation for the schedule entry. - /// - [Required] - public required IEnumerable TypeOfOccupations { get; set; } - - /// - /// Gets or sets the names of the group for the schedule entry. - /// - [Required] - public required string Group { get; set; } - /// - /// Gets or sets the IDs of the group for the schedule entry. - /// - [Required] - public required int GroupId { get; set; } - - /// - /// Gets or sets the names of the campuses for the schedule entry. - /// - public required IEnumerable Campus { get; set; } - - /// - /// Gets or sets the IDs of the campuses for the schedule entry. - /// - public required IEnumerable CampusId { get; set; } - - /// - /// Gets or sets the names of the professors for the schedule entry. - /// - public required IEnumerable Professors { get; set; } - - /// - /// Gets or sets the IDs of the professors for the schedule entry. - /// - public required IEnumerable ProfessorsId { get; set; } - - /// - /// Gets or sets the links to online meetings for the schedule entry. - /// - public required IEnumerable LinkToMeet { get; set; } -} - -/// -/// Represents a response containing schedule information for a lecture hall. -/// -public class LectureHallScheduleResponse -{ - /// - /// Gets or sets the names of the lecture halls. - /// - public required string LectureHalls { get; set; } - - /// - /// Gets or sets the IDs of the lecture halls. - /// - public required int LectureHallsId { get; set; } - - /// - /// Gets or sets the schedules for the lecture hall. - /// - [Required] - public required IEnumerable Schedules { get; set; } -} diff --git a/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs b/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs deleted file mode 100644 index b25711e..0000000 --- a/ApiDto/Responses/Schedule/ProfessorScheduleResponse.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Mirea.Api.Dto.Responses.Schedule; - -/// -/// Represents information about a specific schedule entry for a professor. -/// -public class ProfessorScheduleInfo -{ - /// - /// Gets or sets the day of the week for the schedule entry. - /// - [Required] - public DayOfWeek DayOfWeek { get; set; } - - /// - /// Gets or sets the pair number for the schedule entry. - /// - [Required] - public int PairNumber { get; set; } - - /// - /// Gets or sets a value indicating whether the pair is on an even week. - /// - [Required] - public bool IsEven { get; set; } - - /// - /// Gets or sets the name of the discipline for the schedule entry. - /// - [Required] - public required string Discipline { get; set; } - - /// - /// Gets or sets the ID of the discipline for the schedule entry. - /// - [Required] - public required int DisciplineId { get; set; } - - /// - /// Gets or sets exclude or include weeks for a specific discipline. - /// - /// - /// If is , then the values in show the weeks when there will be no discipline. - ///
- /// If is , then the values in indicate the weeks during which a particular discipline will be studied. - ///
- /// If is , then there are no specific - ///
- /// - - public bool? IsExcludedWeeks { get; set; } - /// - /// The week numbers required for the correct display of the schedule. - ///
- /// Whether there will be during the week or not depends on the property. - ///
- /// - /// To get the current week's number, use other queries. - /// - public IEnumerable? Weeks { get; set; } - - /// - /// Gets or sets the type of occupation for the schedule entry. - /// - [Required] - public required IEnumerable TypeOfOccupations { get; set; } - - /// - /// Gets or sets the names of the group for the schedule entry. - /// - [Required] - - public required string Group { get; set; } - /// - /// Gets or sets the IDs of the group for the schedule entry. - /// - [Required] - public required int GroupId { get; set; } - - /// - /// Gets or sets the names of the lecture halls for the schedule entry. - /// - public required IEnumerable LectureHalls { get; set; } - - /// - /// Gets or sets the IDs of the lecture halls for the schedule entry. - /// - public required IEnumerable LectureHallsId { get; set; } - - /// - /// Gets or sets the names of the campuses for the schedule entry. - /// - public required IEnumerable Campus { get; set; } - - /// - /// Gets or sets the IDs of the campuses for the schedule entry. - /// - public required IEnumerable CampusId { get; set; } - - /// - /// Gets or sets the links to online meetings for the schedule entry. - /// - public required IEnumerable LinkToMeet { get; set; } -} - -/// -/// Represents a response containing schedule information for a professor. -/// -public class ProfessorScheduleResponse -{ - /// - /// Gets or sets the name of the professor. - /// - [Required] - public required string Professor { get; set; } - - /// - /// Gets or sets the ID of the professor. - /// - [Required] - public required int ProfessorId { get; set; } - - /// - /// Gets or sets the schedules for the professor. - /// - [Required] - public required IEnumerable Schedules { get; set; } -} diff --git a/ApiDto/Responses/Schedule/ScheduleResponse.cs b/ApiDto/Responses/ScheduleResponse.cs similarity index 98% rename from ApiDto/Responses/Schedule/ScheduleResponse.cs rename to ApiDto/Responses/ScheduleResponse.cs index 599cc3e..71462b9 100644 --- a/ApiDto/Responses/Schedule/ScheduleResponse.cs +++ b/ApiDto/Responses/ScheduleResponse.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -namespace Mirea.Api.Dto.Responses.Schedule; +namespace Mirea.Api.Dto.Responses; /// /// Represents a response object containing schedule information. @@ -50,8 +50,8 @@ public class ScheduleResponse /// If is , then there are no specific /// /// - public bool? IsExcludedWeeks { get; set; } + /// /// The week numbers required for the correct display of the schedule. ///
diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 904e3d2..81bc283 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -6,7 +6,6 @@ using Mirea.Api.DataAccess.Application.Cqrs.Schedule.Queries.GetScheduleList; using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Responses; -using Mirea.Api.Dto.Responses.Schedule; using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.General; @@ -53,7 +52,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot new ScheduleResponse() + return Ok(result.Select(s => new ScheduleResponse { DayOfWeek = s.DayOfWeek, PairNumber = s.PairNumber, @@ -84,7 +83,6 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot @@ -101,48 +99,19 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot> GetByGroup(int id, + public async Task>> GetByGroup(int id, [FromQuery] bool? isEven = null, [FromQuery] int[]? disciplines = null, [FromQuery] int[]? professors = null, - [FromQuery] int[]? lectureHalls = null) - + [FromQuery] int[]? lectureHalls = null) => + await Get(new ScheduleRequest { - var result = (await mediator.Send(new GetScheduleListQuery() - { + Disciplines = disciplines, IsEven = isEven, - DisciplineIds = disciplines, - GroupIds = [id], - LectureHallIds = lectureHalls, - ProfessorIds = professors - })).Schedules; - - if (result.Count == 0) NoContent(); - - return Ok(new GroupScheduleResponse() - { - Group = result[0].Group, - GroupId = result[0].GroupId, - Schedules = result.Select(g => new GroupScheduleInfo() - { - DayOfWeek = g.DayOfWeek, - PairNumber = g.PairNumber, - IsEven = g.IsEven, - Discipline = g.Discipline, - DisciplineId = g.DisciplineId, - IsExcludedWeeks = g.IsExcludedWeeks, - Weeks = g.Weeks, - TypeOfOccupations = g.TypeOfOccupations, - LectureHalls = g.LectureHalls, - LectureHallsId = g.LectureHallsId, - Professors = g.Professors, - ProfessorsId = g.ProfessorsId, - Campus = g.Campus, - CampusId = g.CampusId, - LinkToMeet = g.LinkToMeet - }) + Groups = [id], + Professors = professors, + LectureHalls = lectureHalls }); - } /// /// Retrieves schedules for a specific professor based on various filters. @@ -158,52 +127,19 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot> GetByProfessor(int id, + public async Task>> GetByProfessor(int id, [FromQuery] bool? isEven = null, [FromQuery] int[]? disciplines = null, [FromQuery] int[]? groups = null, - [FromQuery] int[]? lectureHalls = null) - + [FromQuery] int[]? lectureHalls = null) => + await Get(new ScheduleRequest { - var result = (await mediator.Send(new GetScheduleListQuery() - { + Disciplines = disciplines, IsEven = isEven, - DisciplineIds = disciplines, - GroupIds = groups, - LectureHallIds = lectureHalls, - ProfessorIds = [id] - })).Schedules; - - if (result.Count == 0) NoContent(); - - return Ok(new ProfessorScheduleResponse() - { - Professor = result.Select(professor => - professor.Professors.FirstOrDefault(x => !string.IsNullOrEmpty(x)) - ).First()!, - ProfessorId = result.Select(professor => - professor.ProfessorsId.FirstOrDefault(x => x != null) - ).First()!.Value, - Schedules = result.Select(p => new ProfessorScheduleInfo() - { - DayOfWeek = p.DayOfWeek, - PairNumber = p.PairNumber, - IsEven = p.IsEven, - Discipline = p.Discipline, - DisciplineId = p.DisciplineId, - IsExcludedWeeks = p.IsExcludedWeeks, - Weeks = p.Weeks, - TypeOfOccupations = p.TypeOfOccupations, - Group = p.Group, - GroupId = p.GroupId, - LectureHalls = p.LectureHalls, - LectureHallsId = p.LectureHallsId, - Campus = p.Campus, - CampusId = p.CampusId, - LinkToMeet = p.LinkToMeet - }) + Groups = groups, + Professors = [id], + LectureHalls = lectureHalls }); - } /// /// Retrieves schedules for a specific lecture hall based on various filters. @@ -219,52 +155,19 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot> GetByLectureHall(int id, + public async Task>> GetByLectureHall(int id, [FromQuery] bool? isEven = null, [FromQuery] int[]? disciplines = null, [FromQuery] int[]? groups = null, - [FromQuery] int[]? professors = null) - + [FromQuery] int[]? professors = null) => + await Get(new ScheduleRequest { - var result = (await mediator.Send(new GetScheduleListQuery() - { + Disciplines = disciplines, IsEven = isEven, - DisciplineIds = disciplines, - GroupIds = groups, - LectureHallIds = [id], - ProfessorIds = professors - })).Schedules; - - if (result.Count == 0) NoContent(); - - return Ok(new LectureHallScheduleResponse() - { - LectureHalls = result.Select(lectureHall => - lectureHall.LectureHalls.FirstOrDefault(x => !string.IsNullOrEmpty(x)) - ).First()!, - LectureHallsId = result.Select(lectureHall => - lectureHall.LectureHallsId.FirstOrDefault(x => x != null) - ).First()!.Value, - Schedules = result.Select(l => new LectureHallScheduleInfo() - { - DayOfWeek = l.DayOfWeek, - PairNumber = l.PairNumber, - IsEven = l.IsEven, - Discipline = l.Discipline, - DisciplineId = l.DisciplineId, - IsExcludedWeeks = l.IsExcludedWeeks, - Weeks = l.Weeks, - TypeOfOccupations = l.TypeOfOccupations, - Group = l.Group, - GroupId = l.GroupId, - Professors = l.Professors, - ProfessorsId = l.ProfessorsId, - Campus = l.Campus, - CampusId = l.CampusId, - LinkToMeet = l.LinkToMeet - }) + Groups = groups, + Professors = professors, + LectureHalls = [id] }); - } /// /// Retrieves schedules for a specific discipline based on various filters. @@ -280,44 +183,17 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot> GetByDiscipline(int id, + public async Task>> GetByDiscipline(int id, [FromQuery] bool? isEven = null, [FromQuery] int[]? groups = null, [FromQuery] int[]? professors = null, - [FromQuery] int[]? lectureHalls = null) - + [FromQuery] int[]? lectureHalls = null) => + await Get(new ScheduleRequest { - var result = (await mediator.Send(new GetScheduleListQuery() - { + Disciplines = [id], IsEven = isEven, - DisciplineIds = [id], - GroupIds = groups, - LectureHallIds = lectureHalls, - ProfessorIds = professors - })).Schedules; - - if (result.Count == 0) NoContent(); - - return Ok(new DisciplineScheduleResponse() - { - Discipline = result[0].Discipline, - DisciplineId = result[0].DisciplineId, - Schedules = result.Select(d => new DisciplineScheduleInfo() - { - DayOfWeek = d.DayOfWeek, - PairNumber = d.PairNumber, - IsEven = d.IsEven, - TypeOfOccupation = d.TypeOfOccupations, - Group = d.Group, - GroupId = d.GroupId, - LectureHalls = d.LectureHalls, - LectureHallsId = d.LectureHallsId, - Professors = d.Professors, - ProfessorsId = d.ProfessorsId, - Campus = d.Campus, - CampusId = d.CampusId, - LinkToMeet = d.LinkToMeet - }) + Groups = groups, + Professors = professors, + LectureHalls = lectureHalls }); - } -} \ No newline at end of file + } \ No newline at end of file -- 2.43.0 From b64090277722fc82430565b2587efa125f7c4ff1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 10 Jun 2024 22:01:58 +0300 Subject: [PATCH 226/474] feat: add dark theme for swagger --- .../AppConfig/SwaggerConfiguration.cs | 1 + Endpoint/Program.cs | 1 + Endpoint/wwwroot/css/swagger/SwaggerDark.css | 849 ++++++++++++++++++ 3 files changed, 851 insertions(+) create mode 100644 Endpoint/wwwroot/css/swagger/SwaggerDark.css diff --git a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs index 13f8e1f..c43c730 100644 --- a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs @@ -33,6 +33,7 @@ public static class SwaggerConfiguration app.UseSwagger(); app.UseSwaggerUI(options => { + options.InjectStylesheet("/css/swagger/SwaggerDark.css"); var provider = services.GetService(); foreach (var description in provider!.ApiVersionDescriptions) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 2a9da94..cf016f4 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -69,6 +69,7 @@ public class Program var app = builder.Build(); + app.UseStaticFiles(); app.UseCors("AllowAll"); app.UseCustomSerilog(); diff --git a/Endpoint/wwwroot/css/swagger/SwaggerDark.css b/Endpoint/wwwroot/css/swagger/SwaggerDark.css new file mode 100644 index 0000000..cfc4a49 --- /dev/null +++ b/Endpoint/wwwroot/css/swagger/SwaggerDark.css @@ -0,0 +1,849 @@ +@media only screen and (prefers-color-scheme: dark) { + + a { color: #8c8cfa; } + + ::-webkit-scrollbar-track-piece { background-color: rgba(255, 255, 255, .2) !important; } + + ::-webkit-scrollbar-track { background-color: rgba(255, 255, 255, .3) !important; } + + ::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, .5) !important; } + + embed[type="application/pdf"] { filter: invert(90%); } + + html { + background: #1f1f1f !important; + box-sizing: border-box; + filter: contrast(100%) brightness(100%) saturate(100%); + overflow-y: scroll; + } + + body { + background: #1f1f1f; + background-color: #1f1f1f; + background-image: none !important; + } + + button, input, select, textarea { + background-color: #1f1f1f; + color: #bfbfbf; + } + + font, html { color: #bfbfbf; } + + .swagger-ui, .swagger-ui section h3 { color: #b5bac9; } + + .swagger-ui a { background-color: transparent; } + + .swagger-ui mark { + background-color: #664b00; + color: #bfbfbf; + } + + .swagger-ui legend { color: inherit; } + + .swagger-ui .debug * { outline: #e6da99 solid 1px; } + + .swagger-ui .debug-white * { outline: #fff solid 1px; } + + .swagger-ui .debug-black * { outline: #bfbfbf solid 1px; } + + .swagger-ui .debug-grid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) 0 0; } + + .swagger-ui .debug-grid-16 { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) 0 0; } + + .swagger-ui .debug-grid-8-solid { background: url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) 0 0 #1c1c21; } + + .swagger-ui .debug-grid-16-solid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) 0 0 #1c1c21; } + + .swagger-ui .b--black { border-color: #000; } + + .swagger-ui .b--near-black { border-color: #121212; } + + .swagger-ui .b--dark-gray { border-color: #333; } + + .swagger-ui .b--mid-gray { border-color: #545454; } + + .swagger-ui .b--gray { border-color: #787878; } + + .swagger-ui .b--silver { border-color: #999; } + + .swagger-ui .b--light-silver { border-color: #6e6e6e; } + + .swagger-ui .b--moon-gray { border-color: #4d4d4d; } + + .swagger-ui .b--light-gray { border-color: #2b2b2b; } + + .swagger-ui .b--near-white { border-color: #242424; } + + .swagger-ui .b--white { border-color: #1c1c21; } + + .swagger-ui .b--white-90 { border-color: rgba(28, 28, 33, .9); } + + .swagger-ui .b--white-80 { border-color: rgba(28, 28, 33, .8); } + + .swagger-ui .b--white-70 { border-color: rgba(28, 28, 33, .7); } + + .swagger-ui .b--white-60 { border-color: rgba(28, 28, 33, .6); } + + .swagger-ui .b--white-50 { border-color: rgba(28, 28, 33, .5); } + + .swagger-ui .b--white-40 { border-color: rgba(28, 28, 33, .4); } + + .swagger-ui .b--white-30 { border-color: rgba(28, 28, 33, .3); } + + .swagger-ui .b--white-20 { border-color: rgba(28, 28, 33, .2); } + + .swagger-ui .b--white-10 { border-color: rgba(28, 28, 33, .1); } + + .swagger-ui .b--white-05 { border-color: rgba(28, 28, 33, .05); } + + .swagger-ui .b--white-025 { border-color: rgba(28, 28, 33, .024); } + + .swagger-ui .b--white-0125 { border-color: rgba(28, 28, 33, .01); } + + .swagger-ui .b--black-90 { border-color: rgba(0, 0, 0, .9); } + + .swagger-ui .b--black-80 { border-color: rgba(0, 0, 0, .8); } + + .swagger-ui .b--black-70 { border-color: rgba(0, 0, 0, .7); } + + .swagger-ui .b--black-60 { border-color: rgba(0, 0, 0, .6); } + + .swagger-ui .b--black-50 { border-color: rgba(0, 0, 0, .5); } + + .swagger-ui .b--black-40 { border-color: rgba(0, 0, 0, .4); } + + .swagger-ui .b--black-30 { border-color: rgba(0, 0, 0, .3); } + + .swagger-ui .b--black-20 { border-color: rgba(0, 0, 0, .2); } + + .swagger-ui .b--black-10 { border-color: rgba(0, 0, 0, .1); } + + .swagger-ui .b--black-05 { border-color: rgba(0, 0, 0, .05); } + + .swagger-ui .b--black-025 { border-color: rgba(0, 0, 0, .024); } + + .swagger-ui .b--black-0125 { border-color: rgba(0, 0, 0, .01); } + + .swagger-ui .b--dark-red { border-color: #bc2f36; } + + .swagger-ui .b--red { border-color: #c83932; } + + .swagger-ui .b--light-red { border-color: #ab3c2b; } + + .swagger-ui .b--orange { border-color: #cc6e33; } + + .swagger-ui .b--purple { border-color: #5e2ca5; } + + .swagger-ui .b--light-purple { border-color: #672caf; } + + .swagger-ui .b--dark-pink { border-color: #ab2b81; } + + .swagger-ui .b--hot-pink { border-color: #c03086; } + + .swagger-ui .b--pink { border-color: #8f2464; } + + .swagger-ui .b--light-pink { border-color: #721d4d; } + + .swagger-ui .b--dark-green { border-color: #1c6e50; } + + .swagger-ui .b--green { border-color: #279b70; } + + .swagger-ui .b--light-green { border-color: #228762; } + + .swagger-ui .b--navy { border-color: #0d1d35; } + + .swagger-ui .b--dark-blue { border-color: #20497e; } + + .swagger-ui .b--blue { border-color: #4380d0; } + + .swagger-ui .b--light-blue { border-color: #20517e; } + + .swagger-ui .b--lightest-blue { border-color: #143a52; } + + .swagger-ui .b--washed-blue { border-color: #0c312d; } + + .swagger-ui .b--washed-green { border-color: #0f3d2c; } + + .swagger-ui .b--washed-red { border-color: #411010; } + + .swagger-ui .b--transparent { border-color: transparent; } + + .swagger-ui .b--gold, .swagger-ui .b--light-yellow, .swagger-ui .b--washed-yellow, .swagger-ui .b--yellow { border-color: #664b00; } + + .swagger-ui .shadow-1 { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } + + .swagger-ui .shadow-2 { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } + + .swagger-ui .shadow-3 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } + + .swagger-ui .shadow-4 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } + + .swagger-ui .shadow-5 { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } + + @media screen and (min-width: 30em) { + .swagger-ui .shadow-1-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } + + .swagger-ui .shadow-2-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } + + .swagger-ui .shadow-3-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } + + .swagger-ui .shadow-4-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } + + .swagger-ui .shadow-5-ns { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } + } + + @media screen and (max-width: 60em) and (min-width: 30em) { + .swagger-ui .shadow-1-m { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } + + .swagger-ui .shadow-2-m { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } + + .swagger-ui .shadow-3-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } + + .swagger-ui .shadow-4-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } + + .swagger-ui .shadow-5-m { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } + } + + @media screen and (min-width: 60em) { + .swagger-ui .shadow-1-l { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } + + .swagger-ui .shadow-2-l { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } + + .swagger-ui .shadow-3-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } + + .swagger-ui .shadow-4-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } + + .swagger-ui .shadow-5-l { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } + } + + .swagger-ui .black-05 { color: rgba(191, 191, 191, .05); } + + .swagger-ui .bg-black-05 { background-color: rgba(0, 0, 0, .05); } + + .swagger-ui .black-90, .swagger-ui .hover-black-90:focus, .swagger-ui .hover-black-90:hover { color: rgba(191, 191, 191, .9); } + + .swagger-ui .black-80, .swagger-ui .hover-black-80:focus, .swagger-ui .hover-black-80:hover { color: rgba(191, 191, 191, .8); } + + .swagger-ui .black-70, .swagger-ui .hover-black-70:focus, .swagger-ui .hover-black-70:hover { color: rgba(191, 191, 191, .7); } + + .swagger-ui .black-60, .swagger-ui .hover-black-60:focus, .swagger-ui .hover-black-60:hover { color: rgba(191, 191, 191, .6); } + + .swagger-ui .black-50, .swagger-ui .hover-black-50:focus, .swagger-ui .hover-black-50:hover { color: rgba(191, 191, 191, .5); } + + .swagger-ui .black-40, .swagger-ui .hover-black-40:focus, .swagger-ui .hover-black-40:hover { color: rgba(191, 191, 191, .4); } + + .swagger-ui .black-30, .swagger-ui .hover-black-30:focus, .swagger-ui .hover-black-30:hover { color: rgba(191, 191, 191, .3); } + + .swagger-ui .black-20, .swagger-ui .hover-black-20:focus, .swagger-ui .hover-black-20:hover { color: rgba(191, 191, 191, .2); } + + .swagger-ui .black-10, .swagger-ui .hover-black-10:focus, .swagger-ui .hover-black-10:hover { color: rgba(191, 191, 191, .1); } + + .swagger-ui .hover-white-90:focus, .swagger-ui .hover-white-90:hover, .swagger-ui .white-90 { color: rgba(255, 255, 255, .9); } + + .swagger-ui .hover-white-80:focus, .swagger-ui .hover-white-80:hover, .swagger-ui .white-80 { color: rgba(255, 255, 255, .8); } + + .swagger-ui .hover-white-70:focus, .swagger-ui .hover-white-70:hover, .swagger-ui .white-70 { color: rgba(255, 255, 255, .7); } + + .swagger-ui .hover-white-60:focus, .swagger-ui .hover-white-60:hover, .swagger-ui .white-60 { color: rgba(255, 255, 255, .6); } + + .swagger-ui .hover-white-50:focus, .swagger-ui .hover-white-50:hover, .swagger-ui .white-50 { color: rgba(255, 255, 255, .5); } + + .swagger-ui .hover-white-40:focus, .swagger-ui .hover-white-40:hover, .swagger-ui .white-40 { color: rgba(255, 255, 255, .4); } + + .swagger-ui .hover-white-30:focus, .swagger-ui .hover-white-30:hover, .swagger-ui .white-30 { color: rgba(255, 255, 255, .3); } + + .swagger-ui .hover-white-20:focus, .swagger-ui .hover-white-20:hover, .swagger-ui .white-20 { color: rgba(255, 255, 255, .2); } + + .swagger-ui .hover-white-10:focus, .swagger-ui .hover-white-10:hover, .swagger-ui .white-10 { color: rgba(255, 255, 255, .1); } + + .swagger-ui .hover-moon-gray:focus, .swagger-ui .hover-moon-gray:hover, .swagger-ui .moon-gray { color: #ccc; } + + .swagger-ui .hover-light-gray:focus, .swagger-ui .hover-light-gray:hover, .swagger-ui .light-gray { color: #ededed; } + + .swagger-ui .hover-near-white:focus, .swagger-ui .hover-near-white:hover, .swagger-ui .near-white { color: #f5f5f5; } + + .swagger-ui .dark-red, .swagger-ui .hover-dark-red:focus, .swagger-ui .hover-dark-red:hover { color: #e6999d; } + + .swagger-ui .hover-red:focus, .swagger-ui .hover-red:hover, .swagger-ui .red { color: #e69d99; } + + .swagger-ui .hover-light-red:focus, .swagger-ui .hover-light-red:hover, .swagger-ui .light-red { color: #e6a399; } + + .swagger-ui .hover-orange:focus, .swagger-ui .hover-orange:hover, .swagger-ui .orange { color: #e6b699; } + + .swagger-ui .gold, .swagger-ui .hover-gold:focus, .swagger-ui .hover-gold:hover { color: #e6d099; } + + .swagger-ui .hover-yellow:focus, .swagger-ui .hover-yellow:hover, .swagger-ui .yellow { color: #e6da99; } + + .swagger-ui .hover-light-yellow:focus, .swagger-ui .hover-light-yellow:hover, .swagger-ui .light-yellow { color: #ede6b6; } + + .swagger-ui .hover-purple:focus, .swagger-ui .hover-purple:hover, .swagger-ui .purple { color: #b99ae4; } + + .swagger-ui .hover-light-purple:focus, .swagger-ui .hover-light-purple:hover, .swagger-ui .light-purple { color: #bb99e6; } + + .swagger-ui .dark-pink, .swagger-ui .hover-dark-pink:focus, .swagger-ui .hover-dark-pink:hover { color: #e699cc; } + + .swagger-ui .hot-pink, .swagger-ui .hover-hot-pink:focus, .swagger-ui .hover-hot-pink:hover, .swagger-ui .hover-pink:focus, .swagger-ui .hover-pink:hover, .swagger-ui .pink { color: #e699c7; } + + .swagger-ui .hover-light-pink:focus, .swagger-ui .hover-light-pink:hover, .swagger-ui .light-pink { color: #edb6d5; } + + .swagger-ui .dark-green, .swagger-ui .green, .swagger-ui .hover-dark-green:focus, .swagger-ui .hover-dark-green:hover, .swagger-ui .hover-green:focus, .swagger-ui .hover-green:hover { color: #99e6c9; } + + .swagger-ui .hover-light-green:focus, .swagger-ui .hover-light-green:hover, .swagger-ui .light-green { color: #a1e8ce; } + + .swagger-ui .hover-navy:focus, .swagger-ui .hover-navy:hover, .swagger-ui .navy { color: #99b8e6; } + + .swagger-ui .blue, .swagger-ui .dark-blue, .swagger-ui .hover-blue:focus, .swagger-ui .hover-blue:hover, .swagger-ui .hover-dark-blue:focus, .swagger-ui .hover-dark-blue:hover { color: #99bae6; } + + .swagger-ui .hover-light-blue:focus, .swagger-ui .hover-light-blue:hover, .swagger-ui .light-blue { color: #a9cbea; } + + .swagger-ui .hover-lightest-blue:focus, .swagger-ui .hover-lightest-blue:hover, .swagger-ui .lightest-blue { color: #d6e9f5; } + + .swagger-ui .hover-washed-blue:focus, .swagger-ui .hover-washed-blue:hover, .swagger-ui .washed-blue { color: #f7fdfc; } + + .swagger-ui .hover-washed-green:focus, .swagger-ui .hover-washed-green:hover, .swagger-ui .washed-green { color: #ebfaf4; } + + .swagger-ui .hover-washed-yellow:focus, .swagger-ui .hover-washed-yellow:hover, .swagger-ui .washed-yellow { color: #fbf9ef; } + + .swagger-ui .hover-washed-red:focus, .swagger-ui .hover-washed-red:hover, .swagger-ui .washed-red { color: #f9e7e7; } + + .swagger-ui .color-inherit, .swagger-ui .hover-inherit:focus, .swagger-ui .hover-inherit:hover { color: inherit; } + + .swagger-ui .bg-black-90, .swagger-ui .hover-bg-black-90:focus, .swagger-ui .hover-bg-black-90:hover { background-color: rgba(0, 0, 0, .9); } + + .swagger-ui .bg-black-80, .swagger-ui .hover-bg-black-80:focus, .swagger-ui .hover-bg-black-80:hover { background-color: rgba(0, 0, 0, .8); } + + .swagger-ui .bg-black-70, .swagger-ui .hover-bg-black-70:focus, .swagger-ui .hover-bg-black-70:hover { background-color: rgba(0, 0, 0, .7); } + + .swagger-ui .bg-black-60, .swagger-ui .hover-bg-black-60:focus, .swagger-ui .hover-bg-black-60:hover { background-color: rgba(0, 0, 0, .6); } + + .swagger-ui .bg-black-50, .swagger-ui .hover-bg-black-50:focus, .swagger-ui .hover-bg-black-50:hover { background-color: rgba(0, 0, 0, .5); } + + .swagger-ui .bg-black-40, .swagger-ui .hover-bg-black-40:focus, .swagger-ui .hover-bg-black-40:hover { background-color: rgba(0, 0, 0, .4); } + + .swagger-ui .bg-black-30, .swagger-ui .hover-bg-black-30:focus, .swagger-ui .hover-bg-black-30:hover { background-color: rgba(0, 0, 0, .3); } + + .swagger-ui .bg-black-20, .swagger-ui .hover-bg-black-20:focus, .swagger-ui .hover-bg-black-20:hover { background-color: rgba(0, 0, 0, .2); } + + .swagger-ui .bg-white-90, .swagger-ui .hover-bg-white-90:focus, .swagger-ui .hover-bg-white-90:hover { background-color: rgba(28, 28, 33, .9); } + + .swagger-ui .bg-white-80, .swagger-ui .hover-bg-white-80:focus, .swagger-ui .hover-bg-white-80:hover { background-color: rgba(28, 28, 33, .8); } + + .swagger-ui .bg-white-70, .swagger-ui .hover-bg-white-70:focus, .swagger-ui .hover-bg-white-70:hover { background-color: rgba(28, 28, 33, .7); } + + .swagger-ui .bg-white-60, .swagger-ui .hover-bg-white-60:focus, .swagger-ui .hover-bg-white-60:hover { background-color: rgba(28, 28, 33, .6); } + + .swagger-ui .bg-white-50, .swagger-ui .hover-bg-white-50:focus, .swagger-ui .hover-bg-white-50:hover { background-color: rgba(28, 28, 33, .5); } + + .swagger-ui .bg-white-40, .swagger-ui .hover-bg-white-40:focus, .swagger-ui .hover-bg-white-40:hover { background-color: rgba(28, 28, 33, .4); } + + .swagger-ui .bg-white-30, .swagger-ui .hover-bg-white-30:focus, .swagger-ui .hover-bg-white-30:hover { background-color: rgba(28, 28, 33, .3); } + + .swagger-ui .bg-white-20, .swagger-ui .hover-bg-white-20:focus, .swagger-ui .hover-bg-white-20:hover { background-color: rgba(28, 28, 33, .2); } + + .swagger-ui .bg-black, .swagger-ui .hover-bg-black:focus, .swagger-ui .hover-bg-black:hover { background-color: #000; } + + .swagger-ui .bg-near-black, .swagger-ui .hover-bg-near-black:focus, .swagger-ui .hover-bg-near-black:hover { background-color: #121212; } + + .swagger-ui .bg-dark-gray, .swagger-ui .hover-bg-dark-gray:focus, .swagger-ui .hover-bg-dark-gray:hover { background-color: #333; } + + .swagger-ui .bg-mid-gray, .swagger-ui .hover-bg-mid-gray:focus, .swagger-ui .hover-bg-mid-gray:hover { background-color: #545454; } + + .swagger-ui .bg-gray, .swagger-ui .hover-bg-gray:focus, .swagger-ui .hover-bg-gray:hover { background-color: #787878; } + + .swagger-ui .bg-silver, .swagger-ui .hover-bg-silver:focus, .swagger-ui .hover-bg-silver:hover { background-color: #999; } + + .swagger-ui .bg-white, .swagger-ui .hover-bg-white:focus, .swagger-ui .hover-bg-white:hover { background-color: #1c1c21; } + + .swagger-ui .bg-transparent, .swagger-ui .hover-bg-transparent:focus, .swagger-ui .hover-bg-transparent:hover { background-color: transparent; } + + .swagger-ui .bg-dark-red, .swagger-ui .hover-bg-dark-red:focus, .swagger-ui .hover-bg-dark-red:hover { background-color: #bc2f36; } + + .swagger-ui .bg-red, .swagger-ui .hover-bg-red:focus, .swagger-ui .hover-bg-red:hover { background-color: #c83932; } + + .swagger-ui .bg-light-red, .swagger-ui .hover-bg-light-red:focus, .swagger-ui .hover-bg-light-red:hover { background-color: #ab3c2b; } + + .swagger-ui .bg-orange, .swagger-ui .hover-bg-orange:focus, .swagger-ui .hover-bg-orange:hover { background-color: #cc6e33; } + + .swagger-ui .bg-gold, .swagger-ui .bg-light-yellow, .swagger-ui .bg-washed-yellow, .swagger-ui .bg-yellow, .swagger-ui .hover-bg-gold:focus, .swagger-ui .hover-bg-gold:hover, .swagger-ui .hover-bg-light-yellow:focus, .swagger-ui .hover-bg-light-yellow:hover, .swagger-ui .hover-bg-washed-yellow:focus, .swagger-ui .hover-bg-washed-yellow:hover, .swagger-ui .hover-bg-yellow:focus, .swagger-ui .hover-bg-yellow:hover { background-color: #664b00; } + + .swagger-ui .bg-purple, .swagger-ui .hover-bg-purple:focus, .swagger-ui .hover-bg-purple:hover { background-color: #5e2ca5; } + + .swagger-ui .bg-light-purple, .swagger-ui .hover-bg-light-purple:focus, .swagger-ui .hover-bg-light-purple:hover { background-color: #672caf; } + + .swagger-ui .bg-dark-pink, .swagger-ui .hover-bg-dark-pink:focus, .swagger-ui .hover-bg-dark-pink:hover { background-color: #ab2b81; } + + .swagger-ui .bg-hot-pink, .swagger-ui .hover-bg-hot-pink:focus, .swagger-ui .hover-bg-hot-pink:hover { background-color: #c03086; } + + .swagger-ui .bg-pink, .swagger-ui .hover-bg-pink:focus, .swagger-ui .hover-bg-pink:hover { background-color: #8f2464; } + + .swagger-ui .bg-light-pink, .swagger-ui .hover-bg-light-pink:focus, .swagger-ui .hover-bg-light-pink:hover { background-color: #721d4d; } + + .swagger-ui .bg-dark-green, .swagger-ui .hover-bg-dark-green:focus, .swagger-ui .hover-bg-dark-green:hover { background-color: #1c6e50; } + + .swagger-ui .bg-green, .swagger-ui .hover-bg-green:focus, .swagger-ui .hover-bg-green:hover { background-color: #279b70; } + + .swagger-ui .bg-light-green, .swagger-ui .hover-bg-light-green:focus, .swagger-ui .hover-bg-light-green:hover { background-color: #228762; } + + .swagger-ui .bg-navy, .swagger-ui .hover-bg-navy:focus, .swagger-ui .hover-bg-navy:hover { background-color: #0d1d35; } + + .swagger-ui .bg-dark-blue, .swagger-ui .hover-bg-dark-blue:focus, .swagger-ui .hover-bg-dark-blue:hover { background-color: #20497e; } + + .swagger-ui .bg-blue, .swagger-ui .hover-bg-blue:focus, .swagger-ui .hover-bg-blue:hover { background-color: #4380d0; } + + .swagger-ui .bg-light-blue, .swagger-ui .hover-bg-light-blue:focus, .swagger-ui .hover-bg-light-blue:hover { background-color: #20517e; } + + .swagger-ui .bg-lightest-blue, .swagger-ui .hover-bg-lightest-blue:focus, .swagger-ui .hover-bg-lightest-blue:hover { background-color: #143a52; } + + .swagger-ui .bg-washed-blue, .swagger-ui .hover-bg-washed-blue:focus, .swagger-ui .hover-bg-washed-blue:hover { background-color: #0c312d; } + + .swagger-ui .bg-washed-green, .swagger-ui .hover-bg-washed-green:focus, .swagger-ui .hover-bg-washed-green:hover { background-color: #0f3d2c; } + + .swagger-ui .bg-washed-red, .swagger-ui .hover-bg-washed-red:focus, .swagger-ui .hover-bg-washed-red:hover { background-color: #411010; } + + .swagger-ui .bg-inherit, .swagger-ui .hover-bg-inherit:focus, .swagger-ui .hover-bg-inherit:hover { background-color: inherit; } + + .swagger-ui .shadow-hover { transition: all .5s cubic-bezier(.165, .84, .44, 1) 0s; } + + .swagger-ui .shadow-hover::after { + border-radius: inherit; + box-shadow: rgba(0, 0, 0, .2) 0 0 16px 2px; + content: ""; + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + transition: opacity .5s cubic-bezier(.165, .84, .44, 1) 0s; + width: 100%; + z-index: -1; + } + + .swagger-ui .bg-animate, .swagger-ui .bg-animate:focus, .swagger-ui .bg-animate:hover { transition: background-color .15s ease-in-out 0s; } + + .swagger-ui .nested-links a { + color: #99bae6; + transition: color .15s ease-in 0s; + } + + .swagger-ui .nested-links a:focus, .swagger-ui .nested-links a:hover { + color: #a9cbea; + transition: color .15s ease-in 0s; + } + + .swagger-ui .opblock-tag { + border-bottom: 1px solid rgba(58, 64, 80, .3); + color: #b5bac9; + transition: all .2s ease 0s; + } + + .swagger-ui .opblock-tag svg, .swagger-ui section.models h4 svg { transition: all .4s ease 0s; } + + .swagger-ui .opblock { + border: 1px solid #000; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, .19) 0 0 3px; + margin: 0 0 15px; + } + + .swagger-ui .opblock .tab-header .tab-item.active h4 span::after { background: gray; } + + .swagger-ui .opblock.is-open .opblock-summary { border-bottom: 1px solid #000; } + + .swagger-ui .opblock .opblock-section-header { + background: rgba(28, 28, 33, .8); + box-shadow: rgba(0, 0, 0, .1) 0 1px 2px; + } + + .swagger-ui .opblock .opblock-section-header > label > span { padding: 0 10px 0 0; } + + .swagger-ui .opblock .opblock-summary-method { + background: #000; + color: #fff; + text-shadow: rgba(0, 0, 0, .1) 0 1px 0; + } + + .swagger-ui .opblock.opblock-post { + background: rgba(72, 203, 144, .1); + border-color: #48cb90; + } + + .swagger-ui .opblock.opblock-post .opblock-summary-method, .swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after { background: #48cb90; } + + .swagger-ui .opblock.opblock-post .opblock-summary { border-color: #48cb90; } + + .swagger-ui .opblock.opblock-put { + background: rgba(213, 157, 88, .1); + border-color: #d59d58; + } + + .swagger-ui .opblock.opblock-put .opblock-summary-method, .swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span::after { background: #d59d58; } + + .swagger-ui .opblock.opblock-put .opblock-summary { border-color: #d59d58; } + + .swagger-ui .opblock.opblock-delete { + background: rgba(200, 50, 50, .1); + border-color: #c83232; + } + + .swagger-ui .opblock.opblock-delete .opblock-summary-method, .swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after { background: #c83232; } + + .swagger-ui .opblock.opblock-delete .opblock-summary { border-color: #c83232; } + + .swagger-ui .opblock.opblock-get { + background: rgba(42, 105, 167, .1); + border-color: #2a69a7; + } + + .swagger-ui .opblock.opblock-get .opblock-summary-method, .swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after { background: #2a69a7; } + + .swagger-ui .opblock.opblock-get .opblock-summary { border-color: #2a69a7; } + + .swagger-ui .opblock.opblock-patch { + background: rgba(92, 214, 188, .1); + border-color: #5cd6bc; + } + + .swagger-ui .opblock.opblock-patch .opblock-summary-method, .swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span::after { background: #5cd6bc; } + + .swagger-ui .opblock.opblock-patch .opblock-summary { border-color: #5cd6bc; } + + .swagger-ui .opblock.opblock-head { + background: rgba(140, 63, 207, .1); + border-color: #8c3fcf; + } + + .swagger-ui .opblock.opblock-head .opblock-summary-method, .swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span::after { background: #8c3fcf; } + + .swagger-ui .opblock.opblock-head .opblock-summary { border-color: #8c3fcf; } + + .swagger-ui .opblock.opblock-options { + background: rgba(36, 89, 143, .1); + border-color: #24598f; + } + + .swagger-ui .opblock.opblock-options .opblock-summary-method, .swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span::after { background: #24598f; } + + .swagger-ui .opblock.opblock-options .opblock-summary { border-color: #24598f; } + + .swagger-ui .opblock.opblock-deprecated { + background: rgba(46, 46, 46, .1); + border-color: #2e2e2e; + opacity: .6; + } + + .swagger-ui .opblock.opblock-deprecated .opblock-summary-method, .swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span::after { background: #2e2e2e; } + + .swagger-ui .opblock.opblock-deprecated .opblock-summary { border-color: #2e2e2e; } + + .swagger-ui .filter .operation-filter-input { border: 2px solid #2b3446; } + + .swagger-ui .tab li:first-of-type::after { background: rgba(0, 0, 0, .2); } + + .swagger-ui .download-contents { + background: #7c8192; + color: #fff; + } + + .swagger-ui .scheme-container { + background: #1c1c21; + box-shadow: rgba(0, 0, 0, .15) 0 1px 2px 0; + } + + .swagger-ui .loading-container .loading::before { + animation: 1s linear 0s infinite normal none running rotation, .5s ease 0s 1 normal none running opacity; + border-color: rgba(0, 0, 0, .6) rgba(84, 84, 84, .1) rgba(84, 84, 84, .1); + } + + .swagger-ui .response-control-media-type--accept-controller select { border-color: #196619; } + + .swagger-ui .response-control-media-type__accept-message { color: #99e699; } + + .swagger-ui .version-pragma__message code { background-color: #3b3b3b; } + + .swagger-ui .btn { + background: 0 0; + border: 2px solid gray; + box-shadow: rgba(0, 0, 0, .1) 0 1px 2px; + color: #b5bac9; + } + + .swagger-ui .btn:hover { box-shadow: rgba(0, 0, 0, .3) 0 0 5px; } + + .swagger-ui .btn.authorize, .swagger-ui .btn.cancel { + background-color: transparent; + border-color: #a72a2a; + color: #e69999; + } + + .swagger-ui .btn.cancel:hover { + background-color: #a72a2a; + color: #fff; + } + + .swagger-ui .btn.authorize { + border-color: #48cb90; + color: #9ce3c3; + } + + .swagger-ui .btn.authorize svg { fill: #9ce3c3; } + + .btn.authorize.unlocked:hover { + background-color: #48cb90; + color: #fff; + } + + .btn.authorize.unlocked:hover svg { + fill: #fbfbfb; + } + + .swagger-ui .btn.execute { + background-color: #5892d5; + border-color: #5892d5; + color: #fff; + } + + .swagger-ui .copy-to-clipboard { background: #7c8192; } + + .swagger-ui .copy-to-clipboard button { background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat; } + + .swagger-ui select { + background: url("data:image/svg+xml;charset=utf-8,") right 10px center/20px no-repeat #212121; + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQuc3ZnIgogICBpZD0ic3ZnNCIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMjAgMjAiPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTEwIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzOCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICAgIGlua3NjYXBlOndpbmRvdy15PSItOSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTkiCiAgICAgaW5rc2NhcGU6Y3k9IjEwIgogICAgIGlua3NjYXBlOmN4PSIxMCIKICAgICBpbmtzY2FwZTp6b29tPSI0MS41IgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEzLjQxOCA3Ljg1OWEuNjk1LjY5NSAwIDAxLjk3OCAwIC42OC42OCAwIDAxMCAuOTY5bC0zLjkwOCAzLjgzYS42OTcuNjk3IDAgMDEtLjk3OSAwbC0zLjkwOC0zLjgzYS42OC42OCAwIDAxMC0uOTY5LjY5NS42OTUgMCAwMS45NzggMEwxMCAxMWwzLjQxOC0zLjE0MXoiIC8+Cjwvc3ZnPgo=) right 10px center/20px no-repeat #1c1c21; + border: 2px solid #41444e; + } + + .swagger-ui select[multiple] { background: #212121; } + + .swagger-ui button.invalid, .swagger-ui input[type=email].invalid, .swagger-ui input[type=file].invalid, .swagger-ui input[type=password].invalid, .swagger-ui input[type=search].invalid, .swagger-ui input[type=text].invalid, .swagger-ui select.invalid, .swagger-ui textarea.invalid { + background: #390e0e; + border-color: #c83232; + } + + .swagger-ui input[type=email], .swagger-ui input[type=file], .swagger-ui input[type=password], .swagger-ui input[type=search], .swagger-ui input[type=text], .swagger-ui textarea { + background: #1c1c21; + border: 1px solid #404040; + } + + .swagger-ui textarea { + background: rgba(28, 28, 33, .8); + color: #b5bac9; + } + + .swagger-ui input[disabled], .swagger-ui select[disabled] { + background-color: #1f1f1f; + color: #bfbfbf; + } + + .swagger-ui textarea[disabled] { + background-color: #41444e; + color: #fff; + } + + .swagger-ui select[disabled] { border-color: #878787; } + + .swagger-ui textarea:focus { border: 2px solid #2a69a7; } + + .swagger-ui .checkbox input[type=checkbox] + label > .item { + background: #303030; + box-shadow: #303030 0 0 0 2px; + } + + .swagger-ui .checkbox input[type=checkbox]:checked + label > .item { background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat #303030; } + + .swagger-ui .dialog-ux .backdrop-ux { background: rgba(0, 0, 0, .8); } + + .swagger-ui .dialog-ux .modal-ux { + background: #1c1c21; + border: 1px solid #2e2e2e; + box-shadow: rgba(0, 0, 0, .2) 0 10px 30px 0; + } + + .swagger-ui .dialog-ux .modal-ux-header .close-modal { background: 0 0; } + + .swagger-ui .model .deprecated span, .swagger-ui .model .deprecated td { color: #bfbfbf !important; } + + .swagger-ui .model-toggle::after { background: url("data:image/svg+xml;charset=utf-8,") 50% center/100% no-repeat; } + + .swagger-ui .model-hint { + background: rgba(0, 0, 0, .7); + color: #ebebeb; + } + + .swagger-ui section.models { border: 1px solid rgba(58, 64, 80, .3); } + + .swagger-ui section.models.is-open h4 { border-bottom: 1px solid rgba(58, 64, 80, .3); } + + .swagger-ui section.models .model-container { background: rgba(0, 0, 0, .05); } + + .swagger-ui section.models .model-container:hover { background: rgba(0, 0, 0, .07); } + + .swagger-ui .model-box { background: rgba(0, 0, 0, .1); } + + .swagger-ui .prop-type { color: #aaaad4; } + + .swagger-ui table thead tr td, .swagger-ui table thead tr th { + border-bottom: 1px solid rgba(58, 64, 80, .2); + color: #b5bac9; + } + + .swagger-ui .parameter__name.required::after { color: rgba(230, 153, 153, .6); } + + .swagger-ui .topbar .download-url-wrapper .select-label { color: #f0f0f0; } + + .swagger-ui .topbar .download-url-wrapper .download-url-button { + background: #63a040; + color: #fff; + } + + .swagger-ui .info .title small { background: #7c8492; } + + .swagger-ui .info .title small.version-stamp { background-color: #7a9b27; } + + .swagger-ui .auth-container .errors { + background-color: #350d0d; + color: #b5bac9; + } + + .swagger-ui .errors-wrapper { + background: rgba(200, 50, 50, .1); + border: 2px solid #c83232; + } + + .swagger-ui .markdown code, .swagger-ui .renderedmarkdown code { + background: rgba(0, 0, 0, .05); + color: #c299e6; + } + + .swagger-ui .model-toggle:after { background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQyLnN2ZyIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgaGVpZ2h0PSIyNCIKICAgd2lkdGg9IjI0Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGExMCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMKICAgICBpZD0iZGVmczgiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTkiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9Ii05IgogICAgIGlua3NjYXBlOmN5PSIxMiIKICAgICBpbmtzY2FwZTpjeD0iMTIiCiAgICAgaW5rc2NhcGU6em9vbT0iMzQuNTgzMzMzIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02eiIgLz4KPC9zdmc+Cg==) 50% no-repeat; } + + /* arrows for each operation and request are now white */ + .arrow, #large-arrow-up { fill: #fff; } + + #unlocked, #locked { fill: #fff; } + + ::-webkit-scrollbar-track { background-color: #646464 !important; } + + ::-webkit-scrollbar-thumb { + background-color: #242424 !important; + border: 2px solid #3e4346 !important; + } + + ::-webkit-scrollbar-button:vertical:start:decrement { + background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), linear-gradient(230deg, #696969 40%, transparent 41%), linear-gradient(0deg, #696969 40%, transparent 31%); + background-color: #b6b6b6; + } + + ::-webkit-scrollbar-button:vertical:end:increment { + background: linear-gradient(310deg, #696969 40%, transparent 41%), linear-gradient(50deg, #696969 40%, transparent 41%), linear-gradient(180deg, #696969 40%, transparent 31%); + background-color: #b6b6b6; + } + + ::-webkit-scrollbar-button:horizontal:end:increment { + background: linear-gradient(210deg, #696969 40%, transparent 41%), linear-gradient(330deg, #696969 40%, transparent 41%), linear-gradient(90deg, #696969 30%, transparent 31%); + background-color: #b6b6b6; + } + + ::-webkit-scrollbar-button:horizontal:start:decrement { + background: linear-gradient(30deg, #696969 40%, transparent 41%), linear-gradient(150deg, #696969 40%, transparent 41%), linear-gradient(270deg, #696969 30%, transparent 31%); + background-color: #b6b6b6; + } + + ::-webkit-scrollbar-button, ::-webkit-scrollbar-track-piece { background-color: #3e4346 !important; } + + .swagger-ui .black, .swagger-ui .checkbox, .swagger-ui .dark-gray, .swagger-ui .download-url-wrapper .loading, .swagger-ui .errors-wrapper .errors small, .swagger-ui .fallback, .swagger-ui .filter .loading, .swagger-ui .gray, .swagger-ui .hover-black:focus, .swagger-ui .hover-black:hover, .swagger-ui .hover-dark-gray:focus, .swagger-ui .hover-dark-gray:hover, .swagger-ui .hover-gray:focus, .swagger-ui .hover-gray:hover, .swagger-ui .hover-light-silver:focus, .swagger-ui .hover-light-silver:hover, .swagger-ui .hover-mid-gray:focus, .swagger-ui .hover-mid-gray:hover, .swagger-ui .hover-near-black:focus, .swagger-ui .hover-near-black:hover, .swagger-ui .hover-silver:focus, .swagger-ui .hover-silver:hover, .swagger-ui .light-silver, .swagger-ui .markdown pre, .swagger-ui .mid-gray, .swagger-ui .model .property, .swagger-ui .model .property.primitive, .swagger-ui .model-title, .swagger-ui .near-black, .swagger-ui .parameter__extension, .swagger-ui .parameter__in, .swagger-ui .prop-format, .swagger-ui .renderedmarkdown pre, .swagger-ui .response-col_links .response-undocumented, .swagger-ui .response-col_status .response-undocumented, .swagger-ui .silver, .swagger-ui section.models h4, .swagger-ui section.models h5, .swagger-ui span.token-not-formatted, .swagger-ui span.token-string, .swagger-ui table.headers .header-example, .swagger-ui table.model tr.description, .swagger-ui table.model tr.extension { color: #bfbfbf; } + + .swagger-ui .hover-white:focus, .swagger-ui .hover-white:hover, .swagger-ui .info .title small pre, .swagger-ui .topbar a, .swagger-ui .white { color: #fff; } + + .swagger-ui .bg-black-10, .swagger-ui .hover-bg-black-10:focus, .swagger-ui .hover-bg-black-10:hover, .swagger-ui .stripe-dark:nth-child(2n + 1) { background-color: rgba(0, 0, 0, .1); } + + .swagger-ui .bg-white-10, .swagger-ui .hover-bg-white-10:focus, .swagger-ui .hover-bg-white-10:hover, .swagger-ui .stripe-light:nth-child(2n + 1) { background-color: rgba(28, 28, 33, .1); } + + .swagger-ui .bg-light-silver, .swagger-ui .hover-bg-light-silver:focus, .swagger-ui .hover-bg-light-silver:hover, .swagger-ui .striped--light-silver:nth-child(2n + 1) { background-color: #6e6e6e; } + + .swagger-ui .bg-moon-gray, .swagger-ui .hover-bg-moon-gray:focus, .swagger-ui .hover-bg-moon-gray:hover, .swagger-ui .striped--moon-gray:nth-child(2n + 1) { background-color: #4d4d4d; } + + .swagger-ui .bg-light-gray, .swagger-ui .hover-bg-light-gray:focus, .swagger-ui .hover-bg-light-gray:hover, .swagger-ui .striped--light-gray:nth-child(2n + 1) { background-color: #2b2b2b; } + + .swagger-ui .bg-near-white, .swagger-ui .hover-bg-near-white:focus, .swagger-ui .hover-bg-near-white:hover, .swagger-ui .striped--near-white:nth-child(2n + 1) { background-color: #242424; } + + .swagger-ui .opblock-tag:hover, .swagger-ui section.models h4:hover { background: rgba(0, 0, 0, .02); } + + .swagger-ui .checkbox p, .swagger-ui .dialog-ux .modal-ux-content h4, .swagger-ui .dialog-ux .modal-ux-content p, .swagger-ui .dialog-ux .modal-ux-header h3, .swagger-ui .errors-wrapper .errors h4, .swagger-ui .errors-wrapper hgroup h4, .swagger-ui .info .base-url, .swagger-ui .info .title, .swagger-ui .info h1, .swagger-ui .info h2, .swagger-ui .info h3, .swagger-ui .info h4, .swagger-ui .info h5, .swagger-ui .info li, .swagger-ui .info p, .swagger-ui .info table, .swagger-ui .loading-container .loading::after, .swagger-ui .model, .swagger-ui .opblock .opblock-section-header h4, .swagger-ui .opblock .opblock-section-header > label, .swagger-ui .opblock .opblock-summary-description, .swagger-ui .opblock .opblock-summary-operation-id, .swagger-ui .opblock .opblock-summary-path, .swagger-ui .opblock .opblock-summary-path__deprecated, .swagger-ui .opblock-description-wrapper, .swagger-ui .opblock-description-wrapper h4, .swagger-ui .opblock-description-wrapper p, .swagger-ui .opblock-external-docs-wrapper, .swagger-ui .opblock-external-docs-wrapper h4, .swagger-ui .opblock-external-docs-wrapper p, .swagger-ui .opblock-tag small, .swagger-ui .opblock-title_normal, .swagger-ui .opblock-title_normal h4, .swagger-ui .opblock-title_normal p, .swagger-ui .parameter__name, .swagger-ui .parameter__type, .swagger-ui .response-col_links, .swagger-ui .response-col_status, .swagger-ui .responses-inner h4, .swagger-ui .responses-inner h5, .swagger-ui .scheme-container .schemes > label, .swagger-ui .scopes h2, .swagger-ui .servers > label, .swagger-ui .tab li, .swagger-ui label, .swagger-ui select, .swagger-ui table.headers td { color: #b5bac9; } + + .swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed, .swagger-ui .model-deprecated-warning, .swagger-ui .parameter__deprecated, .swagger-ui .parameter__name.required span, .swagger-ui table.model tr.property-row .star { color: #e69999; } + + .swagger-ui .opblock-body pre.microlight, .swagger-ui textarea.curl { + background: #41444e; + border-radius: 4px; + color: #fff; + } + + .swagger-ui .expand-methods svg, .swagger-ui .expand-methods:hover svg { fill: #bfbfbf; } + + .swagger-ui .auth-container, .swagger-ui .dialog-ux .modal-ux-header { border-bottom: 1px solid #2e2e2e; } + + .swagger-ui .topbar .download-url-wrapper .select-label select, .swagger-ui .topbar .download-url-wrapper input[type=text] { border: 2px solid #63a040; } + + .swagger-ui .info a, .swagger-ui .info a:hover, .swagger-ui .scopes h2 a { color: #99bde6; } + + /* Dark Scrollbar */ + ::-webkit-scrollbar { + width: 14px; + height: 14px; + } + + ::-webkit-scrollbar-button { + background-color: #3e4346 !important; + } + + ::-webkit-scrollbar-track { + background-color: #646464 !important; + } + + ::-webkit-scrollbar-track-piece { + background-color: #3e4346 !important; + } + + ::-webkit-scrollbar-thumb { + height: 50px; + background-color: #242424 !important; + border: 2px solid #3e4346 !important; + } + + ::-webkit-scrollbar-corner {} + + ::-webkit-resizer {} + + ::-webkit-scrollbar-button:vertical:start:decrement { + background: + linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), + linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%); + background-color: #b6b6b6; + } + + ::-webkit-scrollbar-button:vertical:end:increment { + background: + linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%); + background-color: #b6b6b6; + } + + ::-webkit-scrollbar-button:horizontal:end:increment { + background: + linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%); + background-color: #b6b6b6; + } + + ::-webkit-scrollbar-button:horizontal:start:decrement { + background: + linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%); + background-color: #b6b6b6; + } +} -- 2.43.0 From 984791f193e26f806462a0231ad15323b8115d4a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 10 Jun 2024 22:02:29 +0300 Subject: [PATCH 227/474] style: change template for log message --- Endpoint/Configuration/AppConfig/LoggerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs index 06e9f53..a82010f 100644 --- a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs @@ -57,7 +57,7 @@ public static class LoggerConfiguration { app.UseSerilogRequestLogging(options => { - options.MessageTemplate = "Handled {RequestPath} in {Elapsed:0.0000} ms"; + options.MessageTemplate = "[{RequestMethod}] {RequestPath} [Client {RemoteIPAddress}] [{StatusCode}] in {Elapsed:0.0000} ms"; options.GetLevel = (_, elapsed, ex) => elapsed >= 2500 || ex != null ? LogEventLevel.Warning -- 2.43.0 From 2e389b252c84081ffed6df5c2acfca3217e714ab Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 10 Jun 2024 22:03:48 +0300 Subject: [PATCH 228/474] refactor: use default pairPeriod --- .../Configuration/ScheduleConfigurationRequest.cs | 10 +--------- Endpoint/Controllers/Configuration/SetupController.cs | 11 ++++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ApiDto/Requests/Configuration/ScheduleConfigurationRequest.cs b/ApiDto/Requests/Configuration/ScheduleConfigurationRequest.cs index ca6e950..b24f529 100644 --- a/ApiDto/Requests/Configuration/ScheduleConfigurationRequest.cs +++ b/ApiDto/Requests/Configuration/ScheduleConfigurationRequest.cs @@ -1,6 +1,4 @@ -using Mirea.Api.Dto.Common; -using System; -using System.Collections.Generic; +using System; using System.ComponentModel.DataAnnotations; namespace Mirea.Api.Dto.Requests.Configuration; @@ -20,10 +18,4 @@ public class ScheduleConfigurationRequest /// [Required] public DateOnly StartTerm { get; set; } - - /// - /// Gets or sets the pair period times, keyed by pair number. - /// - [Required] - public required IDictionary PairPeriod { get; set; } } \ No newline at end of file diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 9bda318..2ccac21 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -294,7 +294,16 @@ public class SetupController( // every 6 hours CronUpdateSchedule = request.CronUpdateSchedule ?? "0 */6 * * *", StartTerm = request.StartTerm, - PairPeriod = request.PairPeriod.ConvertFromDto() + PairPeriod = new Dictionary + { + {1, new ScheduleSettings.PairPeriodTime(new TimeOnly(9, 0, 0), new TimeOnly(10, 30, 0))}, + {2, new ScheduleSettings.PairPeriodTime(new TimeOnly(10, 40, 0), new TimeOnly(12, 10, 0))}, + {3, new ScheduleSettings.PairPeriodTime(new TimeOnly(12, 40, 0), new TimeOnly(14, 10, 0))}, + {4, new ScheduleSettings.PairPeriodTime(new TimeOnly(14, 20, 0), new TimeOnly(15, 50, 0))}, + {5, new ScheduleSettings.PairPeriodTime(new TimeOnly(16, 20, 0), new TimeOnly(17, 50, 0))}, + {6, new ScheduleSettings.PairPeriodTime(new TimeOnly(18, 0, 0), new TimeOnly(19, 30, 0))}, + {7, new ScheduleSettings.PairPeriodTime(new TimeOnly(19, 40, 0), new TimeOnly(21, 10, 0))}, + } }; if (!CronExpression.TryParse(general.ScheduleSettings.CronUpdateSchedule, CronFormat.Standard, out _)) -- 2.43.0 From 0ecb796d54cc60612a668923cee5a9a529186758 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 10 Jun 2024 22:04:29 +0300 Subject: [PATCH 229/474] fix: set default settings for logging if null or empty --- .../Configuration/SetupController.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 2ccac21..67afb71 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -236,11 +236,7 @@ public class SetupController( { true => new LogSettings { - EnableLogToFile = true, - LogFileName = "log-", - LogFilePath = OperatingSystem.IsWindows() || PathBuilder.IsDefaultPath ? - PathBuilder.Combine("logs") : - "/var/log/mirea" + EnableLogToFile = true }, false => new LogSettings { @@ -250,6 +246,17 @@ public class SetupController( } }; + if (settings.EnableLogToFile) + { + if (string.IsNullOrEmpty(settings.LogFileName)) + settings.LogFileName = "log-"; + + if (string.IsNullOrEmpty(settings.LogFilePath)) + settings.LogFilePath = OperatingSystem.IsWindows() || PathBuilder.IsDefaultPath ? + PathBuilder.Combine("logs") : + "/var/log/mirea"; + } + var general = GeneralConfig; general.LogSettings = settings; GeneralConfig = general; -- 2.43.0 From 7d3952c37377bb357869372f2b497576c402c255 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 10 Jun 2024 22:05:18 +0300 Subject: [PATCH 230/474] build: upgrade reference --- Endpoint/Endpoint.csproj | 4 ++-- Security/Security.csproj | 2 +- SqlData/Application/Application.csproj | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 6def168..1dcb456 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -29,8 +29,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Security/Security.csproj b/Security/Security.csproj index 218d9f6..e40c16a 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -15,7 +15,7 @@ - + diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 3f82d6c..927ddcc 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -13,9 +13,9 @@ - - - + + + -- 2.43.0 From 70780d620a177cf1fecb752db082251fbfe96500 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 10 Jun 2024 22:15:15 +0300 Subject: [PATCH 231/474] fix: error CS0246 --- Endpoint/Controllers/Configuration/SetupController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 67afb71..7ccea5f 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -18,6 +18,7 @@ using MySqlConnector; using Npgsql; using StackExchange.Redis; using System; +using System.Collections.Generic; using System.Data; using System.IO; using System.Net.Mail; -- 2.43.0 From eba11f515d9ba19148430f32004c3cbf3557c984 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 13 Jun 2024 00:42:15 +0300 Subject: [PATCH 232/474] fix: singleton added in JwtTokenService --- Endpoint/Configuration/AppConfig/SecureConfiguration.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs b/Endpoint/Configuration/AppConfig/SecureConfiguration.cs index fd84f06..8e49960 100644 --- a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SecureConfiguration.cs @@ -14,7 +14,6 @@ public static class SecureConfiguration { services.AddSecurityServices(configuration); - services.AddSingleton(); services.AddSingleton(); if (configuration.Get()?.CacheSettings?.TypeDatabase == CacheSettings.CacheEnum.Redis) -- 2.43.0 From 21866d54cbe685c60a6e964916d7a79c087b1e6c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 13 Jun 2024 00:42:41 +0300 Subject: [PATCH 233/474] fix: add private to get EncryptKey --- Endpoint/Common/Services/Security/JwtTokenService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Common/Services/Security/JwtTokenService.cs b/Endpoint/Common/Services/Security/JwtTokenService.cs index 7c3225f..f5f7429 100644 --- a/Endpoint/Common/Services/Security/JwtTokenService.cs +++ b/Endpoint/Common/Services/Security/JwtTokenService.cs @@ -13,7 +13,7 @@ public class JwtTokenService : IAccessToken public required string Audience { private get; init; } public TimeSpan Lifetime { private get; init; } - public ReadOnlyMemory EncryptionKey { get; init; } + public ReadOnlyMemory EncryptionKey { private get; init; } public ReadOnlyMemory SigningKey { private get; init; } public (string Token, DateTime ExpireIn) GenerateToken(string userId) -- 2.43.0 From a5f9e67647ec63f9577fc282d7d1a58054251f79 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 15 Jun 2024 21:53:00 +0300 Subject: [PATCH 234/474] feat: add middleware for revocated tokens --- .../Middleware/JwtRevocationMiddleware.cs | 23 +++++++++++++++++++ Endpoint/Program.cs | 1 + 2 files changed, 24 insertions(+) create mode 100644 Endpoint/Middleware/JwtRevocationMiddleware.cs diff --git a/Endpoint/Middleware/JwtRevocationMiddleware.cs b/Endpoint/Middleware/JwtRevocationMiddleware.cs new file mode 100644 index 0000000..97818c7 --- /dev/null +++ b/Endpoint/Middleware/JwtRevocationMiddleware.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Http; +using Mirea.Api.Security.Common.Interfaces; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Middleware; + +public class JwtRevocationMiddleware(RequestDelegate next) +{ + public async Task Invoke(HttpContext context, IRevokedToken revokedTokenStore) + { + if (context.Request.Headers.ContainsKey("Authorization")) + { + var token = context.Request.Headers.Authorization.ToString().Replace("Bearer ", ""); + if (await revokedTokenStore.IsTokenRevokedAsync(token)) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return; + } + } + + await next(context); + } +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index cf016f4..a904e98 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -95,6 +95,7 @@ public class Program app.UseMiddleware(); app.UseMiddleware(); + app.UseMiddleware(); app.UseHttpsRedirection(); -- 2.43.0 From 039d323643ceb6fa736b58a1d0c102e09700926c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:36:11 +0300 Subject: [PATCH 235/474] fix: add refresh expire date --- .../Common/Dto/Responses/AuthTokenResponse.cs | 3 ++- Security/Services/AuthService.cs | 22 ++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Security/Common/Dto/Responses/AuthTokenResponse.cs b/Security/Common/Dto/Responses/AuthTokenResponse.cs index 0c8a3d4..16aed38 100644 --- a/Security/Common/Dto/Responses/AuthTokenResponse.cs +++ b/Security/Common/Dto/Responses/AuthTokenResponse.cs @@ -5,6 +5,7 @@ namespace Mirea.Api.Security.Common.Dto.Responses; public class AuthTokenResponse { public required string AccessToken { get; set; } + public DateTime AccessExpiresIn { get; set; } public required string RefreshToken { get; set; } - public DateTime ExpiresIn { get; set; } + public DateTime RefreshExpiresIn { get; set; } } \ No newline at end of file diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 5426532..2aa01d7 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -34,7 +34,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I public async Task GenerateAuthTokensAsync(TokenRequest request, string userId, CancellationToken cancellation = default) { var refreshToken = GenerateRefreshToken(); - var accessToken = GenerateAccessToken(userId); + var (token, expireIn) = GenerateAccessToken(userId); var authTokenStruct = new AuthToken { @@ -43,16 +43,17 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I RefreshToken = refreshToken, UserAgent = request.UserAgent, UserId = userId, - AccessToken = accessToken.Token + AccessToken = token }; await SetAuthTokenDataToCache(request.Fingerprint, authTokenStruct, cancellation); return new AuthTokenResponse { - AccessToken = accessToken.Token, - ExpiresIn = accessToken.ExpireIn, - RefreshToken = authTokenStruct.RefreshToken + AccessToken = token, + AccessExpiresIn = expireIn, + RefreshToken = authTokenStruct.RefreshToken, + RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime), }; } @@ -77,17 +78,18 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I throw new SecurityException(request.Fingerprint); } - var accessToken = GenerateAccessToken(authToken.UserId); + var (token, expireIn) = GenerateAccessToken(authToken.UserId); await RevokeAccessToken(authToken.AccessToken); - authToken.AccessToken = accessToken.Token; + authToken.AccessToken = token; await SetAuthTokenDataToCache(request.Fingerprint, authToken, cancellation); return new AuthTokenResponse { - AccessToken = accessToken.Token, - ExpiresIn = accessToken.ExpireIn, - RefreshToken = GenerateRefreshToken() + AccessToken = token, + AccessExpiresIn = expireIn, + RefreshToken = GenerateRefreshToken(), + RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime) }; } -- 2.43.0 From 79151e7da8b5452293a18ad178654bb40fb521c1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:43:40 +0300 Subject: [PATCH 236/474] feat: add bearer auth to swagger --- .../AppConfig/SwaggerConfiguration.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs index c43c730..4c2ff9f 100644 --- a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; using Mirea.Api.Endpoint.Configuration.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using System; @@ -19,6 +20,29 @@ public static class SwaggerConfiguration options.OperationFilter(); var basePath = AppDomain.CurrentDomain.BaseDirectory; + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Keep the JWT token in the field (Bearer token)", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + [] + } + }); + options.IncludeXmlComments(Path.Combine(basePath, "docs.xml")); options.IncludeXmlComments(Path.Combine(basePath, "ApiDtoDocs.xml")); }); -- 2.43.0 From 1a0d539e7632cf0f297bf0744a1670fc99046cca Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:44:34 +0300 Subject: [PATCH 237/474] fix: if cache get bytes then skip serealize --- .../Common/Services/Security/DistributedCacheService.cs | 2 +- Endpoint/Common/Services/Security/MemoryCacheService.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Endpoint/Common/Services/Security/DistributedCacheService.cs b/Endpoint/Common/Services/Security/DistributedCacheService.cs index bf3dc39..1d4a32b 100644 --- a/Endpoint/Common/Services/Security/DistributedCacheService.cs +++ b/Endpoint/Common/Services/Security/DistributedCacheService.cs @@ -17,7 +17,7 @@ public class DistributedCacheService(IDistributedCache cache) : ICacheService SlidingExpiration = slidingExpiration }; - var serializedValue = JsonSerializer.SerializeToUtf8Bytes(value); + var serializedValue = value as byte[] ?? JsonSerializer.SerializeToUtf8Bytes(value); await cache.SetAsync(key, serializedValue, options, cancellationToken); } diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs index a428034..fcb1f09 100644 --- a/Endpoint/Common/Services/Security/MemoryCacheService.cs +++ b/Endpoint/Common/Services/Security/MemoryCacheService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Caching.Memory; using Mirea.Api.Security.Common.Interfaces; using System; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -16,14 +17,14 @@ public class MemoryCacheService(IMemoryCache cache) : ICacheService SlidingExpiration = slidingExpiration }; - cache.Set(key, value, options); + cache.Set(key, value as byte[] ?? JsonSerializer.SerializeToUtf8Bytes(value), options); return Task.CompletedTask; } public Task GetAsync(string key, CancellationToken cancellationToken = default) { - cache.TryGetValue(key, out T? value); - return Task.FromResult(value); + cache.TryGetValue(key, out byte[]? value); + return Task.FromResult(JsonSerializer.Deserialize(value)); } public Task RemoveAsync(string key, CancellationToken cancellationToken = default) -- 2.43.0 From a36e0694ec8b8bb3e0884641c92b613aa15381d9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:50:42 +0300 Subject: [PATCH 238/474] feat: add token response --- ApiDto/Responses/TokenResponse.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 ApiDto/Responses/TokenResponse.cs diff --git a/ApiDto/Responses/TokenResponse.cs b/ApiDto/Responses/TokenResponse.cs new file mode 100644 index 0000000..9761b87 --- /dev/null +++ b/ApiDto/Responses/TokenResponse.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; + +/// +/// Provides a JWT and RT token. +/// +public class TokenResponse +{ + /// + /// A JWT token for accessing protected resources. + /// + [Required] + public required string AccessToken { get; set; } + + /// + /// The date and time when the JWT token expires. + /// + /// After this date, a new JWT token must be requested. + [Required] + public required DateTime ExpiresIn { get; set; } +} \ No newline at end of file -- 2.43.0 From 55371cb6751b5830b91d70a380fce9b3c22ac29b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:51:06 +0300 Subject: [PATCH 239/474] feat: add request to log in --- ApiDto/Requests/LoginRequest.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 ApiDto/Requests/LoginRequest.cs diff --git a/ApiDto/Requests/LoginRequest.cs b/ApiDto/Requests/LoginRequest.cs new file mode 100644 index 0000000..65fd4ec --- /dev/null +++ b/ApiDto/Requests/LoginRequest.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Requests; + +/// +/// Request to receive protected content +/// +public class LoginRequest +{ + /// + /// Login or Email to identify the client. + /// + [Required] + public required string Username { get; set; } + + /// + /// The client's password. + /// + [Required] + public required string Password { get; set; } +} \ No newline at end of file -- 2.43.0 From c62ec33130560fab198e985831ec6b026b34719c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:51:23 +0300 Subject: [PATCH 240/474] feat: add user roles --- ApiDto/Common/AuthRoles.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ApiDto/Common/AuthRoles.cs diff --git a/ApiDto/Common/AuthRoles.cs b/ApiDto/Common/AuthRoles.cs new file mode 100644 index 0000000..3d7529c --- /dev/null +++ b/ApiDto/Common/AuthRoles.cs @@ -0,0 +1,12 @@ +namespace Mirea.Api.Dto.Common; + +/// +/// An enumeration that indicates which role the user belongs to +/// +public enum AuthRoles +{ + /// + /// Administrator + /// + Admin +} \ No newline at end of file -- 2.43.0 From 160c7505f0ee87aa5d7f3710b7c0f0726b99e03d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 21 Jun 2024 21:52:21 +0300 Subject: [PATCH 241/474] feat: add controller for authentication --- Endpoint/Controllers/V1/AuthController.cs | 140 ++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 Endpoint/Controllers/V1/AuthController.cs diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs new file mode 100644 index 0000000..86b6fd7 --- /dev/null +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -0,0 +1,140 @@ +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.Model; +using Mirea.Api.Security.Common.Dto.Requests; +using Mirea.Api.Security.Services; +using System; +using System.Security; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class AuthController(IOptionsSnapshot user, AuthService auth, PasswordHashService passwordService) : BaseController, IActionFilter +{ + private string Fingerprint { get; set; } = string.Empty; + private string Ip { get; set; } = string.Empty; + private string UserAgent { get; set; } = string.Empty; + private string RefreshToken { get; set; } = string.Empty; + + private void SetCookie(string name, string value, DateTimeOffset? expires = null) + { + var cookieOptions = new CookieOptions + { + Expires = expires, + Path = "/api", + Domain = Request.Headers["X-Forwarded-Host"], + Secure = true, + HttpOnly = true + }; + + Response.Cookies.Append(name, value, cookieOptions); + } + + private void SetRefreshToken(string value, DateTimeOffset? expires = null) => + SetCookie("refresh_token", value, expires); + + private void SetFirstToken(string value, DateTimeOffset? expires = null) => + SetCookie("authentication_token", value, expires); + + [ApiExplorerSettings(IgnoreApi = true)] + public void OnActionExecuting(ActionExecutingContext context) + { + Ip = context.HttpContext.Connection.RemoteIpAddress?.ToString()!; + UserAgent = context.HttpContext.Request.Headers.UserAgent.ToString(); + Fingerprint = context.HttpContext.Request.Cookies["user_key"] ?? string.Empty; + RefreshToken = Request.Cookies["refresh_token"] ?? string.Empty; + + if (!string.IsNullOrWhiteSpace(Fingerprint)) return; + + Fingerprint = Guid.NewGuid().ToString().Replace("-", ""); + SetCookie("user_key", Fingerprint); + } + + [ApiExplorerSettings(IgnoreApi = true)] + public void OnActionExecuted(ActionExecutedContext context) { } + + [HttpPost("Login")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task> 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 Unauthorized("Invalid username/email or password"); + + var token = await auth.GenerateAuthTokensAsync(new TokenRequest + { + Fingerprint = Fingerprint, + Ip = Ip, + UserAgent = UserAgent + }, "1"); + + SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); + + return Ok(new TokenResponse + { + AccessToken = token.AccessToken, + ExpiresIn = token.AccessExpiresIn + }); + } + + [HttpGet("ReLogin")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task> ReLogin() + { + if (string.IsNullOrEmpty(RefreshToken)) + return Unauthorized(); + + try + { + var token = await auth.RefreshTokenAsync( + new TokenRequest + { + Ip = Ip, + UserAgent = UserAgent, + Fingerprint = Fingerprint + }, + RefreshToken + ); + + SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); + + return Ok(new TokenResponse + { + AccessToken = token.AccessToken, + ExpiresIn = token.AccessExpiresIn + }); + } + catch (SecurityException) + { + return Unauthorized(); + } + } + + [HttpGet("Logout")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [Authorize] + public async Task Logout() + { + SetRefreshToken("", DateTimeOffset.MinValue); + SetFirstToken("", DateTimeOffset.MinValue); + + await auth.LogoutAsync(Fingerprint); + + return Ok(); + } + + [HttpGet("GetRole")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [Authorize] + public ActionResult GetRole() => Ok(AuthRoles.Admin); +} -- 2.43.0 From 8d4c482bbd40775e2a7c7f1f3b0e5cc2d8d4af02 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 27 Jun 2024 23:37:40 +0300 Subject: [PATCH 242/474] fix: set correct cache key --- Security/Services/AuthService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 2aa01d7..d932dd1 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -72,7 +72,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.UserAgent != request.UserAgent && authToken.Ip != request.Ip) { - await cache.RemoveAsync(request.Fingerprint, cancellation); + await cache.RemoveAsync(GetAuthCacheKey(request.Fingerprint), cancellation); await RevokeAccessToken(authToken.AccessToken); throw new SecurityException(request.Fingerprint); -- 2.43.0 From 612efcb91cc660221f1731fbe6b3c9380e47c9fa Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 28 Jun 2024 01:49:45 +0300 Subject: [PATCH 243/474] fix: exception if value is null --- Endpoint/Common/Services/Security/MemoryCacheService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs index fcb1f09..6c5b8f6 100644 --- a/Endpoint/Common/Services/Security/MemoryCacheService.cs +++ b/Endpoint/Common/Services/Security/MemoryCacheService.cs @@ -23,8 +23,11 @@ public class MemoryCacheService(IMemoryCache cache) : ICacheService public Task GetAsync(string key, CancellationToken cancellationToken = default) { - cache.TryGetValue(key, out byte[]? value); - return Task.FromResult(JsonSerializer.Deserialize(value)); + return Task.FromResult( + cache.TryGetValue(key, out byte[]? value) ? + JsonSerializer.Deserialize(value) : + default + ); } public Task RemoveAsync(string key, CancellationToken cancellationToken = default) -- 2.43.0 From f89136669d5fb7c9620ecdaacf78c8ad1c236558 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 28 Jun 2024 22:52:05 +0300 Subject: [PATCH 244/474] fix: change RT in cache after generation --- Security/Services/AuthService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index d932dd1..5b293b3 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -81,14 +81,18 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I var (token, expireIn) = GenerateAccessToken(authToken.UserId); await RevokeAccessToken(authToken.AccessToken); + var newRefreshToken = GenerateRefreshToken(); + authToken.AccessToken = token; + authToken.RefreshToken = newRefreshToken; + await SetAuthTokenDataToCache(request.Fingerprint, authToken, cancellation); return new AuthTokenResponse { AccessToken = token, AccessExpiresIn = expireIn, - RefreshToken = GenerateRefreshToken(), + RefreshToken = newRefreshToken, RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime) }; } -- 2.43.0 From 2c112d00df883a3fc57dd8556b7a54ab595830d6 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 28 Jun 2024 22:52:58 +0300 Subject: [PATCH 245/474] fix: add Admin model to configuration --- Endpoint/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index a904e98..d368177 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -6,6 +6,7 @@ using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; using Mirea.Api.DataAccess.Persistence.Common; using Mirea.Api.Endpoint.Common.Interfaces; +using Mirea.Api.Endpoint.Common.Model; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.AppConfig; using Mirea.Api.Endpoint.Configuration.General; @@ -37,6 +38,8 @@ public class Program builder.Configuration.AddConfiguration(EnvironmentConfiguration.GetEnvironment()); builder.Configuration.AddJsonFile(PathBuilder.Combine(GeneralConfig.FilePath), optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); + builder.Configuration.AddJsonFile(PathBuilder.Combine(Admin.PathToSave), optional: true, reloadOnChange: true); + builder.Services.Configure(builder.Configuration); builder.Host.AddCustomSerilog(); AddDatabase(builder.Services, builder.Configuration); -- 2.43.0 From 41b5bb571b7b2c3b98e20a6cda6d0e8fb2cd716f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 28 Jun 2024 22:55:18 +0300 Subject: [PATCH 246/474] docs: add xml comments --- Endpoint/Controllers/V1/AuthController.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 86b6fd7..dcd1821 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -60,6 +60,12 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [ApiExplorerSettings(IgnoreApi = true)] public void OnActionExecuted(ActionExecutedContext context) { } + /// + /// Handles user authentication by verifying the username/email and password, + /// then generating and returning an authentication token if successful. + /// + /// The login request containing the username/email and password. + /// A TokenResponse containing the access token and its expiry if successful, otherwise an Unauthorized response. [HttpPost("Login")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task> Login([FromBody] LoginRequest request) @@ -87,6 +93,10 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass }); } + /// + /// Refreshes the authentication token using the existing refresh token. + /// + /// A TokenResponse containing the new access token and its expiry if successful, otherwise an Unauthorized response. [HttpGet("ReLogin")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task> ReLogin() @@ -120,6 +130,10 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass } } + /// + /// Logs the user out by clearing the refresh token and performing any necessary cleanup. + /// + /// An Ok response if the logout was successful. [HttpGet("Logout")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] @@ -133,6 +147,10 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass return Ok(); } + /// + /// Retrieves the role of the authenticated user. + /// + /// The role of the authenticated user. [HttpGet("GetRole")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] -- 2.43.0 From 9abdb1ac43c107fe3f7e1f0249e49e2f24afc163 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 2 Jul 2024 23:24:00 +0300 Subject: [PATCH 247/474] build: create dockerfile --- Dockerfile | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index d5e55b5..0ec705c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,23 @@ -#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. - FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -USER app +LABEL company="Winsomnia" +LABEL maintainer.name="Wesser" maintainer.email="support@winsomnia.net" WORKDIR /app -EXPOSE 8080 -EXPOSE 8081 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["Backend.csproj", "."] -RUN dotnet restore "./././Backend.csproj" COPY . . -WORKDIR "/src/." -RUN dotnet build "./Backend.csproj" -c $BUILD_CONFIGURATION -o /app/build -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./Backend.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +ARG NUGET_USERNAME +ARG NUGET_PASSWORD +ENV NUGET_USERNAME=$NUGET_USERNAME +ENV NUGET_PASSWORD=$NUGET_PASSWORD + +RUN dotnet restore ./Backend.sln --configfile nuget.config +WORKDIR /app +WORKDIR /src +RUN dotnet publish ./Endpoint/Endpoint.csproj -c Release -o /app FROM base AS final WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Backend.dll"] \ No newline at end of file +COPY --from=build /app . +ENTRYPOINT ["dotnet", "Enpoint.dll"] \ No newline at end of file -- 2.43.0 From 8c340e2a97ccbe8dfe1bc15b1223adcd82da31d5 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 2 Jul 2024 23:24:28 +0300 Subject: [PATCH 248/474] build: add deploy to server --- .gitea/workflows/deploy.yaml | 48 ++++++++++++++++++++++++++++++++++++ .gitea/workflows/test.yaml | 1 + Backend.sln | 1 + nuget.config | 13 ++++++++++ 4 files changed, 63 insertions(+) create mode 100644 .gitea/workflows/deploy.yaml create mode 100644 nuget.config diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..beb01fa --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,48 @@ +name: Build and Deploy Docker Container + +on: + push: + branches: + [master, 'release/*'] + +jobs: + build_and_deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + + - name: Build and push Docker image + run: | + docker build --build-arg NUGET_USERNAME=${{ secrets.NUGET_USERNAME }} --build-arg NUGET_PASSWORD=${{ secrets.NUGET_PASSWORD }} -t ${{ secrets.DOCKER_USERNAME }}/your-app-name:latest . + docker push ${{ secrets.DOCKER_USERNAME }}/your-app-name:latest + + - name: Start ssh-agent + id: ssh-agent + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Deploy to Server + env: + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_USER: ${{ secrets.SSH_USER }} + DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/mirea-bakend:latest + run: | + ssh-keyscan $SSH_HOST >> ~/.ssh/known_hosts + ssh $SSH_USER@$SSH_HOST " + docker pull $DOCKER_IMAGE && + docker stop mirea-bakend || true && + docker rm mirea-bakend || true && + docker run -d --name mirea-bakend -p 8085:8080 $DOCKER_IMAGE + " + + - name: Remove all keys from ssh-agent + run: ssh-add -D \ No newline at end of file diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml index 9d784b5..e870ad0 100644 --- a/.gitea/workflows/test.yaml +++ b/.gitea/workflows/test.yaml @@ -11,6 +11,7 @@ jobs: runs-on: ubuntu-latest steps: + - name: Checkout repository - uses: actions/checkout@v4 - name: Set up .NET Core diff --git a/Backend.sln b/Backend.sln index 81a8dbe..aa0141f 100644 --- a/Backend.sln +++ b/Backend.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", .gitattributes = .gitattributes .gitignore = .gitignore Dockerfile = Dockerfile + .gitea\workflows\deploy.yaml = .gitea\workflows\deploy.yaml LICENSE.txt = LICENSE.txt README.md = README.md .gitea\workflows\test.yaml = .gitea\workflows\test.yaml diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..96674ab --- /dev/null +++ b/nuget.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file -- 2.43.0 From 2e2cee2ca72e795bfb57ada964c05f2c2fb31b27 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 2 Jul 2024 23:33:22 +0300 Subject: [PATCH 249/474] build: change image name --- .gitea/workflows/deploy.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index beb01fa..b23552b 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -21,8 +21,8 @@ jobs: - name: Build and push Docker image run: | - docker build --build-arg NUGET_USERNAME=${{ secrets.NUGET_USERNAME }} --build-arg NUGET_PASSWORD=${{ secrets.NUGET_PASSWORD }} -t ${{ secrets.DOCKER_USERNAME }}/your-app-name:latest . - docker push ${{ secrets.DOCKER_USERNAME }}/your-app-name:latest + docker build --build-arg NUGET_USERNAME=${{ secrets.NUGET_USERNAME }} --build-arg NUGET_PASSWORD=${{ secrets.NUGET_PASSWORD }} -t ${{ secrets.DOCKER_USERNAME }}/mirea-bakend:latest . + docker push ${{ secrets.DOCKER_USERNAME }}/mirea-bakend:latest - name: Start ssh-agent id: ssh-agent -- 2.43.0 From de2f909ed664452c126c1dc4ff77fcc05e1b9bf7 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 2 Jul 2024 23:42:29 +0300 Subject: [PATCH 250/474] build: try create image --- .../{deploy.yaml => deploy-stage.yaml} | 26 ++++++++++++------- .gitea/workflows/test.yaml | 4 +-- Backend.sln | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) rename .gitea/workflows/{deploy.yaml => deploy-stage.yaml} (57%) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy-stage.yaml similarity index 57% rename from .gitea/workflows/deploy.yaml rename to .gitea/workflows/deploy-stage.yaml index b23552b..209708f 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy-stage.yaml @@ -6,7 +6,7 @@ on: [master, 'release/*'] jobs: - build_and_deploy: + build-and-deploy: runs-on: ubuntu-latest steps: @@ -16,13 +16,19 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub - run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Create nuget.config from secret + run: echo "${{ secrets.NUGET_CONFIG }}" > nuget.config - name: Build and push Docker image run: | - docker build --build-arg NUGET_USERNAME=${{ secrets.NUGET_USERNAME }} --build-arg NUGET_PASSWORD=${{ secrets.NUGET_PASSWORD }} -t ${{ secrets.DOCKER_USERNAME }}/mirea-bakend:latest . - docker push ${{ secrets.DOCKER_USERNAME }}/mirea-bakend:latest + docker build --build-arg NUGET_USERNAME=${{ secrets.NUGET_USERNAME }} --build-arg NUGET_PASSWORD=${{ secrets.NUGET_PASSWORD }} -t ${{ secrets.DOCKER_USERNAME }}/mirea-backend:latest . + docker push ${{ secrets.DOCKER_USERNAME }}/mirea-backend:latest - name: Start ssh-agent id: ssh-agent @@ -34,15 +40,15 @@ jobs: env: SSH_HOST: ${{ secrets.SSH_HOST }} SSH_USER: ${{ secrets.SSH_USER }} - DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/mirea-bakend:latest + DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/mirea-backend:latest run: | ssh-keyscan $SSH_HOST >> ~/.ssh/known_hosts ssh $SSH_USER@$SSH_HOST " docker pull $DOCKER_IMAGE && - docker stop mirea-bakend || true && - docker rm mirea-bakend || true && - docker run -d --name mirea-bakend -p 8085:8080 $DOCKER_IMAGE + docker stop mirea-backend || true && + docker rm mirea-backend || true && + docker run -d --name mirea-backend -p 8085:8080 $DOCKER_IMAGE " - name: Remove all keys from ssh-agent - run: ssh-add -D \ No newline at end of file + run: ssh-add -D diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml index e870ad0..8ee95ea 100644 --- a/.gitea/workflows/test.yaml +++ b/.gitea/workflows/test.yaml @@ -12,12 +12,12 @@ jobs: steps: - name: Checkout repository - - uses: actions/checkout@v4 + uses: actions/checkout@v4 - name: Set up .NET Core uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: '8.0.x' - name: Restore dependencies run: dotnet restore diff --git a/Backend.sln b/Backend.sln index aa0141f..5fe3b84 100644 --- a/Backend.sln +++ b/Backend.sln @@ -11,8 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", .env = .env .gitattributes = .gitattributes .gitignore = .gitignore + .gitea\workflows\deploy-stage.yaml = .gitea\workflows\deploy-stage.yaml Dockerfile = Dockerfile - .gitea\workflows\deploy.yaml = .gitea\workflows\deploy.yaml LICENSE.txt = LICENSE.txt README.md = README.md .gitea\workflows\test.yaml = .gitea\workflows\test.yaml -- 2.43.0 From 5b8d9e1f4a90ce95158c188f93ffbeddca4be0da Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 3 Jul 2024 00:05:01 +0300 Subject: [PATCH 251/474] build: add secret env --- .gitea/workflows/deploy-stage.yaml | 36 ++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/deploy-stage.yaml b/.gitea/workflows/deploy-stage.yaml index 209708f..168087d 100644 --- a/.gitea/workflows/deploy-stage.yaml +++ b/.gitea/workflows/deploy-stage.yaml @@ -22,9 +22,6 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Create nuget.config from secret - run: echo "${{ secrets.NUGET_CONFIG }}" > nuget.config - - name: Build and push Docker image run: | docker build --build-arg NUGET_USERNAME=${{ secrets.NUGET_USERNAME }} --build-arg NUGET_PASSWORD=${{ secrets.NUGET_PASSWORD }} -t ${{ secrets.DOCKER_USERNAME }}/mirea-backend:latest . @@ -41,13 +38,44 @@ jobs: SSH_HOST: ${{ secrets.SSH_HOST }} SSH_USER: ${{ secrets.SSH_USER }} DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/mirea-backend:latest + PATH_TO_SAVE: /data + SECURITY_SIGNING_TOKEN: ${{ secrets.SECURITY_SIGNING_TOKEN }} + SECURITY_ENCRYPTION_TOKEN: ${{ secrets.SECURITY_ENCRYPTION_TOKEN }} + SECURITY_LIFE_TIME_RT: ${{ secrets.SECURITY_LIFE_TIME_RT }} + SECURITY_LIFE_TIME_JWT: ${{ secrets.SECURITY_LIFE_TIME_JWT }} + SECURITY_LIFE_TIME_1_FA: ${{ secrets.SECURITY_LIFE_TIME_1_FA }} + SECURITY_JWT_ISSUER: ${{ secrets.SECURITY_JWT_ISSUER }} + SECURITY_JWT_AUDIENCE: ${{ secrets.SECURITY_JWT_AUDIENCE }} + SECURITY_HASH_ITERATION: ${{ secrets.SECURITY_HASH_ITERATION }} + SECURITY_HASH_MEMORY: ${{ secrets.SECURITY_HASH_MEMORY }} + SECURITY_HASH_PARALLELISM: ${{ secrets.SECURITY_HASH_PARALLELISM }} + SECURITY_HASH_SIZE: ${{ secrets.SECURITY_HASH_SIZE }} + SECURITY_HASH_TOKEN: ${{ secrets.SECURITY_HASH_TOKEN }} + SECURITY_SALT_SIZE: ${{ secrets.SECURITY_SALT_SIZE }} run: | ssh-keyscan $SSH_HOST >> ~/.ssh/known_hosts ssh $SSH_USER@$SSH_HOST " docker pull $DOCKER_IMAGE && docker stop mirea-backend || true && docker rm mirea-backend || true && - docker run -d --name mirea-backend -p 8085:8080 $DOCKER_IMAGE + docker run -d --name mirea-backend -p 8085:8080 \ + -restart=on-failure \ + -v mirea-data:/data \ + -e PATH_TO_SAVE=$PATH_TO_SAVE \ + -e SECURITY_SIGNING_TOKEN=$SECURITY_SIGNING_TOKEN \ + -e SECURITY_ENCRYPTION_TOKEN=$SECURITY_ENCRYPTION_TOKEN \ + -e SECURITY_LIFE_TIME_RT=$SECURITY_LIFE_TIME_RT \ + -e SECURITY_LIFE_TIME_JWT=$SECURITY_LIFE_TIME_JWT \ + -e SECURITY_LIFE_TIME_1_FA=$SECURITY_LIFE_TIME_1_FA \ + -e SECURITY_JWT_ISSUER=$SECURITY_JWT_ISSUER \ + -e SECURITY_JWT_AUDIENCE=$SECURITY_JWT_AUDIENCE \ + -e SECURITY_HASH_ITERATION=$SECURITY_HASH_ITERATION \ + -e SECURITY_HASH_MEMORY=$SECURITY_HASH_MEMORY \ + -e SECURITY_HASH_PARALLELISM=$SECURITY_HASH_PARALLELISM \ + -e SECURITY_HASH_SIZE=$SECURITY_HASH_SIZE \ + -e SECURITY_HASH_TOKEN=$SECURITY_HASH_TOKEN \ + -e SECURITY_SALT_SIZE=$SECURITY_SALT_SIZE \ + $DOCKER_IMAGE " - name: Remove all keys from ssh-agent -- 2.43.0 From 07edf0e5add96112881fc34c50d5ac05f12f376b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 3 Jul 2024 00:15:41 +0300 Subject: [PATCH 252/474] build: fix restart policy --- .gitea/workflows/deploy-stage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy-stage.yaml b/.gitea/workflows/deploy-stage.yaml index 168087d..60742a0 100644 --- a/.gitea/workflows/deploy-stage.yaml +++ b/.gitea/workflows/deploy-stage.yaml @@ -59,7 +59,7 @@ jobs: docker stop mirea-backend || true && docker rm mirea-backend || true && docker run -d --name mirea-backend -p 8085:8080 \ - -restart=on-failure \ + --restart=on-failure:10 \ -v mirea-data:/data \ -e PATH_TO_SAVE=$PATH_TO_SAVE \ -e SECURITY_SIGNING_TOKEN=$SECURITY_SIGNING_TOKEN \ -- 2.43.0 From 7e82d4a52072ab494bd1f7a6865edcd9c711468b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 3 Jul 2024 01:09:06 +0300 Subject: [PATCH 253/474] build: fix name of program --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0ec705c..182aaba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,4 +20,4 @@ RUN dotnet publish ./Endpoint/Endpoint.csproj -c Release -o /app FROM base AS final WORKDIR /app COPY --from=build /app . -ENTRYPOINT ["dotnet", "Enpoint.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "Endpoint.dll"] \ No newline at end of file -- 2.43.0 From 2f7c77e76495f1775de3f5eab3d99714cfc34d7d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 3 Jul 2024 10:09:50 +0300 Subject: [PATCH 254/474] fix: try get value Signed-off-by: Polianin Nikita --- Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs index 80f2fb0..9f4523b 100644 --- a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs @@ -61,7 +61,8 @@ public static class EnvironmentConfiguration result.AddInMemoryCollection(LoadEnvironment(".env.develop")!); #endif - Environment.SetEnvironmentVariable("PATH_TO_SAVE", variablesFromFile["PATH_TO_SAVE"]); + if (variablesFromFile.TryGetValue("PATH_TO_SAVE", out var data)) + Environment.SetEnvironmentVariable("PATH_TO_SAVE", variablesFromFile["PATH_TO_SAVE"]); return result.Build(); } -- 2.43.0 From e0cff050de0c58fd90d533797d8a9dfd76f89843 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 3 Jul 2024 10:10:58 +0300 Subject: [PATCH 255/474] build: fix endpoint name --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 182aaba..e1856c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,4 +20,4 @@ RUN dotnet publish ./Endpoint/Endpoint.csproj -c Release -o /app FROM base AS final WORKDIR /app COPY --from=build /app . -ENTRYPOINT ["dotnet", "Endpoint.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "Mirea.Api.Endpoint.dll"] \ No newline at end of file -- 2.43.0 From 098fca5df8693ecf479d8df0e32693a66cc27a75 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 3 Jul 2024 11:04:34 +0300 Subject: [PATCH 256/474] build: fix copy docs Signed-off-by: Polianin Nikita --- ApiDto/ApiDto.csproj | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ApiDto/ApiDto.csproj b/ApiDto/ApiDto.csproj index f806393..ffeb91a 100644 --- a/ApiDto/ApiDto.csproj +++ b/ApiDto/ApiDto.csproj @@ -20,4 +20,23 @@ + + + CopyXmlDocuments; + $(CopyAllFilesToSingleFolderForPackageDependsOn); + + + CopyXmlDocuments; + $(CopyAllFilesToSingleFolderForMsdeployDependsOn); + + + + + + + bin\%(RecursiveDir)%(Filename)%(Extension) + + + + -- 2.43.0 From 2041a187e7bfee922e72b948a5182fcf903f8cc6 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 22:40:59 +0300 Subject: [PATCH 257/474] fix: create directory if not exist --- .../Configuration/AppConfig/EnvironmentConfiguration.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs index 9f4523b..cb22eb5 100644 --- a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs @@ -61,8 +61,12 @@ public static class EnvironmentConfiguration result.AddInMemoryCollection(LoadEnvironment(".env.develop")!); #endif - if (variablesFromFile.TryGetValue("PATH_TO_SAVE", out var data)) - Environment.SetEnvironmentVariable("PATH_TO_SAVE", variablesFromFile["PATH_TO_SAVE"]); + if (!variablesFromFile.TryGetValue("PATH_TO_SAVE", out var data)) + return result.Build(); + + Environment.SetEnvironmentVariable("PATH_TO_SAVE", data); + if (!Directory.Exists(data)) + Directory.CreateDirectory(data); return result.Build(); } -- 2.43.0 From fe24dfcd6a8b7cd964192e37520619210241d79a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 23:31:05 +0300 Subject: [PATCH 258/474] perf: return Dictionary instead interface --- Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs index cb22eb5..fe90c2c 100644 --- a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs @@ -9,9 +9,9 @@ namespace Mirea.Api.Endpoint.Configuration.AppConfig; public static class EnvironmentConfiguration { - private static IDictionary LoadEnvironment(string envFile) + private static Dictionary LoadEnvironment(string envFile) { - Dictionary environment = new(); + Dictionary environment = []; if (!File.Exists(envFile)) return environment; -- 2.43.0 From 7e2016080f98d8d54fc4697e1472c91d06fead7e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 23:37:16 +0300 Subject: [PATCH 259/474] style: compact css --- Endpoint/wwwroot/css/swagger/SwaggerDark.css | 850 +------------------ 1 file changed, 1 insertion(+), 849 deletions(-) diff --git a/Endpoint/wwwroot/css/swagger/SwaggerDark.css b/Endpoint/wwwroot/css/swagger/SwaggerDark.css index cfc4a49..e03ac67 100644 --- a/Endpoint/wwwroot/css/swagger/SwaggerDark.css +++ b/Endpoint/wwwroot/css/swagger/SwaggerDark.css @@ -1,849 +1 @@ -@media only screen and (prefers-color-scheme: dark) { - - a { color: #8c8cfa; } - - ::-webkit-scrollbar-track-piece { background-color: rgba(255, 255, 255, .2) !important; } - - ::-webkit-scrollbar-track { background-color: rgba(255, 255, 255, .3) !important; } - - ::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, .5) !important; } - - embed[type="application/pdf"] { filter: invert(90%); } - - html { - background: #1f1f1f !important; - box-sizing: border-box; - filter: contrast(100%) brightness(100%) saturate(100%); - overflow-y: scroll; - } - - body { - background: #1f1f1f; - background-color: #1f1f1f; - background-image: none !important; - } - - button, input, select, textarea { - background-color: #1f1f1f; - color: #bfbfbf; - } - - font, html { color: #bfbfbf; } - - .swagger-ui, .swagger-ui section h3 { color: #b5bac9; } - - .swagger-ui a { background-color: transparent; } - - .swagger-ui mark { - background-color: #664b00; - color: #bfbfbf; - } - - .swagger-ui legend { color: inherit; } - - .swagger-ui .debug * { outline: #e6da99 solid 1px; } - - .swagger-ui .debug-white * { outline: #fff solid 1px; } - - .swagger-ui .debug-black * { outline: #bfbfbf solid 1px; } - - .swagger-ui .debug-grid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) 0 0; } - - .swagger-ui .debug-grid-16 { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) 0 0; } - - .swagger-ui .debug-grid-8-solid { background: url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) 0 0 #1c1c21; } - - .swagger-ui .debug-grid-16-solid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) 0 0 #1c1c21; } - - .swagger-ui .b--black { border-color: #000; } - - .swagger-ui .b--near-black { border-color: #121212; } - - .swagger-ui .b--dark-gray { border-color: #333; } - - .swagger-ui .b--mid-gray { border-color: #545454; } - - .swagger-ui .b--gray { border-color: #787878; } - - .swagger-ui .b--silver { border-color: #999; } - - .swagger-ui .b--light-silver { border-color: #6e6e6e; } - - .swagger-ui .b--moon-gray { border-color: #4d4d4d; } - - .swagger-ui .b--light-gray { border-color: #2b2b2b; } - - .swagger-ui .b--near-white { border-color: #242424; } - - .swagger-ui .b--white { border-color: #1c1c21; } - - .swagger-ui .b--white-90 { border-color: rgba(28, 28, 33, .9); } - - .swagger-ui .b--white-80 { border-color: rgba(28, 28, 33, .8); } - - .swagger-ui .b--white-70 { border-color: rgba(28, 28, 33, .7); } - - .swagger-ui .b--white-60 { border-color: rgba(28, 28, 33, .6); } - - .swagger-ui .b--white-50 { border-color: rgba(28, 28, 33, .5); } - - .swagger-ui .b--white-40 { border-color: rgba(28, 28, 33, .4); } - - .swagger-ui .b--white-30 { border-color: rgba(28, 28, 33, .3); } - - .swagger-ui .b--white-20 { border-color: rgba(28, 28, 33, .2); } - - .swagger-ui .b--white-10 { border-color: rgba(28, 28, 33, .1); } - - .swagger-ui .b--white-05 { border-color: rgba(28, 28, 33, .05); } - - .swagger-ui .b--white-025 { border-color: rgba(28, 28, 33, .024); } - - .swagger-ui .b--white-0125 { border-color: rgba(28, 28, 33, .01); } - - .swagger-ui .b--black-90 { border-color: rgba(0, 0, 0, .9); } - - .swagger-ui .b--black-80 { border-color: rgba(0, 0, 0, .8); } - - .swagger-ui .b--black-70 { border-color: rgba(0, 0, 0, .7); } - - .swagger-ui .b--black-60 { border-color: rgba(0, 0, 0, .6); } - - .swagger-ui .b--black-50 { border-color: rgba(0, 0, 0, .5); } - - .swagger-ui .b--black-40 { border-color: rgba(0, 0, 0, .4); } - - .swagger-ui .b--black-30 { border-color: rgba(0, 0, 0, .3); } - - .swagger-ui .b--black-20 { border-color: rgba(0, 0, 0, .2); } - - .swagger-ui .b--black-10 { border-color: rgba(0, 0, 0, .1); } - - .swagger-ui .b--black-05 { border-color: rgba(0, 0, 0, .05); } - - .swagger-ui .b--black-025 { border-color: rgba(0, 0, 0, .024); } - - .swagger-ui .b--black-0125 { border-color: rgba(0, 0, 0, .01); } - - .swagger-ui .b--dark-red { border-color: #bc2f36; } - - .swagger-ui .b--red { border-color: #c83932; } - - .swagger-ui .b--light-red { border-color: #ab3c2b; } - - .swagger-ui .b--orange { border-color: #cc6e33; } - - .swagger-ui .b--purple { border-color: #5e2ca5; } - - .swagger-ui .b--light-purple { border-color: #672caf; } - - .swagger-ui .b--dark-pink { border-color: #ab2b81; } - - .swagger-ui .b--hot-pink { border-color: #c03086; } - - .swagger-ui .b--pink { border-color: #8f2464; } - - .swagger-ui .b--light-pink { border-color: #721d4d; } - - .swagger-ui .b--dark-green { border-color: #1c6e50; } - - .swagger-ui .b--green { border-color: #279b70; } - - .swagger-ui .b--light-green { border-color: #228762; } - - .swagger-ui .b--navy { border-color: #0d1d35; } - - .swagger-ui .b--dark-blue { border-color: #20497e; } - - .swagger-ui .b--blue { border-color: #4380d0; } - - .swagger-ui .b--light-blue { border-color: #20517e; } - - .swagger-ui .b--lightest-blue { border-color: #143a52; } - - .swagger-ui .b--washed-blue { border-color: #0c312d; } - - .swagger-ui .b--washed-green { border-color: #0f3d2c; } - - .swagger-ui .b--washed-red { border-color: #411010; } - - .swagger-ui .b--transparent { border-color: transparent; } - - .swagger-ui .b--gold, .swagger-ui .b--light-yellow, .swagger-ui .b--washed-yellow, .swagger-ui .b--yellow { border-color: #664b00; } - - .swagger-ui .shadow-1 { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } - - .swagger-ui .shadow-2 { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } - - .swagger-ui .shadow-3 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } - - .swagger-ui .shadow-4 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } - - .swagger-ui .shadow-5 { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } - - @media screen and (min-width: 30em) { - .swagger-ui .shadow-1-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } - - .swagger-ui .shadow-2-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } - - .swagger-ui .shadow-3-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } - - .swagger-ui .shadow-4-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } - - .swagger-ui .shadow-5-ns { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } - } - - @media screen and (max-width: 60em) and (min-width: 30em) { - .swagger-ui .shadow-1-m { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } - - .swagger-ui .shadow-2-m { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } - - .swagger-ui .shadow-3-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } - - .swagger-ui .shadow-4-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } - - .swagger-ui .shadow-5-m { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } - } - - @media screen and (min-width: 60em) { - .swagger-ui .shadow-1-l { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } - - .swagger-ui .shadow-2-l { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } - - .swagger-ui .shadow-3-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } - - .swagger-ui .shadow-4-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } - - .swagger-ui .shadow-5-l { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } - } - - .swagger-ui .black-05 { color: rgba(191, 191, 191, .05); } - - .swagger-ui .bg-black-05 { background-color: rgba(0, 0, 0, .05); } - - .swagger-ui .black-90, .swagger-ui .hover-black-90:focus, .swagger-ui .hover-black-90:hover { color: rgba(191, 191, 191, .9); } - - .swagger-ui .black-80, .swagger-ui .hover-black-80:focus, .swagger-ui .hover-black-80:hover { color: rgba(191, 191, 191, .8); } - - .swagger-ui .black-70, .swagger-ui .hover-black-70:focus, .swagger-ui .hover-black-70:hover { color: rgba(191, 191, 191, .7); } - - .swagger-ui .black-60, .swagger-ui .hover-black-60:focus, .swagger-ui .hover-black-60:hover { color: rgba(191, 191, 191, .6); } - - .swagger-ui .black-50, .swagger-ui .hover-black-50:focus, .swagger-ui .hover-black-50:hover { color: rgba(191, 191, 191, .5); } - - .swagger-ui .black-40, .swagger-ui .hover-black-40:focus, .swagger-ui .hover-black-40:hover { color: rgba(191, 191, 191, .4); } - - .swagger-ui .black-30, .swagger-ui .hover-black-30:focus, .swagger-ui .hover-black-30:hover { color: rgba(191, 191, 191, .3); } - - .swagger-ui .black-20, .swagger-ui .hover-black-20:focus, .swagger-ui .hover-black-20:hover { color: rgba(191, 191, 191, .2); } - - .swagger-ui .black-10, .swagger-ui .hover-black-10:focus, .swagger-ui .hover-black-10:hover { color: rgba(191, 191, 191, .1); } - - .swagger-ui .hover-white-90:focus, .swagger-ui .hover-white-90:hover, .swagger-ui .white-90 { color: rgba(255, 255, 255, .9); } - - .swagger-ui .hover-white-80:focus, .swagger-ui .hover-white-80:hover, .swagger-ui .white-80 { color: rgba(255, 255, 255, .8); } - - .swagger-ui .hover-white-70:focus, .swagger-ui .hover-white-70:hover, .swagger-ui .white-70 { color: rgba(255, 255, 255, .7); } - - .swagger-ui .hover-white-60:focus, .swagger-ui .hover-white-60:hover, .swagger-ui .white-60 { color: rgba(255, 255, 255, .6); } - - .swagger-ui .hover-white-50:focus, .swagger-ui .hover-white-50:hover, .swagger-ui .white-50 { color: rgba(255, 255, 255, .5); } - - .swagger-ui .hover-white-40:focus, .swagger-ui .hover-white-40:hover, .swagger-ui .white-40 { color: rgba(255, 255, 255, .4); } - - .swagger-ui .hover-white-30:focus, .swagger-ui .hover-white-30:hover, .swagger-ui .white-30 { color: rgba(255, 255, 255, .3); } - - .swagger-ui .hover-white-20:focus, .swagger-ui .hover-white-20:hover, .swagger-ui .white-20 { color: rgba(255, 255, 255, .2); } - - .swagger-ui .hover-white-10:focus, .swagger-ui .hover-white-10:hover, .swagger-ui .white-10 { color: rgba(255, 255, 255, .1); } - - .swagger-ui .hover-moon-gray:focus, .swagger-ui .hover-moon-gray:hover, .swagger-ui .moon-gray { color: #ccc; } - - .swagger-ui .hover-light-gray:focus, .swagger-ui .hover-light-gray:hover, .swagger-ui .light-gray { color: #ededed; } - - .swagger-ui .hover-near-white:focus, .swagger-ui .hover-near-white:hover, .swagger-ui .near-white { color: #f5f5f5; } - - .swagger-ui .dark-red, .swagger-ui .hover-dark-red:focus, .swagger-ui .hover-dark-red:hover { color: #e6999d; } - - .swagger-ui .hover-red:focus, .swagger-ui .hover-red:hover, .swagger-ui .red { color: #e69d99; } - - .swagger-ui .hover-light-red:focus, .swagger-ui .hover-light-red:hover, .swagger-ui .light-red { color: #e6a399; } - - .swagger-ui .hover-orange:focus, .swagger-ui .hover-orange:hover, .swagger-ui .orange { color: #e6b699; } - - .swagger-ui .gold, .swagger-ui .hover-gold:focus, .swagger-ui .hover-gold:hover { color: #e6d099; } - - .swagger-ui .hover-yellow:focus, .swagger-ui .hover-yellow:hover, .swagger-ui .yellow { color: #e6da99; } - - .swagger-ui .hover-light-yellow:focus, .swagger-ui .hover-light-yellow:hover, .swagger-ui .light-yellow { color: #ede6b6; } - - .swagger-ui .hover-purple:focus, .swagger-ui .hover-purple:hover, .swagger-ui .purple { color: #b99ae4; } - - .swagger-ui .hover-light-purple:focus, .swagger-ui .hover-light-purple:hover, .swagger-ui .light-purple { color: #bb99e6; } - - .swagger-ui .dark-pink, .swagger-ui .hover-dark-pink:focus, .swagger-ui .hover-dark-pink:hover { color: #e699cc; } - - .swagger-ui .hot-pink, .swagger-ui .hover-hot-pink:focus, .swagger-ui .hover-hot-pink:hover, .swagger-ui .hover-pink:focus, .swagger-ui .hover-pink:hover, .swagger-ui .pink { color: #e699c7; } - - .swagger-ui .hover-light-pink:focus, .swagger-ui .hover-light-pink:hover, .swagger-ui .light-pink { color: #edb6d5; } - - .swagger-ui .dark-green, .swagger-ui .green, .swagger-ui .hover-dark-green:focus, .swagger-ui .hover-dark-green:hover, .swagger-ui .hover-green:focus, .swagger-ui .hover-green:hover { color: #99e6c9; } - - .swagger-ui .hover-light-green:focus, .swagger-ui .hover-light-green:hover, .swagger-ui .light-green { color: #a1e8ce; } - - .swagger-ui .hover-navy:focus, .swagger-ui .hover-navy:hover, .swagger-ui .navy { color: #99b8e6; } - - .swagger-ui .blue, .swagger-ui .dark-blue, .swagger-ui .hover-blue:focus, .swagger-ui .hover-blue:hover, .swagger-ui .hover-dark-blue:focus, .swagger-ui .hover-dark-blue:hover { color: #99bae6; } - - .swagger-ui .hover-light-blue:focus, .swagger-ui .hover-light-blue:hover, .swagger-ui .light-blue { color: #a9cbea; } - - .swagger-ui .hover-lightest-blue:focus, .swagger-ui .hover-lightest-blue:hover, .swagger-ui .lightest-blue { color: #d6e9f5; } - - .swagger-ui .hover-washed-blue:focus, .swagger-ui .hover-washed-blue:hover, .swagger-ui .washed-blue { color: #f7fdfc; } - - .swagger-ui .hover-washed-green:focus, .swagger-ui .hover-washed-green:hover, .swagger-ui .washed-green { color: #ebfaf4; } - - .swagger-ui .hover-washed-yellow:focus, .swagger-ui .hover-washed-yellow:hover, .swagger-ui .washed-yellow { color: #fbf9ef; } - - .swagger-ui .hover-washed-red:focus, .swagger-ui .hover-washed-red:hover, .swagger-ui .washed-red { color: #f9e7e7; } - - .swagger-ui .color-inherit, .swagger-ui .hover-inherit:focus, .swagger-ui .hover-inherit:hover { color: inherit; } - - .swagger-ui .bg-black-90, .swagger-ui .hover-bg-black-90:focus, .swagger-ui .hover-bg-black-90:hover { background-color: rgba(0, 0, 0, .9); } - - .swagger-ui .bg-black-80, .swagger-ui .hover-bg-black-80:focus, .swagger-ui .hover-bg-black-80:hover { background-color: rgba(0, 0, 0, .8); } - - .swagger-ui .bg-black-70, .swagger-ui .hover-bg-black-70:focus, .swagger-ui .hover-bg-black-70:hover { background-color: rgba(0, 0, 0, .7); } - - .swagger-ui .bg-black-60, .swagger-ui .hover-bg-black-60:focus, .swagger-ui .hover-bg-black-60:hover { background-color: rgba(0, 0, 0, .6); } - - .swagger-ui .bg-black-50, .swagger-ui .hover-bg-black-50:focus, .swagger-ui .hover-bg-black-50:hover { background-color: rgba(0, 0, 0, .5); } - - .swagger-ui .bg-black-40, .swagger-ui .hover-bg-black-40:focus, .swagger-ui .hover-bg-black-40:hover { background-color: rgba(0, 0, 0, .4); } - - .swagger-ui .bg-black-30, .swagger-ui .hover-bg-black-30:focus, .swagger-ui .hover-bg-black-30:hover { background-color: rgba(0, 0, 0, .3); } - - .swagger-ui .bg-black-20, .swagger-ui .hover-bg-black-20:focus, .swagger-ui .hover-bg-black-20:hover { background-color: rgba(0, 0, 0, .2); } - - .swagger-ui .bg-white-90, .swagger-ui .hover-bg-white-90:focus, .swagger-ui .hover-bg-white-90:hover { background-color: rgba(28, 28, 33, .9); } - - .swagger-ui .bg-white-80, .swagger-ui .hover-bg-white-80:focus, .swagger-ui .hover-bg-white-80:hover { background-color: rgba(28, 28, 33, .8); } - - .swagger-ui .bg-white-70, .swagger-ui .hover-bg-white-70:focus, .swagger-ui .hover-bg-white-70:hover { background-color: rgba(28, 28, 33, .7); } - - .swagger-ui .bg-white-60, .swagger-ui .hover-bg-white-60:focus, .swagger-ui .hover-bg-white-60:hover { background-color: rgba(28, 28, 33, .6); } - - .swagger-ui .bg-white-50, .swagger-ui .hover-bg-white-50:focus, .swagger-ui .hover-bg-white-50:hover { background-color: rgba(28, 28, 33, .5); } - - .swagger-ui .bg-white-40, .swagger-ui .hover-bg-white-40:focus, .swagger-ui .hover-bg-white-40:hover { background-color: rgba(28, 28, 33, .4); } - - .swagger-ui .bg-white-30, .swagger-ui .hover-bg-white-30:focus, .swagger-ui .hover-bg-white-30:hover { background-color: rgba(28, 28, 33, .3); } - - .swagger-ui .bg-white-20, .swagger-ui .hover-bg-white-20:focus, .swagger-ui .hover-bg-white-20:hover { background-color: rgba(28, 28, 33, .2); } - - .swagger-ui .bg-black, .swagger-ui .hover-bg-black:focus, .swagger-ui .hover-bg-black:hover { background-color: #000; } - - .swagger-ui .bg-near-black, .swagger-ui .hover-bg-near-black:focus, .swagger-ui .hover-bg-near-black:hover { background-color: #121212; } - - .swagger-ui .bg-dark-gray, .swagger-ui .hover-bg-dark-gray:focus, .swagger-ui .hover-bg-dark-gray:hover { background-color: #333; } - - .swagger-ui .bg-mid-gray, .swagger-ui .hover-bg-mid-gray:focus, .swagger-ui .hover-bg-mid-gray:hover { background-color: #545454; } - - .swagger-ui .bg-gray, .swagger-ui .hover-bg-gray:focus, .swagger-ui .hover-bg-gray:hover { background-color: #787878; } - - .swagger-ui .bg-silver, .swagger-ui .hover-bg-silver:focus, .swagger-ui .hover-bg-silver:hover { background-color: #999; } - - .swagger-ui .bg-white, .swagger-ui .hover-bg-white:focus, .swagger-ui .hover-bg-white:hover { background-color: #1c1c21; } - - .swagger-ui .bg-transparent, .swagger-ui .hover-bg-transparent:focus, .swagger-ui .hover-bg-transparent:hover { background-color: transparent; } - - .swagger-ui .bg-dark-red, .swagger-ui .hover-bg-dark-red:focus, .swagger-ui .hover-bg-dark-red:hover { background-color: #bc2f36; } - - .swagger-ui .bg-red, .swagger-ui .hover-bg-red:focus, .swagger-ui .hover-bg-red:hover { background-color: #c83932; } - - .swagger-ui .bg-light-red, .swagger-ui .hover-bg-light-red:focus, .swagger-ui .hover-bg-light-red:hover { background-color: #ab3c2b; } - - .swagger-ui .bg-orange, .swagger-ui .hover-bg-orange:focus, .swagger-ui .hover-bg-orange:hover { background-color: #cc6e33; } - - .swagger-ui .bg-gold, .swagger-ui .bg-light-yellow, .swagger-ui .bg-washed-yellow, .swagger-ui .bg-yellow, .swagger-ui .hover-bg-gold:focus, .swagger-ui .hover-bg-gold:hover, .swagger-ui .hover-bg-light-yellow:focus, .swagger-ui .hover-bg-light-yellow:hover, .swagger-ui .hover-bg-washed-yellow:focus, .swagger-ui .hover-bg-washed-yellow:hover, .swagger-ui .hover-bg-yellow:focus, .swagger-ui .hover-bg-yellow:hover { background-color: #664b00; } - - .swagger-ui .bg-purple, .swagger-ui .hover-bg-purple:focus, .swagger-ui .hover-bg-purple:hover { background-color: #5e2ca5; } - - .swagger-ui .bg-light-purple, .swagger-ui .hover-bg-light-purple:focus, .swagger-ui .hover-bg-light-purple:hover { background-color: #672caf; } - - .swagger-ui .bg-dark-pink, .swagger-ui .hover-bg-dark-pink:focus, .swagger-ui .hover-bg-dark-pink:hover { background-color: #ab2b81; } - - .swagger-ui .bg-hot-pink, .swagger-ui .hover-bg-hot-pink:focus, .swagger-ui .hover-bg-hot-pink:hover { background-color: #c03086; } - - .swagger-ui .bg-pink, .swagger-ui .hover-bg-pink:focus, .swagger-ui .hover-bg-pink:hover { background-color: #8f2464; } - - .swagger-ui .bg-light-pink, .swagger-ui .hover-bg-light-pink:focus, .swagger-ui .hover-bg-light-pink:hover { background-color: #721d4d; } - - .swagger-ui .bg-dark-green, .swagger-ui .hover-bg-dark-green:focus, .swagger-ui .hover-bg-dark-green:hover { background-color: #1c6e50; } - - .swagger-ui .bg-green, .swagger-ui .hover-bg-green:focus, .swagger-ui .hover-bg-green:hover { background-color: #279b70; } - - .swagger-ui .bg-light-green, .swagger-ui .hover-bg-light-green:focus, .swagger-ui .hover-bg-light-green:hover { background-color: #228762; } - - .swagger-ui .bg-navy, .swagger-ui .hover-bg-navy:focus, .swagger-ui .hover-bg-navy:hover { background-color: #0d1d35; } - - .swagger-ui .bg-dark-blue, .swagger-ui .hover-bg-dark-blue:focus, .swagger-ui .hover-bg-dark-blue:hover { background-color: #20497e; } - - .swagger-ui .bg-blue, .swagger-ui .hover-bg-blue:focus, .swagger-ui .hover-bg-blue:hover { background-color: #4380d0; } - - .swagger-ui .bg-light-blue, .swagger-ui .hover-bg-light-blue:focus, .swagger-ui .hover-bg-light-blue:hover { background-color: #20517e; } - - .swagger-ui .bg-lightest-blue, .swagger-ui .hover-bg-lightest-blue:focus, .swagger-ui .hover-bg-lightest-blue:hover { background-color: #143a52; } - - .swagger-ui .bg-washed-blue, .swagger-ui .hover-bg-washed-blue:focus, .swagger-ui .hover-bg-washed-blue:hover { background-color: #0c312d; } - - .swagger-ui .bg-washed-green, .swagger-ui .hover-bg-washed-green:focus, .swagger-ui .hover-bg-washed-green:hover { background-color: #0f3d2c; } - - .swagger-ui .bg-washed-red, .swagger-ui .hover-bg-washed-red:focus, .swagger-ui .hover-bg-washed-red:hover { background-color: #411010; } - - .swagger-ui .bg-inherit, .swagger-ui .hover-bg-inherit:focus, .swagger-ui .hover-bg-inherit:hover { background-color: inherit; } - - .swagger-ui .shadow-hover { transition: all .5s cubic-bezier(.165, .84, .44, 1) 0s; } - - .swagger-ui .shadow-hover::after { - border-radius: inherit; - box-shadow: rgba(0, 0, 0, .2) 0 0 16px 2px; - content: ""; - height: 100%; - left: 0; - opacity: 0; - position: absolute; - top: 0; - transition: opacity .5s cubic-bezier(.165, .84, .44, 1) 0s; - width: 100%; - z-index: -1; - } - - .swagger-ui .bg-animate, .swagger-ui .bg-animate:focus, .swagger-ui .bg-animate:hover { transition: background-color .15s ease-in-out 0s; } - - .swagger-ui .nested-links a { - color: #99bae6; - transition: color .15s ease-in 0s; - } - - .swagger-ui .nested-links a:focus, .swagger-ui .nested-links a:hover { - color: #a9cbea; - transition: color .15s ease-in 0s; - } - - .swagger-ui .opblock-tag { - border-bottom: 1px solid rgba(58, 64, 80, .3); - color: #b5bac9; - transition: all .2s ease 0s; - } - - .swagger-ui .opblock-tag svg, .swagger-ui section.models h4 svg { transition: all .4s ease 0s; } - - .swagger-ui .opblock { - border: 1px solid #000; - border-radius: 4px; - box-shadow: rgba(0, 0, 0, .19) 0 0 3px; - margin: 0 0 15px; - } - - .swagger-ui .opblock .tab-header .tab-item.active h4 span::after { background: gray; } - - .swagger-ui .opblock.is-open .opblock-summary { border-bottom: 1px solid #000; } - - .swagger-ui .opblock .opblock-section-header { - background: rgba(28, 28, 33, .8); - box-shadow: rgba(0, 0, 0, .1) 0 1px 2px; - } - - .swagger-ui .opblock .opblock-section-header > label > span { padding: 0 10px 0 0; } - - .swagger-ui .opblock .opblock-summary-method { - background: #000; - color: #fff; - text-shadow: rgba(0, 0, 0, .1) 0 1px 0; - } - - .swagger-ui .opblock.opblock-post { - background: rgba(72, 203, 144, .1); - border-color: #48cb90; - } - - .swagger-ui .opblock.opblock-post .opblock-summary-method, .swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after { background: #48cb90; } - - .swagger-ui .opblock.opblock-post .opblock-summary { border-color: #48cb90; } - - .swagger-ui .opblock.opblock-put { - background: rgba(213, 157, 88, .1); - border-color: #d59d58; - } - - .swagger-ui .opblock.opblock-put .opblock-summary-method, .swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span::after { background: #d59d58; } - - .swagger-ui .opblock.opblock-put .opblock-summary { border-color: #d59d58; } - - .swagger-ui .opblock.opblock-delete { - background: rgba(200, 50, 50, .1); - border-color: #c83232; - } - - .swagger-ui .opblock.opblock-delete .opblock-summary-method, .swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after { background: #c83232; } - - .swagger-ui .opblock.opblock-delete .opblock-summary { border-color: #c83232; } - - .swagger-ui .opblock.opblock-get { - background: rgba(42, 105, 167, .1); - border-color: #2a69a7; - } - - .swagger-ui .opblock.opblock-get .opblock-summary-method, .swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after { background: #2a69a7; } - - .swagger-ui .opblock.opblock-get .opblock-summary { border-color: #2a69a7; } - - .swagger-ui .opblock.opblock-patch { - background: rgba(92, 214, 188, .1); - border-color: #5cd6bc; - } - - .swagger-ui .opblock.opblock-patch .opblock-summary-method, .swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span::after { background: #5cd6bc; } - - .swagger-ui .opblock.opblock-patch .opblock-summary { border-color: #5cd6bc; } - - .swagger-ui .opblock.opblock-head { - background: rgba(140, 63, 207, .1); - border-color: #8c3fcf; - } - - .swagger-ui .opblock.opblock-head .opblock-summary-method, .swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span::after { background: #8c3fcf; } - - .swagger-ui .opblock.opblock-head .opblock-summary { border-color: #8c3fcf; } - - .swagger-ui .opblock.opblock-options { - background: rgba(36, 89, 143, .1); - border-color: #24598f; - } - - .swagger-ui .opblock.opblock-options .opblock-summary-method, .swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span::after { background: #24598f; } - - .swagger-ui .opblock.opblock-options .opblock-summary { border-color: #24598f; } - - .swagger-ui .opblock.opblock-deprecated { - background: rgba(46, 46, 46, .1); - border-color: #2e2e2e; - opacity: .6; - } - - .swagger-ui .opblock.opblock-deprecated .opblock-summary-method, .swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span::after { background: #2e2e2e; } - - .swagger-ui .opblock.opblock-deprecated .opblock-summary { border-color: #2e2e2e; } - - .swagger-ui .filter .operation-filter-input { border: 2px solid #2b3446; } - - .swagger-ui .tab li:first-of-type::after { background: rgba(0, 0, 0, .2); } - - .swagger-ui .download-contents { - background: #7c8192; - color: #fff; - } - - .swagger-ui .scheme-container { - background: #1c1c21; - box-shadow: rgba(0, 0, 0, .15) 0 1px 2px 0; - } - - .swagger-ui .loading-container .loading::before { - animation: 1s linear 0s infinite normal none running rotation, .5s ease 0s 1 normal none running opacity; - border-color: rgba(0, 0, 0, .6) rgba(84, 84, 84, .1) rgba(84, 84, 84, .1); - } - - .swagger-ui .response-control-media-type--accept-controller select { border-color: #196619; } - - .swagger-ui .response-control-media-type__accept-message { color: #99e699; } - - .swagger-ui .version-pragma__message code { background-color: #3b3b3b; } - - .swagger-ui .btn { - background: 0 0; - border: 2px solid gray; - box-shadow: rgba(0, 0, 0, .1) 0 1px 2px; - color: #b5bac9; - } - - .swagger-ui .btn:hover { box-shadow: rgba(0, 0, 0, .3) 0 0 5px; } - - .swagger-ui .btn.authorize, .swagger-ui .btn.cancel { - background-color: transparent; - border-color: #a72a2a; - color: #e69999; - } - - .swagger-ui .btn.cancel:hover { - background-color: #a72a2a; - color: #fff; - } - - .swagger-ui .btn.authorize { - border-color: #48cb90; - color: #9ce3c3; - } - - .swagger-ui .btn.authorize svg { fill: #9ce3c3; } - - .btn.authorize.unlocked:hover { - background-color: #48cb90; - color: #fff; - } - - .btn.authorize.unlocked:hover svg { - fill: #fbfbfb; - } - - .swagger-ui .btn.execute { - background-color: #5892d5; - border-color: #5892d5; - color: #fff; - } - - .swagger-ui .copy-to-clipboard { background: #7c8192; } - - .swagger-ui .copy-to-clipboard button { background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat; } - - .swagger-ui select { - background: url("data:image/svg+xml;charset=utf-8,") right 10px center/20px no-repeat #212121; - background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQuc3ZnIgogICBpZD0ic3ZnNCIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMjAgMjAiPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTEwIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzOCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICAgIGlua3NjYXBlOndpbmRvdy15PSItOSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTkiCiAgICAgaW5rc2NhcGU6Y3k9IjEwIgogICAgIGlua3NjYXBlOmN4PSIxMCIKICAgICBpbmtzY2FwZTp6b29tPSI0MS41IgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEzLjQxOCA3Ljg1OWEuNjk1LjY5NSAwIDAxLjk3OCAwIC42OC42OCAwIDAxMCAuOTY5bC0zLjkwOCAzLjgzYS42OTcuNjk3IDAgMDEtLjk3OSAwbC0zLjkwOC0zLjgzYS42OC42OCAwIDAxMC0uOTY5LjY5NS42OTUgMCAwMS45NzggMEwxMCAxMWwzLjQxOC0zLjE0MXoiIC8+Cjwvc3ZnPgo=) right 10px center/20px no-repeat #1c1c21; - border: 2px solid #41444e; - } - - .swagger-ui select[multiple] { background: #212121; } - - .swagger-ui button.invalid, .swagger-ui input[type=email].invalid, .swagger-ui input[type=file].invalid, .swagger-ui input[type=password].invalid, .swagger-ui input[type=search].invalid, .swagger-ui input[type=text].invalid, .swagger-ui select.invalid, .swagger-ui textarea.invalid { - background: #390e0e; - border-color: #c83232; - } - - .swagger-ui input[type=email], .swagger-ui input[type=file], .swagger-ui input[type=password], .swagger-ui input[type=search], .swagger-ui input[type=text], .swagger-ui textarea { - background: #1c1c21; - border: 1px solid #404040; - } - - .swagger-ui textarea { - background: rgba(28, 28, 33, .8); - color: #b5bac9; - } - - .swagger-ui input[disabled], .swagger-ui select[disabled] { - background-color: #1f1f1f; - color: #bfbfbf; - } - - .swagger-ui textarea[disabled] { - background-color: #41444e; - color: #fff; - } - - .swagger-ui select[disabled] { border-color: #878787; } - - .swagger-ui textarea:focus { border: 2px solid #2a69a7; } - - .swagger-ui .checkbox input[type=checkbox] + label > .item { - background: #303030; - box-shadow: #303030 0 0 0 2px; - } - - .swagger-ui .checkbox input[type=checkbox]:checked + label > .item { background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat #303030; } - - .swagger-ui .dialog-ux .backdrop-ux { background: rgba(0, 0, 0, .8); } - - .swagger-ui .dialog-ux .modal-ux { - background: #1c1c21; - border: 1px solid #2e2e2e; - box-shadow: rgba(0, 0, 0, .2) 0 10px 30px 0; - } - - .swagger-ui .dialog-ux .modal-ux-header .close-modal { background: 0 0; } - - .swagger-ui .model .deprecated span, .swagger-ui .model .deprecated td { color: #bfbfbf !important; } - - .swagger-ui .model-toggle::after { background: url("data:image/svg+xml;charset=utf-8,") 50% center/100% no-repeat; } - - .swagger-ui .model-hint { - background: rgba(0, 0, 0, .7); - color: #ebebeb; - } - - .swagger-ui section.models { border: 1px solid rgba(58, 64, 80, .3); } - - .swagger-ui section.models.is-open h4 { border-bottom: 1px solid rgba(58, 64, 80, .3); } - - .swagger-ui section.models .model-container { background: rgba(0, 0, 0, .05); } - - .swagger-ui section.models .model-container:hover { background: rgba(0, 0, 0, .07); } - - .swagger-ui .model-box { background: rgba(0, 0, 0, .1); } - - .swagger-ui .prop-type { color: #aaaad4; } - - .swagger-ui table thead tr td, .swagger-ui table thead tr th { - border-bottom: 1px solid rgba(58, 64, 80, .2); - color: #b5bac9; - } - - .swagger-ui .parameter__name.required::after { color: rgba(230, 153, 153, .6); } - - .swagger-ui .topbar .download-url-wrapper .select-label { color: #f0f0f0; } - - .swagger-ui .topbar .download-url-wrapper .download-url-button { - background: #63a040; - color: #fff; - } - - .swagger-ui .info .title small { background: #7c8492; } - - .swagger-ui .info .title small.version-stamp { background-color: #7a9b27; } - - .swagger-ui .auth-container .errors { - background-color: #350d0d; - color: #b5bac9; - } - - .swagger-ui .errors-wrapper { - background: rgba(200, 50, 50, .1); - border: 2px solid #c83232; - } - - .swagger-ui .markdown code, .swagger-ui .renderedmarkdown code { - background: rgba(0, 0, 0, .05); - color: #c299e6; - } - - .swagger-ui .model-toggle:after { background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQyLnN2ZyIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgaGVpZ2h0PSIyNCIKICAgd2lkdGg9IjI0Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGExMCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMKICAgICBpZD0iZGVmczgiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTkiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9Ii05IgogICAgIGlua3NjYXBlOmN5PSIxMiIKICAgICBpbmtzY2FwZTpjeD0iMTIiCiAgICAgaW5rc2NhcGU6em9vbT0iMzQuNTgzMzMzIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02eiIgLz4KPC9zdmc+Cg==) 50% no-repeat; } - - /* arrows for each operation and request are now white */ - .arrow, #large-arrow-up { fill: #fff; } - - #unlocked, #locked { fill: #fff; } - - ::-webkit-scrollbar-track { background-color: #646464 !important; } - - ::-webkit-scrollbar-thumb { - background-color: #242424 !important; - border: 2px solid #3e4346 !important; - } - - ::-webkit-scrollbar-button:vertical:start:decrement { - background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), linear-gradient(230deg, #696969 40%, transparent 41%), linear-gradient(0deg, #696969 40%, transparent 31%); - background-color: #b6b6b6; - } - - ::-webkit-scrollbar-button:vertical:end:increment { - background: linear-gradient(310deg, #696969 40%, transparent 41%), linear-gradient(50deg, #696969 40%, transparent 41%), linear-gradient(180deg, #696969 40%, transparent 31%); - background-color: #b6b6b6; - } - - ::-webkit-scrollbar-button:horizontal:end:increment { - background: linear-gradient(210deg, #696969 40%, transparent 41%), linear-gradient(330deg, #696969 40%, transparent 41%), linear-gradient(90deg, #696969 30%, transparent 31%); - background-color: #b6b6b6; - } - - ::-webkit-scrollbar-button:horizontal:start:decrement { - background: linear-gradient(30deg, #696969 40%, transparent 41%), linear-gradient(150deg, #696969 40%, transparent 41%), linear-gradient(270deg, #696969 30%, transparent 31%); - background-color: #b6b6b6; - } - - ::-webkit-scrollbar-button, ::-webkit-scrollbar-track-piece { background-color: #3e4346 !important; } - - .swagger-ui .black, .swagger-ui .checkbox, .swagger-ui .dark-gray, .swagger-ui .download-url-wrapper .loading, .swagger-ui .errors-wrapper .errors small, .swagger-ui .fallback, .swagger-ui .filter .loading, .swagger-ui .gray, .swagger-ui .hover-black:focus, .swagger-ui .hover-black:hover, .swagger-ui .hover-dark-gray:focus, .swagger-ui .hover-dark-gray:hover, .swagger-ui .hover-gray:focus, .swagger-ui .hover-gray:hover, .swagger-ui .hover-light-silver:focus, .swagger-ui .hover-light-silver:hover, .swagger-ui .hover-mid-gray:focus, .swagger-ui .hover-mid-gray:hover, .swagger-ui .hover-near-black:focus, .swagger-ui .hover-near-black:hover, .swagger-ui .hover-silver:focus, .swagger-ui .hover-silver:hover, .swagger-ui .light-silver, .swagger-ui .markdown pre, .swagger-ui .mid-gray, .swagger-ui .model .property, .swagger-ui .model .property.primitive, .swagger-ui .model-title, .swagger-ui .near-black, .swagger-ui .parameter__extension, .swagger-ui .parameter__in, .swagger-ui .prop-format, .swagger-ui .renderedmarkdown pre, .swagger-ui .response-col_links .response-undocumented, .swagger-ui .response-col_status .response-undocumented, .swagger-ui .silver, .swagger-ui section.models h4, .swagger-ui section.models h5, .swagger-ui span.token-not-formatted, .swagger-ui span.token-string, .swagger-ui table.headers .header-example, .swagger-ui table.model tr.description, .swagger-ui table.model tr.extension { color: #bfbfbf; } - - .swagger-ui .hover-white:focus, .swagger-ui .hover-white:hover, .swagger-ui .info .title small pre, .swagger-ui .topbar a, .swagger-ui .white { color: #fff; } - - .swagger-ui .bg-black-10, .swagger-ui .hover-bg-black-10:focus, .swagger-ui .hover-bg-black-10:hover, .swagger-ui .stripe-dark:nth-child(2n + 1) { background-color: rgba(0, 0, 0, .1); } - - .swagger-ui .bg-white-10, .swagger-ui .hover-bg-white-10:focus, .swagger-ui .hover-bg-white-10:hover, .swagger-ui .stripe-light:nth-child(2n + 1) { background-color: rgba(28, 28, 33, .1); } - - .swagger-ui .bg-light-silver, .swagger-ui .hover-bg-light-silver:focus, .swagger-ui .hover-bg-light-silver:hover, .swagger-ui .striped--light-silver:nth-child(2n + 1) { background-color: #6e6e6e; } - - .swagger-ui .bg-moon-gray, .swagger-ui .hover-bg-moon-gray:focus, .swagger-ui .hover-bg-moon-gray:hover, .swagger-ui .striped--moon-gray:nth-child(2n + 1) { background-color: #4d4d4d; } - - .swagger-ui .bg-light-gray, .swagger-ui .hover-bg-light-gray:focus, .swagger-ui .hover-bg-light-gray:hover, .swagger-ui .striped--light-gray:nth-child(2n + 1) { background-color: #2b2b2b; } - - .swagger-ui .bg-near-white, .swagger-ui .hover-bg-near-white:focus, .swagger-ui .hover-bg-near-white:hover, .swagger-ui .striped--near-white:nth-child(2n + 1) { background-color: #242424; } - - .swagger-ui .opblock-tag:hover, .swagger-ui section.models h4:hover { background: rgba(0, 0, 0, .02); } - - .swagger-ui .checkbox p, .swagger-ui .dialog-ux .modal-ux-content h4, .swagger-ui .dialog-ux .modal-ux-content p, .swagger-ui .dialog-ux .modal-ux-header h3, .swagger-ui .errors-wrapper .errors h4, .swagger-ui .errors-wrapper hgroup h4, .swagger-ui .info .base-url, .swagger-ui .info .title, .swagger-ui .info h1, .swagger-ui .info h2, .swagger-ui .info h3, .swagger-ui .info h4, .swagger-ui .info h5, .swagger-ui .info li, .swagger-ui .info p, .swagger-ui .info table, .swagger-ui .loading-container .loading::after, .swagger-ui .model, .swagger-ui .opblock .opblock-section-header h4, .swagger-ui .opblock .opblock-section-header > label, .swagger-ui .opblock .opblock-summary-description, .swagger-ui .opblock .opblock-summary-operation-id, .swagger-ui .opblock .opblock-summary-path, .swagger-ui .opblock .opblock-summary-path__deprecated, .swagger-ui .opblock-description-wrapper, .swagger-ui .opblock-description-wrapper h4, .swagger-ui .opblock-description-wrapper p, .swagger-ui .opblock-external-docs-wrapper, .swagger-ui .opblock-external-docs-wrapper h4, .swagger-ui .opblock-external-docs-wrapper p, .swagger-ui .opblock-tag small, .swagger-ui .opblock-title_normal, .swagger-ui .opblock-title_normal h4, .swagger-ui .opblock-title_normal p, .swagger-ui .parameter__name, .swagger-ui .parameter__type, .swagger-ui .response-col_links, .swagger-ui .response-col_status, .swagger-ui .responses-inner h4, .swagger-ui .responses-inner h5, .swagger-ui .scheme-container .schemes > label, .swagger-ui .scopes h2, .swagger-ui .servers > label, .swagger-ui .tab li, .swagger-ui label, .swagger-ui select, .swagger-ui table.headers td { color: #b5bac9; } - - .swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed, .swagger-ui .model-deprecated-warning, .swagger-ui .parameter__deprecated, .swagger-ui .parameter__name.required span, .swagger-ui table.model tr.property-row .star { color: #e69999; } - - .swagger-ui .opblock-body pre.microlight, .swagger-ui textarea.curl { - background: #41444e; - border-radius: 4px; - color: #fff; - } - - .swagger-ui .expand-methods svg, .swagger-ui .expand-methods:hover svg { fill: #bfbfbf; } - - .swagger-ui .auth-container, .swagger-ui .dialog-ux .modal-ux-header { border-bottom: 1px solid #2e2e2e; } - - .swagger-ui .topbar .download-url-wrapper .select-label select, .swagger-ui .topbar .download-url-wrapper input[type=text] { border: 2px solid #63a040; } - - .swagger-ui .info a, .swagger-ui .info a:hover, .swagger-ui .scopes h2 a { color: #99bde6; } - - /* Dark Scrollbar */ - ::-webkit-scrollbar { - width: 14px; - height: 14px; - } - - ::-webkit-scrollbar-button { - background-color: #3e4346 !important; - } - - ::-webkit-scrollbar-track { - background-color: #646464 !important; - } - - ::-webkit-scrollbar-track-piece { - background-color: #3e4346 !important; - } - - ::-webkit-scrollbar-thumb { - height: 50px; - background-color: #242424 !important; - border: 2px solid #3e4346 !important; - } - - ::-webkit-scrollbar-corner {} - - ::-webkit-resizer {} - - ::-webkit-scrollbar-button:vertical:start:decrement { - background: - linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), - linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%); - background-color: #b6b6b6; - } - - ::-webkit-scrollbar-button:vertical:end:increment { - background: - linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%); - background-color: #b6b6b6; - } - - ::-webkit-scrollbar-button:horizontal:end:increment { - background: - linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%); - background-color: #b6b6b6; - } - - ::-webkit-scrollbar-button:horizontal:start:decrement { - background: - linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%), - linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%); - background-color: #b6b6b6; - } -} +@media only screen and (prefers-color-scheme:dark){a{color:#8c8cfa}embed[type="application/pdf"]{filter:invert(90%)}html{background:#1f1f1f!important;box-sizing:border-box;filter:contrast(100%) brightness(100%) saturate(100%);overflow-y:scroll}body{background:#1f1f1f;background-image:none!important}.swagger-ui input[disabled],.swagger-ui select[disabled],button,input,select,textarea{background-color:#1f1f1f;color:#bfbfbf}font,html{color:#bfbfbf}.swagger-ui,.swagger-ui .checkbox p,.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3,.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4,.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table,.swagger-ui .loading-container .loading::after,.swagger-ui .model,.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label,.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated,.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-tag small,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .parameter__name,.swagger-ui .parameter__type,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5,.swagger-ui .scheme-container .schemes>label,.swagger-ui .scopes h2,.swagger-ui .servers>label,.swagger-ui .tab li,.swagger-ui label,.swagger-ui section h3,.swagger-ui select,.swagger-ui table.headers td{color:#b5bac9}.swagger-ui a{background-color:transparent}.swagger-ui mark{background-color:#664b00;color:#bfbfbf}.swagger-ui .color-inherit,.swagger-ui .hover-inherit:focus,.swagger-ui .hover-inherit:hover,.swagger-ui legend{color:inherit}.swagger-ui .debug *{outline:#e6da99 solid 1px}.swagger-ui .debug-white *{outline:#fff solid 1px}.swagger-ui .debug-black *{outline:#bfbfbf solid 1px}.swagger-ui .debug-grid{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==)}.swagger-ui .debug-grid-16{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC)}.swagger-ui .debug-grid-8-solid{background:url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) #1c1c21}.swagger-ui .debug-grid-16-solid{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) #1c1c21}.swagger-ui .b--black{border-color:#000}.swagger-ui .b--near-black{border-color:#121212}.swagger-ui .b--dark-gray{border-color:#333}.swagger-ui .b--mid-gray{border-color:#545454}.swagger-ui .b--gray{border-color:#787878}.swagger-ui .b--silver{border-color:#999}.swagger-ui .b--light-silver{border-color:#6e6e6e}.swagger-ui .b--moon-gray{border-color:#4d4d4d}.swagger-ui .b--light-gray{border-color:#2b2b2b}.swagger-ui .b--near-white{border-color:#242424}.swagger-ui .b--white{border-color:#1c1c21}.swagger-ui .b--white-90{border-color:rgba(28,28,33,.9)}.swagger-ui .b--white-80{border-color:rgba(28,28,33,.8)}.swagger-ui .b--white-70{border-color:rgba(28,28,33,.7)}.swagger-ui .b--white-60{border-color:rgba(28,28,33,.6)}.swagger-ui .b--white-50{border-color:rgba(28,28,33,.5)}.swagger-ui .b--white-40{border-color:rgba(28,28,33,.4)}.swagger-ui .b--white-30{border-color:rgba(28,28,33,.3)}.swagger-ui .b--white-20{border-color:rgba(28,28,33,.2)}.swagger-ui .b--white-10{border-color:rgba(28,28,33,.1)}.swagger-ui .b--white-05{border-color:rgba(28,28,33,.05)}.swagger-ui .b--white-025{border-color:rgba(28,28,33,.024)}.swagger-ui .b--white-0125{border-color:rgba(28,28,33,.01)}.swagger-ui .b--black-90{border-color:rgba(0,0,0,.9)}.swagger-ui .b--black-80{border-color:rgba(0,0,0,.8)}.swagger-ui .b--black-70{border-color:rgba(0,0,0,.7)}.swagger-ui .b--black-60{border-color:rgba(0,0,0,.6)}.swagger-ui .b--black-50{border-color:rgba(0,0,0,.5)}.swagger-ui .b--black-40{border-color:rgba(0,0,0,.4)}.swagger-ui .b--black-30{border-color:rgba(0,0,0,.3)}.swagger-ui .b--black-20{border-color:rgba(0,0,0,.2)}.swagger-ui .b--black-10{border-color:rgba(0,0,0,.1)}.swagger-ui .b--black-05{border-color:rgba(0,0,0,.05)}.swagger-ui .b--black-025{border-color:rgba(0,0,0,.024)}.swagger-ui .b--black-0125{border-color:rgba(0,0,0,.01)}.swagger-ui .b--dark-red{border-color:#bc2f36}.swagger-ui .b--red{border-color:#c83932}.swagger-ui .b--light-red{border-color:#ab3c2b}.swagger-ui .b--orange{border-color:#cc6e33}.swagger-ui .b--purple{border-color:#5e2ca5}.swagger-ui .b--light-purple{border-color:#672caf}.swagger-ui .b--dark-pink{border-color:#ab2b81}.swagger-ui .b--hot-pink{border-color:#c03086}.swagger-ui .b--pink{border-color:#8f2464}.swagger-ui .b--light-pink{border-color:#721d4d}.swagger-ui .b--dark-green{border-color:#1c6e50}.swagger-ui .b--green{border-color:#279b70}.swagger-ui .b--light-green{border-color:#228762}.swagger-ui .b--navy{border-color:#0d1d35}.swagger-ui .b--dark-blue{border-color:#20497e}.swagger-ui .b--blue{border-color:#4380d0}.swagger-ui .b--light-blue{border-color:#20517e}.swagger-ui .b--lightest-blue{border-color:#143a52}.swagger-ui .b--washed-blue{border-color:#0c312d}.swagger-ui .b--washed-green{border-color:#0f3d2c}.swagger-ui .b--washed-red{border-color:#411010}.swagger-ui .b--transparent{border-color:transparent}.swagger-ui .b--gold,.swagger-ui .b--light-yellow,.swagger-ui .b--washed-yellow,.swagger-ui .b--yellow{border-color:#664b00}.swagger-ui .shadow-1{box-shadow:rgba(0,0,0,.2) 0 0 4px 2px}.swagger-ui .shadow-2{box-shadow:rgba(0,0,0,.2) 0 0 8px 2px}.swagger-ui .shadow-3{box-shadow:rgba(0,0,0,.2) 2px 2px 4px 2px}.swagger-ui .shadow-4{box-shadow:rgba(0,0,0,.2) 2px 2px 8px 0}.swagger-ui .shadow-5{box-shadow:rgba(0,0,0,.2) 4px 4px 8px 0}@media screen and (min-width:30em){.swagger-ui .shadow-1-ns{box-shadow:rgba(0,0,0,.2) 0 0 4px 2px}.swagger-ui .shadow-2-ns{box-shadow:rgba(0,0,0,.2) 0 0 8px 2px}.swagger-ui .shadow-3-ns{box-shadow:rgba(0,0,0,.2) 2px 2px 4px 2px}.swagger-ui .shadow-4-ns{box-shadow:rgba(0,0,0,.2) 2px 2px 8px 0}.swagger-ui .shadow-5-ns{box-shadow:rgba(0,0,0,.2) 4px 4px 8px 0}}@media screen and (max-width:60em) and (min-width:30em){.swagger-ui .shadow-1-m{box-shadow:rgba(0,0,0,.2) 0 0 4px 2px}.swagger-ui .shadow-2-m{box-shadow:rgba(0,0,0,.2) 0 0 8px 2px}.swagger-ui .shadow-3-m{box-shadow:rgba(0,0,0,.2) 2px 2px 4px 2px}.swagger-ui .shadow-4-m{box-shadow:rgba(0,0,0,.2) 2px 2px 8px 0}.swagger-ui .shadow-5-m{box-shadow:rgba(0,0,0,.2) 4px 4px 8px 0}}@media screen and (min-width:60em){.swagger-ui .shadow-1-l{box-shadow:rgba(0,0,0,.2) 0 0 4px 2px}.swagger-ui .shadow-2-l{box-shadow:rgba(0,0,0,.2) 0 0 8px 2px}.swagger-ui .shadow-3-l{box-shadow:rgba(0,0,0,.2) 2px 2px 4px 2px}.swagger-ui .shadow-4-l{box-shadow:rgba(0,0,0,.2) 2px 2px 8px 0}.swagger-ui .shadow-5-l{box-shadow:rgba(0,0,0,.2) 4px 4px 8px 0}}.swagger-ui .black-05{color:rgba(191,191,191,.05)}.swagger-ui .bg-black-05{background-color:rgba(0,0,0,.05)}.swagger-ui .black-90,.swagger-ui .hover-black-90:focus,.swagger-ui .hover-black-90:hover{color:rgba(191,191,191,.9)}.swagger-ui .black-80,.swagger-ui .hover-black-80:focus,.swagger-ui .hover-black-80:hover{color:rgba(191,191,191,.8)}.swagger-ui .black-70,.swagger-ui .hover-black-70:focus,.swagger-ui .hover-black-70:hover{color:rgba(191,191,191,.7)}.swagger-ui .black-60,.swagger-ui .hover-black-60:focus,.swagger-ui .hover-black-60:hover{color:rgba(191,191,191,.6)}.swagger-ui .black-50,.swagger-ui .hover-black-50:focus,.swagger-ui .hover-black-50:hover{color:rgba(191,191,191,.5)}.swagger-ui .black-40,.swagger-ui .hover-black-40:focus,.swagger-ui .hover-black-40:hover{color:rgba(191,191,191,.4)}.swagger-ui .black-30,.swagger-ui .hover-black-30:focus,.swagger-ui .hover-black-30:hover{color:rgba(191,191,191,.3)}.swagger-ui .black-20,.swagger-ui .hover-black-20:focus,.swagger-ui .hover-black-20:hover{color:rgba(191,191,191,.2)}.swagger-ui .black-10,.swagger-ui .hover-black-10:focus,.swagger-ui .hover-black-10:hover{color:rgba(191,191,191,.1)}.swagger-ui .hover-white-90:focus,.swagger-ui .hover-white-90:hover,.swagger-ui .white-90{color:rgba(255,255,255,.9)}.swagger-ui .hover-white-80:focus,.swagger-ui .hover-white-80:hover,.swagger-ui .white-80{color:rgba(255,255,255,.8)}.swagger-ui .hover-white-70:focus,.swagger-ui .hover-white-70:hover,.swagger-ui .white-70{color:rgba(255,255,255,.7)}.swagger-ui .hover-white-60:focus,.swagger-ui .hover-white-60:hover,.swagger-ui .white-60{color:rgba(255,255,255,.6)}.swagger-ui .hover-white-50:focus,.swagger-ui .hover-white-50:hover,.swagger-ui .white-50{color:rgba(255,255,255,.5)}.swagger-ui .hover-white-40:focus,.swagger-ui .hover-white-40:hover,.swagger-ui .white-40{color:rgba(255,255,255,.4)}.swagger-ui .hover-white-30:focus,.swagger-ui .hover-white-30:hover,.swagger-ui .white-30{color:rgba(255,255,255,.3)}.swagger-ui .hover-white-20:focus,.swagger-ui .hover-white-20:hover,.swagger-ui .white-20{color:rgba(255,255,255,.2)}.swagger-ui .hover-white-10:focus,.swagger-ui .hover-white-10:hover,.swagger-ui .white-10{color:rgba(255,255,255,.1)}.swagger-ui .hover-moon-gray:focus,.swagger-ui .hover-moon-gray:hover,.swagger-ui .moon-gray{color:#ccc}.swagger-ui .hover-light-gray:focus,.swagger-ui .hover-light-gray:hover,.swagger-ui .light-gray{color:#ededed}.swagger-ui .hover-near-white:focus,.swagger-ui .hover-near-white:hover,.swagger-ui .near-white{color:#f5f5f5}.swagger-ui .dark-red,.swagger-ui .hover-dark-red:focus,.swagger-ui .hover-dark-red:hover{color:#e6999d}.swagger-ui .hover-red:focus,.swagger-ui .hover-red:hover,.swagger-ui .red{color:#e69d99}.swagger-ui .hover-light-red:focus,.swagger-ui .hover-light-red:hover,.swagger-ui .light-red{color:#e6a399}.swagger-ui .hover-orange:focus,.swagger-ui .hover-orange:hover,.swagger-ui .orange{color:#e6b699}.swagger-ui .gold,.swagger-ui .hover-gold:focus,.swagger-ui .hover-gold:hover{color:#e6d099}.swagger-ui .hover-yellow:focus,.swagger-ui .hover-yellow:hover,.swagger-ui .yellow{color:#e6da99}.swagger-ui .hover-light-yellow:focus,.swagger-ui .hover-light-yellow:hover,.swagger-ui .light-yellow{color:#ede6b6}.swagger-ui .hover-purple:focus,.swagger-ui .hover-purple:hover,.swagger-ui .purple{color:#b99ae4}.swagger-ui .hover-light-purple:focus,.swagger-ui .hover-light-purple:hover,.swagger-ui .light-purple{color:#bb99e6}.swagger-ui .dark-pink,.swagger-ui .hover-dark-pink:focus,.swagger-ui .hover-dark-pink:hover{color:#e699cc}.swagger-ui .hot-pink,.swagger-ui .hover-hot-pink:focus,.swagger-ui .hover-hot-pink:hover,.swagger-ui .hover-pink:focus,.swagger-ui .hover-pink:hover,.swagger-ui .pink{color:#e699c7}.swagger-ui .hover-light-pink:focus,.swagger-ui .hover-light-pink:hover,.swagger-ui .light-pink{color:#edb6d5}.swagger-ui .dark-green,.swagger-ui .green,.swagger-ui .hover-dark-green:focus,.swagger-ui .hover-dark-green:hover,.swagger-ui .hover-green:focus,.swagger-ui .hover-green:hover{color:#99e6c9}.swagger-ui .hover-light-green:focus,.swagger-ui .hover-light-green:hover,.swagger-ui .light-green{color:#a1e8ce}.swagger-ui .hover-navy:focus,.swagger-ui .hover-navy:hover,.swagger-ui .navy{color:#99b8e6}.swagger-ui .blue,.swagger-ui .dark-blue,.swagger-ui .hover-blue:focus,.swagger-ui .hover-blue:hover,.swagger-ui .hover-dark-blue:focus,.swagger-ui .hover-dark-blue:hover{color:#99bae6}.swagger-ui .hover-light-blue:focus,.swagger-ui .hover-light-blue:hover,.swagger-ui .light-blue{color:#a9cbea}.swagger-ui .hover-lightest-blue:focus,.swagger-ui .hover-lightest-blue:hover,.swagger-ui .lightest-blue{color:#d6e9f5}.swagger-ui .hover-washed-blue:focus,.swagger-ui .hover-washed-blue:hover,.swagger-ui .washed-blue{color:#f7fdfc}.swagger-ui .hover-washed-green:focus,.swagger-ui .hover-washed-green:hover,.swagger-ui .washed-green{color:#ebfaf4}.swagger-ui .hover-washed-yellow:focus,.swagger-ui .hover-washed-yellow:hover,.swagger-ui .washed-yellow{color:#fbf9ef}.swagger-ui .hover-washed-red:focus,.swagger-ui .hover-washed-red:hover,.swagger-ui .washed-red{color:#f9e7e7}.swagger-ui .bg-black-90,.swagger-ui .hover-bg-black-90:focus,.swagger-ui .hover-bg-black-90:hover{background-color:rgba(0,0,0,.9)}.swagger-ui .bg-black-80,.swagger-ui .hover-bg-black-80:focus,.swagger-ui .hover-bg-black-80:hover{background-color:rgba(0,0,0,.8)}.swagger-ui .bg-black-70,.swagger-ui .hover-bg-black-70:focus,.swagger-ui .hover-bg-black-70:hover{background-color:rgba(0,0,0,.7)}.swagger-ui .bg-black-60,.swagger-ui .hover-bg-black-60:focus,.swagger-ui .hover-bg-black-60:hover{background-color:rgba(0,0,0,.6)}.swagger-ui .bg-black-50,.swagger-ui .hover-bg-black-50:focus,.swagger-ui .hover-bg-black-50:hover{background-color:rgba(0,0,0,.5)}.swagger-ui .bg-black-40,.swagger-ui .hover-bg-black-40:focus,.swagger-ui .hover-bg-black-40:hover{background-color:rgba(0,0,0,.4)}.swagger-ui .bg-black-30,.swagger-ui .hover-bg-black-30:focus,.swagger-ui .hover-bg-black-30:hover{background-color:rgba(0,0,0,.3)}.swagger-ui .bg-black-20,.swagger-ui .hover-bg-black-20:focus,.swagger-ui .hover-bg-black-20:hover{background-color:rgba(0,0,0,.2)}.swagger-ui .bg-white-90,.swagger-ui .hover-bg-white-90:focus,.swagger-ui .hover-bg-white-90:hover{background-color:rgba(28,28,33,.9)}.swagger-ui .bg-white-80,.swagger-ui .hover-bg-white-80:focus,.swagger-ui .hover-bg-white-80:hover{background-color:rgba(28,28,33,.8)}.swagger-ui .bg-white-70,.swagger-ui .hover-bg-white-70:focus,.swagger-ui .hover-bg-white-70:hover{background-color:rgba(28,28,33,.7)}.swagger-ui .bg-white-60,.swagger-ui .hover-bg-white-60:focus,.swagger-ui .hover-bg-white-60:hover{background-color:rgba(28,28,33,.6)}.swagger-ui .bg-white-50,.swagger-ui .hover-bg-white-50:focus,.swagger-ui .hover-bg-white-50:hover{background-color:rgba(28,28,33,.5)}.swagger-ui .bg-white-40,.swagger-ui .hover-bg-white-40:focus,.swagger-ui .hover-bg-white-40:hover{background-color:rgba(28,28,33,.4)}.swagger-ui .bg-white-30,.swagger-ui .hover-bg-white-30:focus,.swagger-ui .hover-bg-white-30:hover{background-color:rgba(28,28,33,.3)}.swagger-ui .bg-white-20,.swagger-ui .hover-bg-white-20:focus,.swagger-ui .hover-bg-white-20:hover{background-color:rgba(28,28,33,.2)}.swagger-ui .bg-black,.swagger-ui .hover-bg-black:focus,.swagger-ui .hover-bg-black:hover{background-color:#000}.swagger-ui .bg-near-black,.swagger-ui .hover-bg-near-black:focus,.swagger-ui .hover-bg-near-black:hover{background-color:#121212}.swagger-ui .bg-dark-gray,.swagger-ui .hover-bg-dark-gray:focus,.swagger-ui .hover-bg-dark-gray:hover{background-color:#333}.swagger-ui .bg-mid-gray,.swagger-ui .hover-bg-mid-gray:focus,.swagger-ui .hover-bg-mid-gray:hover{background-color:#545454}.swagger-ui .bg-gray,.swagger-ui .hover-bg-gray:focus,.swagger-ui .hover-bg-gray:hover{background-color:#787878}.swagger-ui .bg-silver,.swagger-ui .hover-bg-silver:focus,.swagger-ui .hover-bg-silver:hover{background-color:#999}.swagger-ui .bg-white,.swagger-ui .hover-bg-white:focus,.swagger-ui .hover-bg-white:hover{background-color:#1c1c21}.swagger-ui .bg-transparent,.swagger-ui .hover-bg-transparent:focus,.swagger-ui .hover-bg-transparent:hover{background-color:transparent}.swagger-ui .bg-dark-red,.swagger-ui .hover-bg-dark-red:focus,.swagger-ui .hover-bg-dark-red:hover{background-color:#bc2f36}.swagger-ui .bg-red,.swagger-ui .hover-bg-red:focus,.swagger-ui .hover-bg-red:hover{background-color:#c83932}.swagger-ui .bg-light-red,.swagger-ui .hover-bg-light-red:focus,.swagger-ui .hover-bg-light-red:hover{background-color:#ab3c2b}.swagger-ui .bg-orange,.swagger-ui .hover-bg-orange:focus,.swagger-ui .hover-bg-orange:hover{background-color:#cc6e33}.swagger-ui .bg-gold,.swagger-ui .bg-light-yellow,.swagger-ui .bg-washed-yellow,.swagger-ui .bg-yellow,.swagger-ui .hover-bg-gold:focus,.swagger-ui .hover-bg-gold:hover,.swagger-ui .hover-bg-light-yellow:focus,.swagger-ui .hover-bg-light-yellow:hover,.swagger-ui .hover-bg-washed-yellow:focus,.swagger-ui .hover-bg-washed-yellow:hover,.swagger-ui .hover-bg-yellow:focus,.swagger-ui .hover-bg-yellow:hover{background-color:#664b00}.swagger-ui .bg-purple,.swagger-ui .hover-bg-purple:focus,.swagger-ui .hover-bg-purple:hover{background-color:#5e2ca5}.swagger-ui .bg-light-purple,.swagger-ui .hover-bg-light-purple:focus,.swagger-ui .hover-bg-light-purple:hover{background-color:#672caf}.swagger-ui .bg-dark-pink,.swagger-ui .hover-bg-dark-pink:focus,.swagger-ui .hover-bg-dark-pink:hover{background-color:#ab2b81}.swagger-ui .bg-hot-pink,.swagger-ui .hover-bg-hot-pink:focus,.swagger-ui .hover-bg-hot-pink:hover{background-color:#c03086}.swagger-ui .bg-pink,.swagger-ui .hover-bg-pink:focus,.swagger-ui .hover-bg-pink:hover{background-color:#8f2464}.swagger-ui .bg-light-pink,.swagger-ui .hover-bg-light-pink:focus,.swagger-ui .hover-bg-light-pink:hover{background-color:#721d4d}.swagger-ui .bg-dark-green,.swagger-ui .hover-bg-dark-green:focus,.swagger-ui .hover-bg-dark-green:hover{background-color:#1c6e50}.swagger-ui .bg-green,.swagger-ui .hover-bg-green:focus,.swagger-ui .hover-bg-green:hover{background-color:#279b70}.swagger-ui .bg-light-green,.swagger-ui .hover-bg-light-green:focus,.swagger-ui .hover-bg-light-green:hover{background-color:#228762}.swagger-ui .bg-navy,.swagger-ui .hover-bg-navy:focus,.swagger-ui .hover-bg-navy:hover{background-color:#0d1d35}.swagger-ui .bg-dark-blue,.swagger-ui .hover-bg-dark-blue:focus,.swagger-ui .hover-bg-dark-blue:hover{background-color:#20497e}.swagger-ui .bg-blue,.swagger-ui .hover-bg-blue:focus,.swagger-ui .hover-bg-blue:hover{background-color:#4380d0}.swagger-ui .bg-light-blue,.swagger-ui .hover-bg-light-blue:focus,.swagger-ui .hover-bg-light-blue:hover{background-color:#20517e}.swagger-ui .bg-lightest-blue,.swagger-ui .hover-bg-lightest-blue:focus,.swagger-ui .hover-bg-lightest-blue:hover{background-color:#143a52}.swagger-ui .bg-washed-blue,.swagger-ui .hover-bg-washed-blue:focus,.swagger-ui .hover-bg-washed-blue:hover{background-color:#0c312d}.swagger-ui .bg-washed-green,.swagger-ui .hover-bg-washed-green:focus,.swagger-ui .hover-bg-washed-green:hover{background-color:#0f3d2c}.swagger-ui .bg-washed-red,.swagger-ui .hover-bg-washed-red:focus,.swagger-ui .hover-bg-washed-red:hover{background-color:#411010}.swagger-ui .bg-inherit,.swagger-ui .hover-bg-inherit:focus,.swagger-ui .hover-bg-inherit:hover{background-color:inherit}.swagger-ui .shadow-hover{transition:.5s cubic-bezier(.165, .84, .44, 1)}.swagger-ui .shadow-hover::after{border-radius:inherit;box-shadow:rgba(0,0,0,.2) 0 0 16px 2px;content:"";height:100%;left:0;opacity:0;position:absolute;top:0;transition:opacity .5s cubic-bezier(.165, .84, .44, 1);width:100%;z-index:-1}.swagger-ui .bg-animate,.swagger-ui .bg-animate:focus,.swagger-ui .bg-animate:hover{transition:background-color .15s ease-in-out}.swagger-ui .nested-links a{color:#99bae6;transition:color .15s ease-in}.swagger-ui .nested-links a:focus,.swagger-ui .nested-links a:hover{color:#a9cbea;transition:color .15s ease-in}.swagger-ui .opblock-tag{border-bottom:1px solid rgba(58,64,80,.3);color:#b5bac9;transition:.2s}.swagger-ui .opblock-tag svg,.swagger-ui section.models h4 svg{transition:.4s}.swagger-ui .opblock{border:1px solid #000;border-radius:4px;box-shadow:rgba(0,0,0,.19) 0 0 3px;margin:0 0 15px}.swagger-ui .opblock .tab-header .tab-item.active h4 span::after{background:gray}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid #000}.swagger-ui .opblock .opblock-section-header{background:rgba(28,28,33,.8);box-shadow:rgba(0,0,0,.1) 0 1px 2px}.swagger-ui .opblock .opblock-section-header>label>span{padding:0 10px 0 0}.swagger-ui .opblock .opblock-summary-method{background:#000;color:#fff;text-shadow:rgba(0,0,0,.1) 0 1px 0}.swagger-ui .opblock.opblock-post{background:rgba(72,203,144,.1);border-color:#48cb90}.swagger-ui .opblock.opblock-post .opblock-summary-method,.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after{background:#48cb90}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:#48cb90}.swagger-ui .opblock.opblock-put{background:rgba(213,157,88,.1);border-color:#d59d58}.swagger-ui .opblock.opblock-put .opblock-summary-method,.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span::after{background:#d59d58}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:#d59d58}.swagger-ui .opblock.opblock-delete{background:rgba(200,50,50,.1);border-color:#c83232}.swagger-ui .opblock.opblock-delete .opblock-summary-method,.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after{background:#c83232}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:#c83232}.swagger-ui .opblock.opblock-get{background:rgba(42,105,167,.1);border-color:#2a69a7}.swagger-ui .opblock.opblock-get .opblock-summary-method,.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after{background:#2a69a7}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:#2a69a7}.swagger-ui .opblock.opblock-patch{background:rgba(92,214,188,.1);border-color:#5cd6bc}.swagger-ui .opblock.opblock-patch .opblock-summary-method,.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span::after{background:#5cd6bc}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:#5cd6bc}.swagger-ui .opblock.opblock-head{background:rgba(140,63,207,.1);border-color:#8c3fcf}.swagger-ui .opblock.opblock-head .opblock-summary-method,.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span::after{background:#8c3fcf}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:#8c3fcf}.swagger-ui .opblock.opblock-options{background:rgba(36,89,143,.1);border-color:#24598f}.swagger-ui .opblock.opblock-options .opblock-summary-method,.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span::after{background:#24598f}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:#24598f}.swagger-ui .opblock.opblock-deprecated{background:rgba(46,46,46,.1);border-color:#2e2e2e;opacity:.6}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method,.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span::after{background:#2e2e2e}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:#2e2e2e}.swagger-ui .filter .operation-filter-input{border:2px solid #2b3446}.swagger-ui .tab li:first-of-type::after{background:rgba(0,0,0,.2)}.swagger-ui .download-contents{background:#7c8192;color:#fff}.swagger-ui .scheme-container{background:#1c1c21;box-shadow:rgba(0,0,0,.15) 0 1px 2px 0}.swagger-ui .loading-container .loading::before{animation:1s linear infinite rotation,.5s opacity;border-color:rgba(0,0,0,.6) rgba(84,84,84,.1) rgba(84,84,84,.1)}.swagger-ui .response-control-media-type--accept-controller select{border-color:#196619}.swagger-ui .response-control-media-type__accept-message{color:#99e699}.swagger-ui .version-pragma__message code{background-color:#3b3b3b}.swagger-ui .btn{background:0 0;border:2px solid gray;box-shadow:rgba(0,0,0,.1) 0 1px 2px;color:#b5bac9}.swagger-ui .btn:hover{box-shadow:rgba(0,0,0,.3) 0 0 5px}.swagger-ui .btn.authorize,.swagger-ui .btn.cancel{background-color:transparent;border-color:#a72a2a;color:#e69999}.swagger-ui .btn.cancel:hover{background-color:#a72a2a;color:#fff}.swagger-ui .btn.authorize{border-color:#48cb90;color:#9ce3c3}.swagger-ui .btn.authorize svg{fill:#9ce3c3}.btn.authorize.unlocked:hover{background-color:#48cb90;color:#fff}.btn.authorize.unlocked:hover svg{fill:#fbfbfb}.swagger-ui .btn.execute{background-color:#5892d5;border-color:#5892d5;color:#fff}.swagger-ui .copy-to-clipboard{background:#7c8192}.swagger-ui .copy-to-clipboard button{background:url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat}.swagger-ui select{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQuc3ZnIgogICBpZD0ic3ZnNCIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMjAgMjAiPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTEwIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzOCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICAgIGlua3NjYXBlOndpbmRvdy15PSItOSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTkiCiAgICAgaW5rc2NhcGU6Y3k9IjEwIgogICAgIGlua3NjYXBlOmN4PSIxMCIKICAgICBpbmtzY2FwZTp6b29tPSI0MS41IgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEzLjQxOCA3Ljg1OWEuNjk1LjY5NSAwIDAxLjk3OCAwIC42OC42OCAwIDAxMCAuOTY5bC0zLjkwOCAzLjgzYS42OTcuNjk3IDAgMDEtLjk3OSAwbC0zLjkwOC0zLjgzYS42OC42OCAwIDAxMC0uOTY5LjY5NS42OTUgMCAwMS45NzggMEwxMCAxMWwzLjQxOC0zLjE0MXoiIC8+Cjwvc3ZnPgo=) right 10px center/20px no-repeat #1c1c21;border:2px solid #41444e}.swagger-ui select[multiple]{background:#212121}.swagger-ui button.invalid,.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:#390e0e;border-color:#c83232}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:#1c1c21;border:1px solid #404040}.swagger-ui textarea{background:rgba(28,28,33,.8);color:#b5bac9}.swagger-ui textarea[disabled]{background-color:#41444e;color:#fff}.swagger-ui select[disabled]{border-color:#878787}.swagger-ui textarea:focus{border:2px solid #2a69a7}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:#303030;box-shadow:#303030 0 0 0 2px}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat #303030}.swagger-ui .dialog-ux .backdrop-ux{background:rgba(0,0,0,.8)}.swagger-ui .dialog-ux .modal-ux{background:#1c1c21;border:1px solid #2e2e2e;box-shadow:rgba(0,0,0,.2) 0 10px 30px 0}.swagger-ui .dialog-ux .modal-ux-header .close-modal{background:0 0}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:#bfbfbf!important}.swagger-ui .model-toggle::after{background:url("data:image/svg+xml;charset=utf-8,") 50% center/100% no-repeat}.swagger-ui .model-hint{background:rgba(0,0,0,.7);color:#ebebeb}.swagger-ui section.models{border:1px solid rgba(58,64,80,.3)}.swagger-ui section.models.is-open h4{border-bottom:1px solid rgba(58,64,80,.3)}.swagger-ui section.models .model-container{background:rgba(0,0,0,.05)}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui .model-box{background:rgba(0,0,0,.1)}.swagger-ui .prop-type{color:#aaaad4}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid rgba(58,64,80,.2);color:#b5bac9}.swagger-ui .parameter__name.required::after{color:rgba(230,153,153,.6)}.swagger-ui .topbar .download-url-wrapper .select-label{color:#f0f0f0}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:#63a040;color:#fff}.swagger-ui .info .title small{background:#7c8492}.swagger-ui .info .title small.version-stamp{background-color:#7a9b27}.swagger-ui .auth-container .errors{background-color:#350d0d;color:#b5bac9}.swagger-ui .errors-wrapper{background:rgba(200,50,50,.1);border:2px solid #c83232}.swagger-ui .markdown code,.swagger-ui .renderedmarkdown code{background:rgba(0,0,0,.05);color:#c299e6}.swagger-ui .model-toggle:after{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQyLnN2ZyIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgaGVpZ2h0PSIyNCIKICAgd2lkdGg9IjI0Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGExMCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMKICAgICBpZD0iZGVmczgiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTkiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9Ii05IgogICAgIGlua3NjYXBlOmN5PSIxMiIKICAgICBpbmtzY2FwZTpjeD0iMTIiCiAgICAgaW5rc2NhcGU6em9vbT0iMzQuNTgzMzMzIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02eiIgLz4KPC9zdmc+Cg==) 50% no-repeat}#large-arrow-up,#locked,#unlocked,.arrow{fill:#fff}::-webkit-scrollbar-button,::-webkit-scrollbar-track-piece{background-color:#3e4346!important}.swagger-ui .black,.swagger-ui .checkbox,.swagger-ui .dark-gray,.swagger-ui .download-url-wrapper .loading,.swagger-ui .errors-wrapper .errors small,.swagger-ui .fallback,.swagger-ui .filter .loading,.swagger-ui .gray,.swagger-ui .hover-black:focus,.swagger-ui .hover-black:hover,.swagger-ui .hover-dark-gray:focus,.swagger-ui .hover-dark-gray:hover,.swagger-ui .hover-gray:focus,.swagger-ui .hover-gray:hover,.swagger-ui .hover-light-silver:focus,.swagger-ui .hover-light-silver:hover,.swagger-ui .hover-mid-gray:focus,.swagger-ui .hover-mid-gray:hover,.swagger-ui .hover-near-black:focus,.swagger-ui .hover-near-black:hover,.swagger-ui .hover-silver:focus,.swagger-ui .hover-silver:hover,.swagger-ui .light-silver,.swagger-ui .markdown pre,.swagger-ui .mid-gray,.swagger-ui .model .property,.swagger-ui .model .property.primitive,.swagger-ui .model-title,.swagger-ui .near-black,.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .prop-format,.swagger-ui .renderedmarkdown pre,.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented,.swagger-ui .silver,.swagger-ui section.models h4,.swagger-ui section.models h5,.swagger-ui span.token-not-formatted,.swagger-ui span.token-string,.swagger-ui table.headers .header-example,.swagger-ui table.model tr.description,.swagger-ui table.model tr.extension{color:#bfbfbf}.swagger-ui .hover-white:focus,.swagger-ui .hover-white:hover,.swagger-ui .info .title small pre,.swagger-ui .topbar a,.swagger-ui .white{color:#fff}.swagger-ui .bg-black-10,.swagger-ui .hover-bg-black-10:focus,.swagger-ui .hover-bg-black-10:hover,.swagger-ui .stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.swagger-ui .bg-white-10,.swagger-ui .hover-bg-white-10:focus,.swagger-ui .hover-bg-white-10:hover,.swagger-ui .stripe-light:nth-child(odd){background-color:rgba(28,28,33,.1)}.swagger-ui .bg-light-silver,.swagger-ui .hover-bg-light-silver:focus,.swagger-ui .hover-bg-light-silver:hover,.swagger-ui .striped--light-silver:nth-child(odd){background-color:#6e6e6e}.swagger-ui .bg-moon-gray,.swagger-ui .hover-bg-moon-gray:focus,.swagger-ui .hover-bg-moon-gray:hover,.swagger-ui .striped--moon-gray:nth-child(odd){background-color:#4d4d4d}.swagger-ui .bg-light-gray,.swagger-ui .hover-bg-light-gray:focus,.swagger-ui .hover-bg-light-gray:hover,.swagger-ui .striped--light-gray:nth-child(odd){background-color:#2b2b2b}.swagger-ui .bg-near-white,.swagger-ui .hover-bg-near-white:focus,.swagger-ui .hover-bg-near-white:hover,.swagger-ui .striped--near-white:nth-child(odd){background-color:#242424}.swagger-ui .opblock-tag:hover,.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed,.swagger-ui .model-deprecated-warning,.swagger-ui .parameter__deprecated,.swagger-ui .parameter__name.required span,.swagger-ui table.model tr.property-row .star{color:#e69999}.swagger-ui .opblock-body pre.microlight,.swagger-ui textarea.curl{background:#41444e;border-radius:4px;color:#fff}.swagger-ui .expand-methods svg,.swagger-ui .expand-methods:hover svg{fill:#bfbfbf}.swagger-ui .auth-container,.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid #2e2e2e}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid #63a040}.swagger-ui .info a,.swagger-ui .info a:hover,.swagger-ui .scopes h2 a{color:#99bde6}::-webkit-scrollbar{width:14px;height:14px}::-webkit-scrollbar-button{background-color:#3e4346!important}::-webkit-scrollbar-track{background-color:#646464!important}::-webkit-scrollbar-track-piece{background-color:#3e4346!important}::-webkit-scrollbar-thumb{height:50px;background-color:#242424!important;border:2px solid #3e4346!important}::-webkit-scrollbar-button:vertical:start:decrement{background:linear-gradient(130deg,#696969 40%,rgba(255,0,0,0) 41%),linear-gradient(230deg,#696969 40%,rgba(0,0,0,0) 41%),linear-gradient(0deg,#696969 40%,rgba(0,0,0,0) 31%);background-color:#b6b6b6}::-webkit-scrollbar-button:vertical:end:increment{background:linear-gradient(310deg,#696969 40%,rgba(0,0,0,0) 41%),linear-gradient(50deg,#696969 40%,rgba(0,0,0,0) 41%),linear-gradient(180deg,#696969 40%,rgba(0,0,0,0) 31%);background-color:#b6b6b6}::-webkit-scrollbar-button:horizontal:end:increment{background:linear-gradient(210deg,#696969 40%,rgba(0,0,0,0) 41%),linear-gradient(330deg,#696969 40%,rgba(0,0,0,0) 41%),linear-gradient(90deg,#696969 30%,rgba(0,0,0,0) 31%);background-color:#b6b6b6}::-webkit-scrollbar-button:horizontal:start:decrement{background:linear-gradient(30deg,#696969 40%,rgba(0,0,0,0) 41%),linear-gradient(150deg,#696969 40%,rgba(0,0,0,0) 41%),linear-gradient(270deg,#696969 30%,rgba(0,0,0,0) 31%);background-color:#b6b6b6}} \ No newline at end of file -- 2.43.0 From 05ca45db491e233820b32e971670dc2df99c1c1b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 23:39:12 +0300 Subject: [PATCH 260/474] perf: precompile regex --- Endpoint/Controllers/Configuration/SetupController.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 7ccea5f..92ec9b6 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -33,7 +33,7 @@ namespace Mirea.Api.Endpoint.Controllers.Configuration; [ApiController] [MaintenanceModeIgnore] [ApiExplorerSettings(IgnoreApi = true)] -public class SetupController( +public partial class SetupController( ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache, @@ -208,7 +208,7 @@ public class SetupController( [BadRequestResponse] public ActionResult CreateAdmin([FromBody] CreateUserRequest user) { - if (user.Password.Length < 8 || !Regex.IsMatch(user.Password, "[A-Z]+") || !Regex.IsMatch(user.Password, "[!@#$%^&*]+")) + if (user.Password.Length < 8 || !PasswordExistUpperLetter().IsMatch(user.Password) || !PasswordExistSpecialSymbol().IsMatch(user.Password)) throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character."); if (!MailAddress.TryCreate(user.Email, out _)) @@ -348,4 +348,10 @@ public class SetupController( return true; } + + [GeneratedRegex("[A-Z]+")] + private static partial Regex PasswordExistUpperLetter(); + + [GeneratedRegex("[!@#$%^&*]+")] + private static partial Regex PasswordExistSpecialSymbol(); } \ No newline at end of file -- 2.43.0 From 9133b57a1b2e8ec51132efb72ee66dd2c47192d9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 23:45:33 +0300 Subject: [PATCH 261/474] refactor: GeneralConfig --- .../Configuration/General/GeneralConfig.cs | 22 +++++++++++++++++-- .../Configuration/SetupController.cs | 13 ++--------- Endpoint/Program.cs | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Endpoint/Configuration/General/GeneralConfig.cs b/Endpoint/Configuration/General/GeneralConfig.cs index 60f98a1..2f74a9a 100644 --- a/Endpoint/Configuration/General/GeneralConfig.cs +++ b/Endpoint/Configuration/General/GeneralConfig.cs @@ -1,14 +1,32 @@ -using Mirea.Api.Endpoint.Configuration.General.Settings; +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.General.Settings; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Mirea.Api.Endpoint.Configuration.General; public class GeneralConfig { - public const string FilePath = "Settings.json"; + [JsonIgnore] private const string FileName = "Settings.json"; + + [JsonIgnore] + public static string FilePath => PathBuilder.Combine(FileName); public DbSettings? DbSettings { get; set; } public CacheSettings? CacheSettings { get; set; } public ScheduleSettings? ScheduleSettings { get; set; } public EmailSettings? EmailSettings { get; set; } public LogSettings? LogSettings { get; set; } + + public void SaveSetting() + { + File.WriteAllText( + FilePath, + JsonSerializer.Serialize(this, new JsonSerializerOptions + { + WriteIndented = true + }) + ); + } } \ No newline at end of file diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 92ec9b6..9e35c8b 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -55,7 +55,7 @@ public partial class SetupController( if (!notConfigureService.IsMaintenanceMode) throw new ControllerArgumentException( "The token cannot be generated because the server has been configured. " + - $"If you need to restart the configuration, then delete the \"{PathBuilder.Combine(GeneralConfig.FilePath)}\" file and restart the application."); + $"If you need to restart the configuration, then delete the \"{GeneralConfig.FilePath}\" file and restart the application."); var token = new byte[32]; RandomNumberGenerator.Create().GetBytes(token); @@ -333,18 +333,9 @@ public partial class SetupController( if (!cache.TryGetValue(CacheAdminKey, out Admin? admin) || admin == null) throw new ControllerArgumentException("The administrator's data was not set."); - if (System.IO.File.Exists(PathBuilder.Combine(GeneralConfig.FilePath))) - System.IO.File.Delete(PathBuilder.Combine(GeneralConfig.FilePath)); - System.IO.File.WriteAllText(PathBuilder.Combine(Admin.PathToSave), JsonSerializer.Serialize(admin)); - System.IO.File.WriteAllText( - PathBuilder.Combine(GeneralConfig.FilePath), - JsonSerializer.Serialize(GeneralConfig, new JsonSerializerOptions - { - WriteIndented = true - }) - ); + GeneralConfig.SaveSetting(); return true; } diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index d368177..3066fa4 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -36,7 +36,7 @@ public class Program var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddConfiguration(EnvironmentConfiguration.GetEnvironment()); - builder.Configuration.AddJsonFile(PathBuilder.Combine(GeneralConfig.FilePath), optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile(GeneralConfig.FilePath, optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); builder.Configuration.AddJsonFile(PathBuilder.Combine(Admin.PathToSave), optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); -- 2.43.0 From e8ca2c42a6c045565a470e41f81fd42d44394347 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 23:46:43 +0300 Subject: [PATCH 262/474] sec: add random scret forward token for set ip if app under proxy --- Endpoint/Configuration/General/GeneralConfig.cs | 1 + Endpoint/Program.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Endpoint/Configuration/General/GeneralConfig.cs b/Endpoint/Configuration/General/GeneralConfig.cs index 2f74a9a..2a6fcc2 100644 --- a/Endpoint/Configuration/General/GeneralConfig.cs +++ b/Endpoint/Configuration/General/GeneralConfig.cs @@ -18,6 +18,7 @@ public class GeneralConfig public ScheduleSettings? ScheduleSettings { get; set; } public EmailSettings? EmailSettings { get; set; } public LogSettings? LogSettings { get; set; } + public string? SecretForwardToken { get; set; } public void SaveSetting() { diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 3066fa4..89c754c 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -12,6 +13,7 @@ using Mirea.Api.Endpoint.Configuration.AppConfig; 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,6 +66,20 @@ public class Program }); }); + builder.Services.Configure(options => + { + var secretForward = builder.Configuration.Get(); + + if (string.IsNullOrEmpty(secretForward!.SecretForwardToken)) + { + secretForward.SecretForwardToken = GeneratorKey.GenerateBase64(18); + secretForward.SaveSetting(); + } + + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.ForwardedForHeaderName = secretForward.SecretForwardToken + "-X-Forwarded-For"; + }); + builder.Services.AddCustomApiVersioning(); builder.Services.AddCustomSwagger(); @@ -75,6 +91,7 @@ public class Program app.UseStaticFiles(); app.UseCors("AllowAll"); app.UseCustomSerilog(); + app.UseForwardedHeaders(); using (var scope = app.Services.CreateScope()) { -- 2.43.0 From 17e20fee2e2573d3437e6a68476a24be705e025f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 23:52:25 +0300 Subject: [PATCH 263/474] refactor: Admin --- Endpoint/Common/Model/Admin.cs | 10 ---------- Endpoint/Common/Settings/Admin.cs | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) delete mode 100644 Endpoint/Common/Model/Admin.cs create mode 100644 Endpoint/Common/Settings/Admin.cs diff --git a/Endpoint/Common/Model/Admin.cs b/Endpoint/Common/Model/Admin.cs deleted file mode 100644 index 59e4b60..0000000 --- a/Endpoint/Common/Model/Admin.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Mirea.Api.Endpoint.Common.Model; - -public class Admin -{ - public const string PathToSave = "admin.json"; - public required string Username { get; set; } - public required string Email { get; set; } - public required string PasswordHash { get; set; } - public required string Salt { get; set; } -} \ No newline at end of file diff --git a/Endpoint/Common/Settings/Admin.cs b/Endpoint/Common/Settings/Admin.cs new file mode 100644 index 0000000..0e41c36 --- /dev/null +++ b/Endpoint/Common/Settings/Admin.cs @@ -0,0 +1,25 @@ +using Mirea.Api.Endpoint.Common.Services; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Mirea.Api.Endpoint.Common.Settings; + +public class Admin : ISaveSettings +{ + [JsonIgnore] private const string FileName = "admin.json"; + + [JsonIgnore] + public static string FilePath => PathBuilder.Combine(FileName); + + public required string Username { get; set; } + public required string Email { get; set; } + public required string PasswordHash { get; set; } + public required string Salt { get; set; } + + + public void SaveSetting() + { + File.WriteAllText(FilePath, JsonSerializer.Serialize(this)); + } +} \ No newline at end of file -- 2.43.0 From 75c1aebea6923f11af5f4db5c0242f6b4716be53 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 23:54:17 +0300 Subject: [PATCH 264/474] refactor: change namespace --- .../General => Common/Settings}/GeneralConfig.cs | 4 ++-- Endpoint/Common/Settings/ISaveSettings.cs | 5 +++++ Endpoint/Configuration/AppConfig/CacheConfiguration.cs | 2 +- Endpoint/Configuration/AppConfig/LoggerConfiguration.cs | 2 +- Endpoint/Configuration/AppConfig/SecureConfiguration.cs | 2 +- .../General/Validators/SettingsRequiredValidator.cs | 1 + Endpoint/Controllers/Configuration/SetupController.cs | 7 ++----- Endpoint/Controllers/V1/AuthController.cs | 2 +- Endpoint/Controllers/V1/ScheduleController.cs | 2 +- Endpoint/Program.cs | 4 ++-- 10 files changed, 17 insertions(+), 14 deletions(-) rename Endpoint/{Configuration/General => Common/Settings}/GeneralConfig.cs (90%) create mode 100644 Endpoint/Common/Settings/ISaveSettings.cs diff --git a/Endpoint/Configuration/General/GeneralConfig.cs b/Endpoint/Common/Settings/GeneralConfig.cs similarity index 90% rename from Endpoint/Configuration/General/GeneralConfig.cs rename to Endpoint/Common/Settings/GeneralConfig.cs index 2a6fcc2..dcf218e 100644 --- a/Endpoint/Configuration/General/GeneralConfig.cs +++ b/Endpoint/Common/Settings/GeneralConfig.cs @@ -4,9 +4,9 @@ using System.IO; using System.Text.Json; using System.Text.Json.Serialization; -namespace Mirea.Api.Endpoint.Configuration.General; +namespace Mirea.Api.Endpoint.Common.Settings; -public class GeneralConfig +public class GeneralConfig : ISaveSettings { [JsonIgnore] private const string FileName = "Settings.json"; diff --git a/Endpoint/Common/Settings/ISaveSettings.cs b/Endpoint/Common/Settings/ISaveSettings.cs new file mode 100644 index 0000000..0b51bde --- /dev/null +++ b/Endpoint/Common/Settings/ISaveSettings.cs @@ -0,0 +1,5 @@ +namespace Mirea.Api.Endpoint.Common.Settings; +public interface ISaveSettings +{ + void SaveSetting(); +} diff --git a/Endpoint/Configuration/AppConfig/CacheConfiguration.cs b/Endpoint/Configuration/AppConfig/CacheConfiguration.cs index bab6483..d81c3cb 100644 --- a/Endpoint/Configuration/AppConfig/CacheConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/CacheConfiguration.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Mirea.Api.Endpoint.Configuration.General; +using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Endpoint.Configuration.General.Settings; namespace Mirea.Api.Endpoint.Configuration.AppConfig; diff --git a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs index a82010f..595810e 100644 --- a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Mirea.Api.Endpoint.Common.Services; -using Mirea.Api.Endpoint.Configuration.General; +using Mirea.Api.Endpoint.Common.Settings; using Serilog; using Serilog.Events; using Serilog.Filters; diff --git a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs b/Endpoint/Configuration/AppConfig/SecureConfiguration.cs index 8e49960..5ea2fca 100644 --- a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SecureConfiguration.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Mirea.Api.Endpoint.Common.Services.Security; -using Mirea.Api.Endpoint.Configuration.General; +using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Endpoint.Configuration.General.Settings; using Mirea.Api.Security; using Mirea.Api.Security.Common.Interfaces; diff --git a/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs b/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs index a1e8fbc..323ea7c 100644 --- a/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs +++ b/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Options; +using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Endpoint.Configuration.General.Attributes; using Mirea.Api.Endpoint.Configuration.General.Interfaces; using System; diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 9e35c8b..46a4d26 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -8,9 +8,8 @@ using Mirea.Api.Dto.Requests.Configuration; using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.Interfaces; -using Mirea.Api.Endpoint.Common.Model; using Mirea.Api.Endpoint.Common.Services; -using Mirea.Api.Endpoint.Configuration.General; +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; @@ -24,7 +23,6 @@ using System.IO; using System.Net.Mail; using System.Runtime.InteropServices; using System.Security.Cryptography; -using System.Text.Json; using System.Text.RegularExpressions; namespace Mirea.Api.Endpoint.Controllers.Configuration; @@ -333,8 +331,7 @@ public partial class SetupController( if (!cache.TryGetValue(CacheAdminKey, out Admin? admin) || admin == null) throw new ControllerArgumentException("The administrator's data was not set."); - System.IO.File.WriteAllText(PathBuilder.Combine(Admin.PathToSave), JsonSerializer.Serialize(admin)); - + admin.SaveSetting(); GeneralConfig.SaveSetting(); return true; diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index dcd1821..0f70204 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Options; using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Responses; -using Mirea.Api.Endpoint.Common.Model; +using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Security.Common.Dto.Requests; using Mirea.Api.Security.Services; using System; diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 81bc283..0b16686 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -8,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.General; +using Mirea.Api.Endpoint.Common.Settings; using System; using System.Collections.Generic; using System.Linq; diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 89c754c..88f99a0 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -7,8 +7,8 @@ using Mirea.Api.DataAccess.Application; using Mirea.Api.DataAccess.Persistence; using Mirea.Api.DataAccess.Persistence.Common; using Mirea.Api.Endpoint.Common.Interfaces; -using Mirea.Api.Endpoint.Common.Model; using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Endpoint.Configuration.AppConfig; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Validators; @@ -40,7 +40,7 @@ public class Program builder.Configuration.AddConfiguration(EnvironmentConfiguration.GetEnvironment()); builder.Configuration.AddJsonFile(GeneralConfig.FilePath, optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); - builder.Configuration.AddJsonFile(PathBuilder.Combine(Admin.PathToSave), optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile(Admin.FilePath, optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); builder.Host.AddCustomSerilog(); -- 2.43.0 From d87654a355113592df9e114ce2da9fcab4840f3a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 4 Jul 2024 23:57:44 +0300 Subject: [PATCH 265/474] ref: update --- Endpoint/Endpoint.csproj | 10 +++++----- Security/Security.csproj | 8 ++++---- SqlData/Application/Application.csproj | 6 +++--- SqlData/Domain/Domain.csproj | 6 +++--- SqlData/Persistence/Persistence.csproj | 6 +++--- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 1dcb456..3ff7872 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-a0 - 1.0.0.0 - 1.0.0.0 + 1.0.0-b0 + 1.0.1.0 + 1.0.1.0 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -31,12 +31,12 @@ - + - + diff --git a/Security/Security.csproj b/Security/Security.csproj index e40c16a..1727334 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -5,15 +5,15 @@ disable enable Winsomnia - 1.0.0-a0 - 1.0.0.0 - 1.0.0.0 + 1.0.0-rc0 + 1.0.2.0 + 1.0.2.0 Mirea.Api.Security $(AssemblyName) - + diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 927ddcc..532d17a 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-a0 - 1.0.0.0 - 1.0.0.0 + 1.0.0-rc0 + 1.0.2.0 + 1.0.2.0 Mirea.Api.DataAccess.Application $(AssemblyName) diff --git a/SqlData/Domain/Domain.csproj b/SqlData/Domain/Domain.csproj index a8caa41..940e1f5 100644 --- a/SqlData/Domain/Domain.csproj +++ b/SqlData/Domain/Domain.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-a0 - 1.0.0.0 - 1.0.0.0 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.DataAccess.Domain $(AssemblyName) diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 07ca710..ce3d3a4 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-a0 - 1.0.0.0 - 1.0.0.0 + 1.0.0-rc0 + 1.0.2.0 + 1.0.2.0 Mirea.Api.DataAccess.Persistence $(AssemblyName) -- 2.43.0 From d45c865f4eafa15702a8aebc5f715b9eda806ed4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 00:44:55 +0300 Subject: [PATCH 266/474] feat: add listen port from env --- .env | 8 +++++++- Endpoint/Program.cs | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.env b/.env index f06ff63..555d136 100644 --- a/.env +++ b/.env @@ -9,7 +9,7 @@ # General -# The path to save the data. +# The path to save the data # string # (optional) # Saving logs (if the full path is not specified), @@ -18,6 +18,12 @@ # If you want to change this value, you need to change the values in Settings.json and move the file itself to the desired location. PATH_TO_SAVE= +# Internal port configuration +# integer +# (optional) +# Specify the internal port on which the server will listen. +INTERNAL_PORT= + # Security # JWT signature token diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 88f99a0..b6ca7ca 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -16,6 +16,7 @@ using Mirea.Api.Endpoint.Middleware; using Mirea.Api.Security.Services; using System; using System.IO; +using Microsoft.AspNetCore.Hosting; namespace Mirea.Api.Endpoint; @@ -66,6 +67,15 @@ public class Program }); }); + if (!string.IsNullOrEmpty(builder.Configuration.GetValue("INTERNAL_PORT"))) + { + builder.WebHost.ConfigureKestrel(options => + { + options.ListenLocalhost( + int.Parse(builder.Configuration.GetValue("INTERNAL_PORT")!)); + }); + } + builder.Services.Configure(options => { var secretForward = builder.Configuration.Get(); -- 2.43.0 From cdb738ca42f791ab1ee14e21c21f894b97d714cd Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 01:11:24 +0300 Subject: [PATCH 267/474] fix: set default port 8080 --- .env | 2 +- Endpoint/Program.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.env b/.env index 555d136..6a7047e 100644 --- a/.env +++ b/.env @@ -22,7 +22,7 @@ PATH_TO_SAVE= # integer # (optional) # Specify the internal port on which the server will listen. -INTERNAL_PORT= +INTERNAL_PORT=8080 # Security diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index b6ca7ca..b336aa3 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -16,7 +16,6 @@ using Mirea.Api.Endpoint.Middleware; using Mirea.Api.Security.Services; using System; using System.IO; -using Microsoft.AspNetCore.Hosting; namespace Mirea.Api.Endpoint; @@ -67,14 +66,11 @@ public class Program }); }); - if (!string.IsNullOrEmpty(builder.Configuration.GetValue("INTERNAL_PORT"))) - { builder.WebHost.ConfigureKestrel(options => { options.ListenLocalhost( int.Parse(builder.Configuration.GetValue("INTERNAL_PORT")!)); }); - } builder.Services.Configure(options => { -- 2.43.0 From 57f4d1b822d744cd667233d89528c5ecd04f80a8 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 01:22:21 +0300 Subject: [PATCH 268/474] fix: add test env to variablesFromFile --- .../Configuration/AppConfig/EnvironmentConfiguration.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs index fe90c2c..e729071 100644 --- a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs @@ -46,6 +46,10 @@ public static class EnvironmentConfiguration { var variablesFromFile = LoadEnvironment(".env"); +#if DEBUG + LoadEnvironment(".env.develop").ToList().ForEach(x => variablesFromFile.Add(x.Key, x.Value)); +#endif + var environmentVariables = Environment.GetEnvironmentVariables() .OfType() .ToDictionary( @@ -57,9 +61,8 @@ public static class EnvironmentConfiguration .AddInMemoryCollection(environmentVariables!) .AddInMemoryCollection(variablesFromFile!); -#if DEBUG - result.AddInMemoryCollection(LoadEnvironment(".env.develop")!); -#endif + + if (!variablesFromFile.TryGetValue("PATH_TO_SAVE", out var data)) return result.Build(); -- 2.43.0 From f42caa3a45adc0f3ea34f5381a124bff76b061b2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 01:35:19 +0300 Subject: [PATCH 269/474] feat: add sub path for actual url --- .env | 10 +++++++++ Endpoint/Common/Services/UrlHelper.cs | 22 +++++++++++++++++++ .../AppConfig/EnvironmentConfiguration.cs | 17 ++++++++------ .../AppConfig/SwaggerConfiguration.cs | 2 ++ .../Configuration/SetupController.cs | 8 +++---- Endpoint/Controllers/V1/AuthController.cs | 5 +++-- Endpoint/Program.cs | 13 ++++++----- 7 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 Endpoint/Common/Services/UrlHelper.cs diff --git a/.env b/.env index 6a7047e..1e663c4 100644 --- a/.env +++ b/.env @@ -18,6 +18,16 @@ # If you want to change this value, you need to change the values in Settings.json and move the file itself to the desired location. PATH_TO_SAVE= +# The actual sub path to the api +# string +# (optional) +ACTUAL_SUB_PATH= + +# The sub path to the swagger +# string +# (optional) +SWAGGER_SUB_PATH=swagger + # Internal port configuration # integer # (optional) diff --git a/Endpoint/Common/Services/UrlHelper.cs b/Endpoint/Common/Services/UrlHelper.cs new file mode 100644 index 0000000..428ebdd --- /dev/null +++ b/Endpoint/Common/Services/UrlHelper.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Linq; + +namespace Mirea.Api.Endpoint.Common.Services; + +public static class UrlHelper +{ + public static string CurrentDomain(HttpContext context) => + context.Request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? context.Request.Host.Value; + + private static string CreateSubPath(string? path) + { + if (string.IsNullOrEmpty(path)) + return "/"; + + return "/" + path.Trim('/') + "/"; + } + + public static string GetSubPath => CreateSubPath(Environment.GetEnvironmentVariable("ACTUAL_SUB_PATH")); + public static string GetSubPathSwagger => CreateSubPath(Environment.GetEnvironmentVariable("SWAGGER_SUB_PATH")); +} \ No newline at end of file diff --git a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs index e729071..796ee3f 100644 --- a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs @@ -61,15 +61,18 @@ public static class EnvironmentConfiguration .AddInMemoryCollection(environmentVariables!) .AddInMemoryCollection(variablesFromFile!); + if (variablesFromFile.TryGetValue("PATH_TO_SAVE", out var pathToSave)) + { + Environment.SetEnvironmentVariable("PATH_TO_SAVE", pathToSave); + if (!Directory.Exists(pathToSave)) + Directory.CreateDirectory(pathToSave); + } + if (variablesFromFile.TryGetValue("ACTUAL_SUB_PATH", out var actualSubPath)) + Environment.SetEnvironmentVariable("ACTUAL_SUB_PATH", actualSubPath); - - if (!variablesFromFile.TryGetValue("PATH_TO_SAVE", out var data)) - return result.Build(); - - Environment.SetEnvironmentVariable("PATH_TO_SAVE", data); - if (!Directory.Exists(data)) - Directory.CreateDirectory(data); + if (variablesFromFile.TryGetValue("SWAGGER_SUB_PATH", out var swaggerSubPath)) + Environment.SetEnvironmentVariable("SWAGGER_SUB_PATH", swaggerSubPath); return result.Build(); } diff --git a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs index 4c2ff9f..7a973ce 100644 --- a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; +using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using System; @@ -65,6 +66,7 @@ public static class SwaggerConfiguration var url = $"/swagger/{description.GroupName}/swagger.json"; var name = description.GroupName.ToUpperInvariant(); options.SwaggerEndpoint(url, name); + options.RoutePrefix = UrlHelper.GetSubPathSwagger.Trim('/'); } }); diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 46a4d26..827257f 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -69,11 +69,11 @@ public partial class SetupController( Response.Cookies.Append("AuthToken", token, new CookieOptions { - HttpOnly = false, - Secure = false, - Path = "/" + Path = UrlHelper.GetSubPath + "api", + Domain = UrlHelper.CurrentDomain(ControllerContext.HttpContext), + Secure = true, + HttpOnly = true }); - return Ok(true); } diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 0f70204..1ecb0fe 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Options; using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Security.Common.Dto.Requests; using Mirea.Api.Security.Services; @@ -28,8 +29,8 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass var cookieOptions = new CookieOptions { Expires = expires, - Path = "/api", - Domain = Request.Headers["X-Forwarded-Host"], + Path = UrlHelper.GetSubPath + "api", + Domain = UrlHelper.CurrentDomain(ControllerContext.HttpContext), Secure = true, HttpOnly = true }; diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index b336aa3..4a2e108 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -66,11 +67,11 @@ public class Program }); }); - builder.WebHost.ConfigureKestrel(options => - { - options.ListenLocalhost( - int.Parse(builder.Configuration.GetValue("INTERNAL_PORT")!)); - }); + builder.WebHost.ConfigureKestrel(options => + { + options.ListenLocalhost( + int.Parse(builder.Configuration.GetValue("INTERNAL_PORT") ?? "8080")); + }); builder.Services.Configure(options => { @@ -94,7 +95,7 @@ public class Program var app = builder.Build(); - app.UseStaticFiles(); + app.UseStaticFiles(UrlHelper.GetSubPath.TrimEnd('/')); app.UseCors("AllowAll"); app.UseCustomSerilog(); app.UseForwardedHeaders(); -- 2.43.0 From 21055176ace74b84c956c6c30efcd0bc81f37bba Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 01:57:34 +0300 Subject: [PATCH 270/474] fix: check file exist --- Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs index 7a973ce..5463d02 100644 --- a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs @@ -44,8 +44,11 @@ public static class SwaggerConfiguration } }); - options.IncludeXmlComments(Path.Combine(basePath, "docs.xml")); - options.IncludeXmlComments(Path.Combine(basePath, "ApiDtoDocs.xml")); + if (File.Exists(Path.Combine(basePath, "docs.xml"))) + options.IncludeXmlComments(Path.Combine(basePath, "docs.xml")); + + if (File.Exists(Path.Combine(basePath, "ApiDtoDocs.xml"))) + options.IncludeXmlComments(Path.Combine(basePath, "ApiDtoDocs.xml")); }); services.AddTransient, ConfigureSwaggerOptions>(); -- 2.43.0 From ac4804e864b407cefd6ef6bf7f91d14a8da42079 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 01:58:14 +0300 Subject: [PATCH 271/474] fix: get host name without port --- Endpoint/Common/Services/UrlHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Common/Services/UrlHelper.cs b/Endpoint/Common/Services/UrlHelper.cs index 428ebdd..b7c7a7a 100644 --- a/Endpoint/Common/Services/UrlHelper.cs +++ b/Endpoint/Common/Services/UrlHelper.cs @@ -7,7 +7,7 @@ namespace Mirea.Api.Endpoint.Common.Services; public static class UrlHelper { public static string CurrentDomain(HttpContext context) => - context.Request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? context.Request.Host.Value; + context.Request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? context.Request.Host.Host; private static string CreateSubPath(string? path) { -- 2.43.0 From 820828276e1f51f523e05dcebe01efdec85c0261 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 01:59:36 +0300 Subject: [PATCH 272/474] fix: get sub url without first "api" --- Endpoint/Common/Services/UrlHelper.cs | 24 +++++++++++++++++++ .../Configuration/SetupController.cs | 2 +- Endpoint/Controllers/V1/AuthController.cs | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Endpoint/Common/Services/UrlHelper.cs b/Endpoint/Common/Services/UrlHelper.cs index b7c7a7a..923c797 100644 --- a/Endpoint/Common/Services/UrlHelper.cs +++ b/Endpoint/Common/Services/UrlHelper.cs @@ -18,5 +18,29 @@ public static class UrlHelper } public static string GetSubPath => CreateSubPath(Environment.GetEnvironmentVariable("ACTUAL_SUB_PATH")); + + public static string GetSubPathWithoutFirstApiName + { + get + { + var path = GetSubPath; + + if (string.IsNullOrEmpty(path) || path == "/") + return CreateSubPath(null); + + var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries); + + for (int i = 0; i < parts.Length; i++) + { + if (!parts[i].Equals("api", StringComparison.CurrentCultureIgnoreCase)) continue; + + parts = parts.Take(i).Concat(parts.Skip(i + 1)).ToArray(); + break; + } + + return CreateSubPath(string.Join("/", parts)); + } + } + public static string GetSubPathSwagger => CreateSubPath(Environment.GetEnvironmentVariable("SWAGGER_SUB_PATH")); } \ No newline at end of file diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 827257f..666c97d 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -69,7 +69,7 @@ public partial class SetupController( Response.Cookies.Append("AuthToken", token, new CookieOptions { - Path = UrlHelper.GetSubPath + "api", + Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", Domain = UrlHelper.CurrentDomain(ControllerContext.HttpContext), Secure = true, HttpOnly = true diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 1ecb0fe..f16a596 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -29,7 +29,7 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass var cookieOptions = new CookieOptions { Expires = expires, - Path = UrlHelper.GetSubPath + "api", + Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", Domain = UrlHelper.CurrentDomain(ControllerContext.HttpContext), Secure = true, HttpOnly = true -- 2.43.0 From 76fd1347ce11f725dd352a966c2979b2f59fe299 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 01:59:54 +0300 Subject: [PATCH 273/474] build: add ACTUAL_SUB_PATH --- .gitea/workflows/deploy-stage.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/deploy-stage.yaml b/.gitea/workflows/deploy-stage.yaml index 60742a0..f023f54 100644 --- a/.gitea/workflows/deploy-stage.yaml +++ b/.gitea/workflows/deploy-stage.yaml @@ -75,6 +75,7 @@ jobs: -e SECURITY_HASH_SIZE=$SECURITY_HASH_SIZE \ -e SECURITY_HASH_TOKEN=$SECURITY_HASH_TOKEN \ -e SECURITY_SALT_SIZE=$SECURITY_SALT_SIZE \ + -e ACTUAL_SUB_PATH=api \ $DOCKER_IMAGE " -- 2.43.0 From 1c27bffa7355744fc30f9562061822601bc388ea Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 02:28:18 +0300 Subject: [PATCH 274/474] build: remove pdb files --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e1856c5..2f0c414 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,9 +15,10 @@ ENV NUGET_PASSWORD=$NUGET_PASSWORD RUN dotnet restore ./Backend.sln --configfile nuget.config WORKDIR /app WORKDIR /src -RUN dotnet publish ./Endpoint/Endpoint.csproj -c Release -o /app +RUN dotnet publish ./Endpoint/Endpoint.csproj -c Release --self-contained false -p:PublishSingleFile=false -o /app FROM base AS final WORKDIR /app COPY --from=build /app . +RUN find . -name "*.pdb" -type f -delete ENTRYPOINT ["dotnet", "Mirea.Api.Endpoint.dll"] \ No newline at end of file -- 2.43.0 From ab660f69c81f1f20881eaacc6cf6a8668aa865ee Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 02:37:12 +0300 Subject: [PATCH 275/474] fix: add listen any ip instead localhost --- Endpoint/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 4a2e108..bebb589 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -69,7 +69,7 @@ public class Program builder.WebHost.ConfigureKestrel(options => { - options.ListenLocalhost( + options.ListenAnyIP( int.Parse(builder.Configuration.GetValue("INTERNAL_PORT") ?? "8080")); }); -- 2.43.0 From 279ca9647b021c26e518d2819e943ae5630e0996 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 02:50:02 +0300 Subject: [PATCH 276/474] fix: path to wwwroot --- Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs index 5463d02..f0ddea2 100644 --- a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs @@ -61,7 +61,7 @@ public static class SwaggerConfiguration app.UseSwagger(); app.UseSwaggerUI(options => { - options.InjectStylesheet("/css/swagger/SwaggerDark.css"); + options.InjectStylesheet($"{UrlHelper.GetSubPath}css/swagger/SwaggerDark.css"); var provider = services.GetService(); foreach (var description in provider!.ApiVersionDescriptions) -- 2.43.0 From 3898463bc426c89dcfe245061e96b8ae777054d1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 02:50:20 +0300 Subject: [PATCH 277/474] build: set SWAGGER_SUB_PATH --- .gitea/workflows/deploy-stage.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/deploy-stage.yaml b/.gitea/workflows/deploy-stage.yaml index f023f54..4da1ace 100644 --- a/.gitea/workflows/deploy-stage.yaml +++ b/.gitea/workflows/deploy-stage.yaml @@ -76,6 +76,7 @@ jobs: -e SECURITY_HASH_TOKEN=$SECURITY_HASH_TOKEN \ -e SECURITY_SALT_SIZE=$SECURITY_SALT_SIZE \ -e ACTUAL_SUB_PATH=api \ + -e SWAGGER_SUB_PATH=swagger \ $DOCKER_IMAGE " -- 2.43.0 From 80b46754ad2efd63fbb2ebd3ffadf280aede0c4b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 5 Jul 2024 23:14:45 +0300 Subject: [PATCH 278/474] feat: add new generator key --- Endpoint/Program.cs | 2 +- Security/Services/GeneratorKey.cs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index bebb589..06fc830 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -79,7 +79,7 @@ public class Program if (string.IsNullOrEmpty(secretForward!.SecretForwardToken)) { - secretForward.SecretForwardToken = GeneratorKey.GenerateBase64(18); + secretForward.SecretForwardToken = GeneratorKey.GenerateAlphaNumeric(16); secretForward.SaveSetting(); } diff --git a/Security/Services/GeneratorKey.cs b/Security/Services/GeneratorKey.cs index 79a0430..b71bfab 100644 --- a/Security/Services/GeneratorKey.cs +++ b/Security/Services/GeneratorKey.cs @@ -1,11 +1,30 @@ using System; using System.Buffers.Text; +using System.Linq; using System.Text; namespace Mirea.Api.Security.Services; public static class GeneratorKey { + public static string GenerateAlphaNumeric(int size, string? excludes = null, string? includes = null) + { + var random = new Random(); + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + string charsForGenerate = excludes? + .Aggregate(chars, (current, ex) => current.Replace(ex.ToString(), string.Empty)) ?? chars; + + if (!string.IsNullOrEmpty(includes)) + charsForGenerate = includes + .Aggregate(charsForGenerate, (current, include) => + current.Contains(include) ? current : current + include); + + return new string(Enumerable.Repeat(charsForGenerate, size) + .Select(s => s[random.Next(s.Length)]) + .ToArray()); + } + public static ReadOnlySpan GenerateBytes(int size) { var key = new byte[size]; -- 2.43.0 From 3326b17d74222049a51fa9be899f5110b3bf0da4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 7 Jul 2024 23:20:54 +0300 Subject: [PATCH 279/474] fix: try disable origins --- Endpoint/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 06fc830..fbcfb82 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -60,9 +60,8 @@ public class Program { options.AddPolicy("AllowAll", policy => { - policy.AllowAnyHeader(); policy.AllowAnyMethod(); - policy.WithOrigins("http://localhost:4200"); + policy.AllowAnyHeader(); policy.AllowCredentials(); }); }); -- 2.43.0 From 497b7f146b2f0bd19706523dce1ef0ba59066a95 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jul 2024 00:00:32 +0300 Subject: [PATCH 280/474] fix: add use forwarded headers and clear known --- Endpoint/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index fbcfb82..9bc37de 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -84,6 +84,8 @@ public class Program options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.ForwardedForHeaderName = secretForward.SecretForwardToken + "-X-Forwarded-For"; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); }); builder.Services.AddCustomApiVersioning(); @@ -94,6 +96,7 @@ public class Program var app = builder.Build(); + app.UseForwardedHeaders(); app.UseStaticFiles(UrlHelper.GetSubPath.TrimEnd('/')); app.UseCors("AllowAll"); app.UseCustomSerilog(); -- 2.43.0 From c6ca717b89f9d03240a639c32ed428e3f2e032b9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 8 Jul 2024 01:56:14 +0300 Subject: [PATCH 281/474] feat: add new endpoint --- Endpoint/Controllers/Configuration/SetupController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 666c97d..2772e4f 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -62,6 +62,10 @@ public partial class SetupController( return Ok(Convert.ToBase64String(token)); } + [HttpGet("IsConfigured")] + public ActionResult IsConfigured() => + !notConfigureService.IsMaintenanceMode; + [HttpGet("CheckToken")] public ActionResult CheckToken([FromQuery] string token) { -- 2.43.0 From 837205f66e7e6083c322a41cf91934d80a5a302a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 24 Jul 2024 21:50:04 +0300 Subject: [PATCH 282/474] build: update ref --- Endpoint/Endpoint.csproj | 8 ++++---- SqlData/Application/Application.csproj | 4 ++-- SqlData/Persistence/Persistence.csproj | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 3ff7872..e193257 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -23,8 +23,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -32,11 +32,11 @@ - + - + diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 532d17a..10fefad 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index ce3d3a4..34264f0 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -13,9 +13,8 @@ - - - + + -- 2.43.0 From c7b401eae78785b4fa3fe97053beff92a1d95760 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 10 Aug 2024 22:04:11 +0300 Subject: [PATCH 283/474] fix: save log with PathBuilder --- Endpoint/Configuration/AppConfig/LoggerConfiguration.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs index 595810e..cd9583a 100644 --- a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs +++ b/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs @@ -28,7 +28,9 @@ public static class LoggerConfiguration if (generalConfig?.EnableLogToFile == true) { - if (!string.IsNullOrEmpty(generalConfig.LogFilePath) && Directory.Exists(PathBuilder.Combine(generalConfig.LogFilePath))) + generalConfig.LogFilePath = PathBuilder.Combine(generalConfig.LogFilePath ?? string.Empty); + + if (!string.IsNullOrEmpty(generalConfig.LogFilePath) && Directory.Exists(generalConfig.LogFilePath)) Directory.CreateDirectory(generalConfig.LogFilePath); configuration.WriteTo.File( -- 2.43.0 From c189cc69557a01bbeeb699232798c1f8607d88ab Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 10 Aug 2024 22:34:52 +0300 Subject: [PATCH 284/474] docs: add readme file --- README.md | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e97794..d2d82f6 100644 --- a/README.md +++ b/README.md @@ -1 +1,204 @@ -# Backend \ No newline at end of file +# MIREA schedule by Winsomnia + +[![NET Release](https://img.shields.io/badge/v8.0-8?style=flat-square&label=.NET&labelColor=512BD4&color=606060)](https://dotnet.microsoft.com/download/dotnet/8.0) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT) + +This project is a backend part of an application developed on ASP.NET , which provides an API for getting schedule data. + +The main task is to provide convenient and flexible tools for accessing the schedule in various ways. + +## Purpose + +The purpose of this project is to provide convenient and flexible tools for obtaining schedule data. + +In a situation where existing resources provide limited functionality or an inconvenient interface, this project aims to provide users with a simple and effective tool for accessing information about class schedules. + +Developing your own API and using your own tools for downloading and processing data allows you to ensure the reliability, flexibility and extensibility of the application functionality. + +## Features + +1. **Flexible API**: The API provides a variety of methods for accessing schedule data. Unlike competitors that provide a limited set of endpoints, this application provides a wider range of functionality, allowing you to get data about groups, campuses, faculties, classrooms and teachers. You can get all the data at once or select specific IDs with the details that are needed. +2. **Database Providers**: The application provides the capability of various database providers. +3. **Using self-written packages**: The project uses two proprietary NuGet packages. One of them is designed for parsing schedules, and the other is for downloading Excel spreadsheets from external sites. + +## Project status + +The project is under development. Further development will be aimed at expanding the functionality and improving the user experience. + +# Environment Variables + +This table provides information about the environment variables that are used in the application. These variables are stored in the [.env](.env) file. + +In addition to these variables, you also need to fill in a file with settings in json format. The web application provided by this project already has everything necessary to configure the file in the Client-Server communication format via the controller. If you need to get the configuration file otherwise, then you need to refer to the classes that provide configuration-related variables. + +Please note that the application will not work correctly if you do not fill in the required variables. + +| Variable | Default | Description | Required | +|---------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| PATH_TO_SAVE | ❌ | The path to save the data. Saving logs (if the full path is not specified), databases (if Sqlite), and other data that should be saved in a place other than where the program is launched. | ✔ | +| SECURITY_SIGNING_TOKEN | ❌ | JWT signature token. This token will be used to create and verify the signature of JWT tokens. The token must be equal to 64 characters. | ✔ | +| SECURITY_ENCRYPTION_TOKEN | ❌ | Token for JWT encryption. This token will be used to encrypt and decrypt JWT tokens. The token must be equal to 32 characters. | ✔ | +| SECURITY_LIFE_TIME_RT | 1440 | Time in minutes after which the Refresh Token will become invalid. | ❌ | +| SECURITY_LIFE_TIME_JWT | 15 | Time in minutes after which the JWT token will become invalid. | ❌ | +| SECURITY_LIFE_TIME_1_FA | 15 | Time in minutes after which the token of the first factor will become invalid. | ❌ | +| SECURITY_JWT_ISSUER | ❌ | An identifier that points to the server that created the token. | ✔ | +| SECURITY_JWT_AUDIENCE | ❌ | ID of the audience for which the token is intended. | ✔ | +| SECURITY_HASH_ITERATION | ❌ | The number of iterations used to hash passwords in the Argon2 algorithm. | ✔ | +| SECURITY_HASH_MEMORY | ❌ | The amount of memory used to hash passwords in the Argon2 algorithm. | ✔ | +| SECURITY_HASH_PARALLELISM | ❌ | Parallelism determines how many of the memory fragments divided into strips will be used to generate a hash. | ✔ | +| SECURITY_HASH_SIZE | 32 | The size of the output hash generated by the password hashing algorithm. | ❌ | +| SECURITY_HASH_TOKEN | ❌ | Additional protection for Argon2. We recommend installing a token so that even if the data is compromised, an attacker cannot brute force a password without a token. | ❌ | +| SECURITY_SALT_SIZE | 16 | The size of the salt used to hash passwords. The salt is a random value added to the password before hashing to prevent the use of rainbow hash tables and other attacks. | ❌ | + +# Installation + +If you want to make a fork of this project or place the Backend application on your hosting yourself, then follow the instructions below. + +1. [Docker Installation](#docker-installation) +2. [Docker Self Build](#docker-self-build) +3. [Manual Installation](#manual-installation) +4. [Self Build](#self-build) + +## Docker Installation + +**Requirements** + +- Docker + +To launch the application, pull out the application image: + +```bash +docker pull winsomnia/mirea-backend:latest +``` + +Next, you need to fill in the required fields inside.env and pass it when the container is started: + +```bash +docker run -d --name mirea-backend -p 8080 \ +--restart=on-failure:10 \ +-v mirea-data:/data \ +-e PATH_TO_SAVE=/data \ +-e .env \ +winsomnia/mirea-backend:latest +``` + +Using the `--name` option, you can specify your container name, for example: `--name mirea`. + +With the `-p` option, you can specify the port you need: `-p 80:8080`. + +It is necessary to tell the application exactly where to save the data so that it does not disappear when the container is deleted. + +To do this, replace the `-v` option, where you need to specify the path to the data on the host first, and then using `:` specify the path inside the container. `-v /nas/mirea/backend:/myfolder`. + +At the same time, do not forget to replace inside [.env](.env) `PATH_TO_SAVE` with what you specify in the `-v` option. In our case, it will be `PATH_TO_SAVE=/myfolder`. + +That's it, the container is running! + +## Docker Self Build + +- Docker + +To build your own application image, run: + +```bash +docker build -t my-name/mirea-backend:latest . +``` + +Where `-t` indicates the name and version of the image. You can specify their `your-name/image-name:version`. + +Now the image is ready. To launch the container, refer to [Docker Installation](#docker-installation), do not forget to specify the name of the image that you have built. + +## Manual Installation + +**Requirements** + +- ASP.NET Core runtime 8.0 + +To install using a pre-built application, follow these steps: + +1. [Install ASP.NET](#install-aspnet) +2. [Download Package](#download-package) +3. [Run](#run) + +### Install ASP.NET + +Installation ASP.NET it depends on the specific platform. +Go to [Microsoft website](https://dotnet.microsoft.com/download/dotnet/8.0 ) and find your platform. Follow the installation instructions. + +### Download Package + +The latest versions of the packages can be found in [releases](https://git.winsomnia.net/Winsomnia/MireaBackend/releases ). If there is no build for your platform, go [to the Self Build section](#self-build). + +### Run + +Go to the directory with the application. + +Don't forget to set up [required configurations](#environment-variables) for the application to work. + +Run the program. + +`Debian/Ubuntu` + +```bash +dotnet Mirea.Api.Endpoint.dll +``` + +## Self Build + +Requirements + +- ASP.NET Core runtime 8.0 +- .NET 8.0 sdk (for build) +- git + +To build your own version of the program, follow these steps: + +1. [Install .NET SDK](#install-net-sdk) +2. [Clone The Repository](#clone-the-repository) +3. [Build Self Release](#build-self-release) + +### Install NET SDK + +Installation.The NET SDK depends on the specific platform. +Go to [Microsoft website](https://dotnet.microsoft.com/download/dotnet/8.0 ) and find your platform. Follow the installation instructions. + +### Clone The Repository + +Install git in advance or clone the repository in another way. + +> ⚠ It is advisable to clone the `master` branch, the rest of the branches may work unstable. + +```bash +git clone https://git.winsomnia.net/Winsomnia/MireaBackend.git \ +cd DoctorTelegramBot +``` + +### Build Self Release + +Go to the project folder. Restore the dependencies using the command: + +```bash +dotnet restore +``` + +Let's move on to the assembly. + +Variables: + +- `` — Platform for which the build will be performed. +- `` — System architecture. Example: x86 x64. +- `` — The directory where the assembly will be saved. + +```bash +dotnet publish "./Endpoint/Endpoint.csproj" -c Release -r - -framework net8.0 -o /p:SelfContained=false /p:UseAppHost=false +``` + +The release is now in the directory you specified. To run it, look at the [startup instructions](#run). + +# Contribution and Feedback + +You can contribute to the project by creating pull requests. Any feedback is welcome to improve the project. + +# License + +This project is licensed under the [MIT License](LICENSE.txt). -- 2.43.0 From c51a9cecc97f63fb8d84ea0c6167d7cb3efe88b9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 10 Aug 2024 23:03:28 +0300 Subject: [PATCH 285/474] fix: storing data protection keys --- Endpoint/Program.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 9bc37de..9883828 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Configuration; @@ -94,13 +95,15 @@ public class Program builder.Services.AddJwtToken(builder.Configuration); builder.Services.AddSecurity(builder.Configuration); + builder.Services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo(PathBuilder.Combine("DataProtection"))); + var app = builder.Build(); app.UseForwardedHeaders(); app.UseStaticFiles(UrlHelper.GetSubPath.TrimEnd('/')); app.UseCors("AllowAll"); app.UseCustomSerilog(); - app.UseForwardedHeaders(); using (var scope = app.Services.CreateScope()) { -- 2.43.0 From b1250616a79236132e4e83986357b1c58f96ca3f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 10 Aug 2024 23:11:43 +0300 Subject: [PATCH 286/474] refactor: use this in static method --- Endpoint/Common/Services/UrlHelper.cs | 2 +- Endpoint/Controllers/Configuration/SetupController.cs | 4 +++- Endpoint/Controllers/V1/AuthController.cs | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Endpoint/Common/Services/UrlHelper.cs b/Endpoint/Common/Services/UrlHelper.cs index 923c797..a8ecbd7 100644 --- a/Endpoint/Common/Services/UrlHelper.cs +++ b/Endpoint/Common/Services/UrlHelper.cs @@ -6,7 +6,7 @@ namespace Mirea.Api.Endpoint.Common.Services; public static class UrlHelper { - public static string CurrentDomain(HttpContext context) => + public static string GetCurrentDomain(this HttpContext context) => context.Request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? context.Request.Host.Host; private static string CreateSubPath(string? path) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 2772e4f..45ba240 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -74,9 +74,11 @@ public partial class SetupController( Response.Cookies.Append("AuthToken", token, new CookieOptions { Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", - Domain = UrlHelper.CurrentDomain(ControllerContext.HttpContext), + Domain = HttpContext.GetCurrentDomain(), +#if !DEBUG Secure = true, HttpOnly = true +#endif }); return Ok(true); } diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index f16a596..88154b2 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -30,9 +30,11 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass { Expires = expires, Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", - Domain = UrlHelper.CurrentDomain(ControllerContext.HttpContext), + Domain = HttpContext.GetCurrentDomain(), +#if !DEBUG Secure = true, HttpOnly = true +#endif }; Response.Cookies.Append(name, value, cookieOptions); -- 2.43.0 From 80dc2e412cb311b9129a8473304f96d4c8ed261e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 12 Aug 2024 21:36:07 +0300 Subject: [PATCH 287/474] refactor: change Invoke to async --- Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs | 2 +- Endpoint/Middleware/JwtRevocationMiddleware.cs | 2 +- Endpoint/Middleware/MaintenanceModeMiddleware.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs index 62c41e2..5800f7d 100644 --- a/Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs @@ -11,7 +11,7 @@ namespace Mirea.Api.Endpoint.Middleware; public class CustomExceptionHandlerMiddleware(RequestDelegate next) { - public async Task Invoke(HttpContext context) + public async Task InvokeAsync(HttpContext context) { try { diff --git a/Endpoint/Middleware/JwtRevocationMiddleware.cs b/Endpoint/Middleware/JwtRevocationMiddleware.cs index 97818c7..5d657f8 100644 --- a/Endpoint/Middleware/JwtRevocationMiddleware.cs +++ b/Endpoint/Middleware/JwtRevocationMiddleware.cs @@ -6,7 +6,7 @@ namespace Mirea.Api.Endpoint.Middleware; public class JwtRevocationMiddleware(RequestDelegate next) { - public async Task Invoke(HttpContext context, IRevokedToken revokedTokenStore) + public async Task InvokeAsync(HttpContext context, IRevokedToken revokedTokenStore) { if (context.Request.Headers.ContainsKey("Authorization")) { diff --git a/Endpoint/Middleware/MaintenanceModeMiddleware.cs b/Endpoint/Middleware/MaintenanceModeMiddleware.cs index 2bd5d3b..128ed7c 100644 --- a/Endpoint/Middleware/MaintenanceModeMiddleware.cs +++ b/Endpoint/Middleware/MaintenanceModeMiddleware.cs @@ -13,7 +13,7 @@ public class MaintenanceModeMiddleware(RequestDelegate next, IMaintenanceModeSer return endpoint?.Metadata.GetMetadata() != null; } - public async Task Invoke(HttpContext context) + public async Task InvokeAsync(HttpContext context) { if (!maintenanceModeService.IsMaintenanceMode && !maintenanceModeNotConfigureService.IsMaintenanceMode || IsIgnoreMaintenanceMode(context)) await next(context); -- 2.43.0 From 565252382c10020c6899aaece6437758757e1bdc Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 12 Aug 2024 21:54:05 +0300 Subject: [PATCH 288/474] feat: add cache control in response --- .../Common/Attributes/CacheMaxAgeAttribute.cs | 26 ++++++ Endpoint/Controllers/V1/ScheduleController.cs | 13 +-- Endpoint/Middleware/CacheMaxAgeMiddleware.cs | 81 +++++++++++++++++++ Endpoint/Program.cs | 3 +- 4 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 Endpoint/Common/Attributes/CacheMaxAgeAttribute.cs create mode 100644 Endpoint/Middleware/CacheMaxAgeMiddleware.cs diff --git a/Endpoint/Common/Attributes/CacheMaxAgeAttribute.cs b/Endpoint/Common/Attributes/CacheMaxAgeAttribute.cs new file mode 100644 index 0000000..f61778f --- /dev/null +++ b/Endpoint/Common/Attributes/CacheMaxAgeAttribute.cs @@ -0,0 +1,26 @@ +using System; + +namespace Mirea.Api.Endpoint.Common.Attributes; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class CacheMaxAgeAttribute : Attribute +{ + public int MaxAge { get; } + + public CacheMaxAgeAttribute(int days = 0, int hours = 0, int minutes = 0) + { + MaxAge = (int)new TimeSpan(days, hours, minutes, 0).TotalSeconds; + } + + public CacheMaxAgeAttribute(int minutes) : this(0, 0, minutes) + { + } + + public CacheMaxAgeAttribute(bool usingSetting = false) + { + if (usingSetting) + MaxAge = -1; + else + MaxAge = 0; + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 0b16686..c4a5941 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -17,11 +17,14 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] +[CacheMaxAge(true)] public class ScheduleController(IMediator mediator, IOptionsSnapshot config) : BaseController { + [CacheMaxAge(1, 0)] [HttpGet("StartTerm")] public ActionResult GetStartTerm() => config.Value.ScheduleSettings!.StartTerm; + [CacheMaxAge(1, 0)] [HttpGet("PairPeriod")] public ActionResult> GetPairPeriod() => config.Value.ScheduleSettings!.PairPeriod.ConvertToDto(); @@ -105,7 +108,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot await Get(new ScheduleRequest - { + { Disciplines = disciplines, IsEven = isEven, Groups = [id], @@ -133,7 +136,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot await Get(new ScheduleRequest - { + { Disciplines = disciplines, IsEven = isEven, Groups = groups, @@ -161,7 +164,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot await Get(new ScheduleRequest - { + { Disciplines = disciplines, IsEven = isEven, Groups = groups, @@ -189,11 +192,11 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot await Get(new ScheduleRequest - { + { Disciplines = [id], IsEven = isEven, Groups = groups, Professors = professors, LectureHalls = lectureHalls }); - } \ No newline at end of file +} \ No newline at end of file diff --git a/Endpoint/Middleware/CacheMaxAgeMiddleware.cs b/Endpoint/Middleware/CacheMaxAgeMiddleware.cs new file mode 100644 index 0000000..0a8f788 --- /dev/null +++ b/Endpoint/Middleware/CacheMaxAgeMiddleware.cs @@ -0,0 +1,81 @@ +using Cronos; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Mirea.Api.Endpoint.Common.Attributes; +using Mirea.Api.Endpoint.Common.Settings; +using System; +using System.Reflection; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Middleware; + +public class CacheMaxAgeMiddleware(RequestDelegate next, IServiceProvider serviceProvider) +{ + public async Task InvokeAsync(HttpContext context) + { + if (!context.Response.StatusCode.ToString().StartsWith('2')) + { + await next(context); + return; + } + + var endpoint = context.GetEndpoint(); + + var actionDescriptor = endpoint?.Metadata.GetMetadata(); + + if (actionDescriptor == null) + { + await next(context); + return; + } + + var controllerType = actionDescriptor.ControllerTypeInfo; + var methodInfo = actionDescriptor.MethodInfo; + + var maxAgeAttribute = methodInfo.GetCustomAttribute() ?? controllerType.GetCustomAttribute(); + + if (maxAgeAttribute == null) + { + await next(context); + return; + } + + switch (maxAgeAttribute.MaxAge) + { + case < 0: + { + DateTime? nextDate; + var now = DateTime.UtcNow; + + using (var scope = serviceProvider.CreateScope()) + { + var updateCronString = scope.ServiceProvider.GetRequiredService>().Value.ScheduleSettings?.CronUpdateSchedule; + + if (string.IsNullOrEmpty(updateCronString) || + !CronExpression.TryParse(updateCronString, CronFormat.Standard, out var updateCron)) + { + await next(context); + return; + } + + nextDate = updateCron.GetNextOccurrence(now); + } + + if (!nextDate.HasValue) + { + await next(context); + return; + } + + context.Response.Headers.CacheControl = "max-age=" + (int)(nextDate.Value - now).TotalSeconds; + break; + } + case > 0: + context.Response.Headers.CacheControl = "max-age=" + maxAgeAttribute.MaxAge; + break; + } + + await next(context); + } +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 9883828..1319332 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -125,9 +125,10 @@ public class Program app.UseCustomSwagger(app.Services); - app.UseMiddleware(); app.UseMiddleware(); + app.UseMiddleware(); app.UseMiddleware(); + app.UseMiddleware(); app.UseHttpsRedirection(); -- 2.43.0 From dee89b278b6290a396e35a1b0ed54e5556e4ad8b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 24 Aug 2024 02:25:29 +0300 Subject: [PATCH 289/474] refactor: set HttpOnly for debug mode too --- Endpoint/Controllers/Configuration/SetupController.cs | 4 ++-- Endpoint/Controllers/V1/AuthController.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 45ba240..86d7edd 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -75,9 +75,9 @@ public partial class SetupController( { Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", Domain = HttpContext.GetCurrentDomain(), + HttpOnly = true, #if !DEBUG - Secure = true, - HttpOnly = true + Secure = true #endif }); return Ok(true); diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 88154b2..71dd6c3 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -31,9 +31,9 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass Expires = expires, Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", Domain = HttpContext.GetCurrentDomain(), + HttpOnly = true, #if !DEBUG - Secure = true, - HttpOnly = true + Secure = true #endif }; @@ -49,9 +49,9 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [ApiExplorerSettings(IgnoreApi = true)] public void OnActionExecuting(ActionExecutingContext context) { - Ip = context.HttpContext.Connection.RemoteIpAddress?.ToString()!; - UserAgent = context.HttpContext.Request.Headers.UserAgent.ToString(); - Fingerprint = context.HttpContext.Request.Cookies["user_key"] ?? string.Empty; + Ip = HttpContext.Connection.RemoteIpAddress?.ToString()!; + UserAgent = Request.Headers.UserAgent.ToString(); + Fingerprint = Request.Cookies["user_key"] ?? string.Empty; RefreshToken = Request.Cookies["refresh_token"] ?? string.Empty; if (!string.IsNullOrWhiteSpace(Fingerprint)) return; -- 2.43.0 From 24c75e430639a23a798cd79a629deb00cdac5ce9 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 24 Aug 2024 02:26:11 +0300 Subject: [PATCH 290/474] refator: set fingerprint expire instead session mode --- Endpoint/Controllers/V1/AuthController.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 71dd6c3..f02cf76 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -40,11 +40,17 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass Response.Cookies.Append(name, value, cookieOptions); } - private void SetRefreshToken(string value, DateTimeOffset? expires = null) => + private void SetRefreshToken(string value, DateTimeOffset? expires = null) + { SetCookie("refresh_token", value, expires); + SetCookie("user_key", Fingerprint, expires); + } - private void SetFirstToken(string value, DateTimeOffset? expires = null) => + private void SetFirstToken(string value, DateTimeOffset? expires = null) + { SetCookie("authentication_token", value, expires); + SetCookie("user_key", Fingerprint, expires); + } [ApiExplorerSettings(IgnoreApi = true)] public void OnActionExecuting(ActionExecutingContext context) @@ -57,7 +63,6 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass if (!string.IsNullOrWhiteSpace(Fingerprint)) return; Fingerprint = Guid.NewGuid().ToString().Replace("-", ""); - SetCookie("user_key", Fingerprint); } [ApiExplorerSettings(IgnoreApi = true)] -- 2.43.0 From 31087a57c98536de963762111dec23ef7c41f7d5 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 24 Aug 2024 02:27:05 +0300 Subject: [PATCH 291/474] feat: add cache for api --- Endpoint/Controllers/V1/CampusController.cs | 1 + Endpoint/Controllers/V1/DisciplineController.cs | 1 + Endpoint/Controllers/V1/FacultyController.cs | 1 + Endpoint/Controllers/V1/GroupController.cs | 1 + Endpoint/Controllers/V1/LectureHallController.cs | 1 + Endpoint/Controllers/V1/ProfessorController.cs | 1 + 6 files changed, 6 insertions(+) diff --git a/Endpoint/Controllers/V1/CampusController.cs b/Endpoint/Controllers/V1/CampusController.cs index 4f4f101..a5dce32 100644 --- a/Endpoint/Controllers/V1/CampusController.cs +++ b/Endpoint/Controllers/V1/CampusController.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] +[CacheMaxAge(true)] public class CampusController(IMediator mediator) : BaseController { /// diff --git a/Endpoint/Controllers/V1/DisciplineController.cs b/Endpoint/Controllers/V1/DisciplineController.cs index 1de1136..7ab686a 100644 --- a/Endpoint/Controllers/V1/DisciplineController.cs +++ b/Endpoint/Controllers/V1/DisciplineController.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] +[CacheMaxAge(true)] public class DisciplineController(IMediator mediator) : BaseController { /// diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index 9652551..7d71fd1 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] +[CacheMaxAge(true)] public class FacultyController(IMediator mediator) : BaseController { /// diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index c12f3da..6197047 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] +[CacheMaxAge(true)] public class GroupController(IMediator mediator) : BaseController { private static int GetCourseNumber(string groupName) diff --git a/Endpoint/Controllers/V1/LectureHallController.cs b/Endpoint/Controllers/V1/LectureHallController.cs index c6e67b8..a5e62ba 100644 --- a/Endpoint/Controllers/V1/LectureHallController.cs +++ b/Endpoint/Controllers/V1/LectureHallController.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] +[CacheMaxAge(true)] public class LectureHallController(IMediator mediator) : BaseController { /// diff --git a/Endpoint/Controllers/V1/ProfessorController.cs b/Endpoint/Controllers/V1/ProfessorController.cs index 3093a38..7e3d8b6 100644 --- a/Endpoint/Controllers/V1/ProfessorController.cs +++ b/Endpoint/Controllers/V1/ProfessorController.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] +[CacheMaxAge(true)] public class ProfessorController(IMediator mediator) : BaseController { /// -- 2.43.0 From fba842acc3cd7a9367d09ecef7658fa071a1b9b0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 24 Aug 2024 04:30:31 +0300 Subject: [PATCH 292/474] feat: add a cache with a short lifetime --- Endpoint/Controllers/V1/AuthController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index f02cf76..275903b 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -6,6 +6,7 @@ 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.Services; using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Security.Common.Dto.Requests; @@ -162,5 +163,6 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [HttpGet("GetRole")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] + [CacheMaxAge(0, 0, 1)] public ActionResult GetRole() => Ok(AuthRoles.Admin); } -- 2.43.0 From 535bafa73a44e0f62e7caa0a87589e8982e7dd61 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 27 Aug 2024 21:35:35 +0300 Subject: [PATCH 293/474] fix: set 8-th mounth instead 9-th --- Endpoint/Controllers/V1/GroupController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index 6197047..fe6f079 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -26,7 +26,7 @@ public class GroupController(IMediator mediator) : BaseController // Convert a two-digit year to a four-digit one yearOfGroup += current.Year / 100 * 100; - return current.Year - yearOfGroup + (current.Month < 9 ? 0 : 1); + return current.Year - yearOfGroup + (current.Month < 8 ? 0 : 1); } /// -- 2.43.0 From f27d07fb5a6acb0bd611cb03447a8a6a27c31eb3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 27 Aug 2024 22:50:21 +0300 Subject: [PATCH 294/474] build: upgrade ref --- Endpoint/Endpoint.csproj | 21 +++++++++++---------- SqlData/Application/Application.csproj | 2 +- SqlData/Persistence/Persistence.csproj | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index e193257..e9ea786 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-b0 - 1.0.1.0 - 1.0.1.0 + 1.0.0-b3 + 1.0.1.3 + 1.0.1.3 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -23,20 +23,21 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + - - - + + + - + diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 10fefad..b72be72 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -16,7 +16,7 @@ - + diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 34264f0..7af3aa8 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -13,8 +13,8 @@ - - + + -- 2.43.0 From a27549092b27f4f2c9a93c98004862696bcaa450 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 27 Aug 2024 22:51:14 +0300 Subject: [PATCH 295/474] refactor: move checking password --- .../Controllers/Configuration/SetupController.cs | 5 ++--- Security/Services/PasswordHashService.cs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 86d7edd..022bf0b 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -23,7 +23,6 @@ using System.IO; using System.Net.Mail; using System.Runtime.InteropServices; using System.Security.Cryptography; -using System.Text.RegularExpressions; namespace Mirea.Api.Endpoint.Controllers.Configuration; @@ -31,7 +30,7 @@ namespace Mirea.Api.Endpoint.Controllers.Configuration; [ApiController] [MaintenanceModeIgnore] [ApiExplorerSettings(IgnoreApi = true)] -public partial class SetupController( +public class SetupController( ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache, @@ -212,7 +211,7 @@ public partial class SetupController( [BadRequestResponse] public ActionResult CreateAdmin([FromBody] CreateUserRequest user) { - if (user.Password.Length < 8 || !PasswordExistUpperLetter().IsMatch(user.Password) || !PasswordExistSpecialSymbol().IsMatch(user.Password)) + if (PasswordHashService.HasPasswordInPolicySecurity(user.Password)) throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character."); if (!MailAddress.TryCreate(user.Email, out _)) diff --git a/Security/Services/PasswordHashService.cs b/Security/Services/PasswordHashService.cs index 8673222..df16f75 100644 --- a/Security/Services/PasswordHashService.cs +++ b/Security/Services/PasswordHashService.cs @@ -1,10 +1,11 @@ using Konscious.Security.Cryptography; using System; using System.Text; +using System.Text.RegularExpressions; namespace Mirea.Api.Security.Services; -public class PasswordHashService +public partial class PasswordHashService { public int SaltSize { private get; init; } public int HashSize { private get; init; } @@ -53,4 +54,15 @@ public class PasswordHashService public bool VerifyPassword(string password, string saltBase64, string hashBase64) => VerifyPassword(password, Convert.FromBase64String(saltBase64), Convert.FromBase64String(hashBase64)); + + public static bool HasPasswordInPolicySecurity(string password) => + password.Length >= 8 && + PasswordExistSpecialSymbol().IsMatch(password) && + PasswordExistUpperLetter().IsMatch(password); + + [GeneratedRegex("[A-Z]+")] + private static partial Regex PasswordExistUpperLetter(); + + [GeneratedRegex("[!@#$%^&*]+")] + private static partial Regex PasswordExistSpecialSymbol(); } \ No newline at end of file -- 2.43.0 From 592e8a1b4230e8c20f81d781dc3e42f687d97d23 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 27 Aug 2024 22:52:07 +0300 Subject: [PATCH 296/474] feat: add renew password --- Endpoint/Controllers/V1/AuthController.cs | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 275903b..9ee7f7d 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -7,6 +7,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.Services; using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Security.Common.Dto.Requests; @@ -165,4 +166,29 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [Authorize] [CacheMaxAge(0, 0, 1)] public ActionResult GetRole() => Ok(AuthRoles.Admin); + + [HttpPost("RenewPassword")] + [ApiExplorerSettings(IgnoreApi = true)] + [Localhost] + [BadRequestResponse] + public ActionResult RenewPassword([FromBody] string? password = null) + { + if (string.IsNullOrEmpty(password)) + password = string.Empty; + else if (!PasswordHashService.HasPasswordInPolicySecurity(password)) + throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character."); + + while (!PasswordHashService.HasPasswordInPolicySecurity(password)) + password = GeneratorKey.GenerateAlphaNumeric(16, includes: "!@#%^"); + + var (salt, hash) = passwordService.HashPassword(password); + + var admin = user.Value; + + admin.Salt = salt; + admin.PasswordHash = hash; + admin.SaveSetting(); + + return Ok(password); + } } -- 2.43.0 From ae0f437e2c6cab0f129315184996ac2c73f0f44e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 27 Aug 2024 22:58:05 +0300 Subject: [PATCH 297/474] fix: remove Regex --- Endpoint/Controllers/Configuration/SetupController.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 022bf0b..153af04 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -341,10 +341,4 @@ public class SetupController( return true; } - - [GeneratedRegex("[A-Z]+")] - private static partial Regex PasswordExistUpperLetter(); - - [GeneratedRegex("[!@#$%^&*]+")] - private static partial Regex PasswordExistSpecialSymbol(); } \ No newline at end of file -- 2.43.0 From 6f9bfd3880f8ac57aad5625522628c5b60c3c52f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 7 Sep 2024 04:18:04 +0300 Subject: [PATCH 298/474] fix: set correct password condition --- Endpoint/Controllers/Configuration/SetupController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 153af04..5b7b149 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -211,7 +211,7 @@ public class SetupController( [BadRequestResponse] public ActionResult CreateAdmin([FromBody] CreateUserRequest user) { - if (PasswordHashService.HasPasswordInPolicySecurity(user.Password)) + if (!PasswordHashService.HasPasswordInPolicySecurity(user.Password)) throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character."); if (!MailAddress.TryCreate(user.Email, out _)) -- 2.43.0 From 0ced152fc96c59f09d9b3d106886b425743ee720 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 7 Sep 2024 04:19:05 +0300 Subject: [PATCH 299/474] fix: remove database when check connect to sqlite --- .../Configuration/SetupController.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 5b7b149..db69a18 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Data; using System.IO; +using System.Linq; using System.Net.Mail; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -93,6 +94,10 @@ public class SetupController( connection.Open(); connection.Close(); + if (connection is SqliteConnection) + SqliteConnection.ClearAllPools(); + } + var general = GeneralConfig; general.DbSettings = new DbSettings { @@ -152,12 +157,21 @@ public class SetupController( else Directory.CreateDirectory(path, UnixFileMode.UserRead | UnixFileMode.UserWrite); } - else + else if (Directory.GetDirectories(path).Length != 0 || + !Directory.GetFiles(path).Select(x => string.Equals(Path.GetFileName(x), "database.db3")).All(x => x)) throw new ControllerArgumentException("Such a folder exists. Enter a different name"); - string connectionString = $"Data Source={PathBuilder.Combine(path, "database.db3")}"; + var filePath = Path.Combine(path, "database.db3"); + var connectionString = $"Data Source={filePath}"; - return SetDatabase(connectionString, DbSettings.DatabaseEnum.Sqlite); + //System.IO.File.Create(filePath); + + var result = SetDatabase(connectionString, DbSettings.DatabaseEnum.Sqlite); + + foreach (var file in Directory.GetFiles(path)) + System.IO.File.Delete(file); + + return result; } [HttpPost("SetRedis")] -- 2.43.0 From 1e204c948cd85f1f88e70d482412fb457d3106bc Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 7 Sep 2024 04:19:51 +0300 Subject: [PATCH 300/474] refactor: set cookie name to attribute --- Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs | 3 ++- Endpoint/Controllers/Configuration/SetupController.cs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs b/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs index 81812bf..88db4c1 100644 --- a/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs +++ b/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs @@ -9,10 +9,11 @@ namespace Mirea.Api.Endpoint.Common.Attributes; [AttributeUsage(AttributeTargets.Method)] public class TokenAuthenticationAttribute : Attribute, IActionFilter { + public const string AuthToken = "AuthToken"; public void OnActionExecuting(ActionExecutingContext context) { var setupToken = context.HttpContext.RequestServices.GetRequiredService(); - if (!context.HttpContext.Request.Cookies.TryGetValue("AuthToken", out string? tokenFromCookie)) + if (!context.HttpContext.Request.Cookies.TryGetValue(AuthToken, out string? tokenFromCookie)) { context.Result = new UnauthorizedResult(); return; diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index db69a18..dafa4a6 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -69,9 +69,10 @@ public class SetupController( [HttpGet("CheckToken")] public ActionResult CheckToken([FromQuery] string token) { - if (!setupToken.MatchToken(Convert.FromBase64String(token))) return Unauthorized("The token is not valid"); + if (!setupToken.MatchToken(Convert.FromBase64String(token))) + return Unauthorized("The token is not valid"); - Response.Cookies.Append("AuthToken", token, new CookieOptions + Response.Cookies.Append(TokenAuthenticationAttribute.AuthToken, token, new CookieOptions { Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", Domain = HttpContext.GetCurrentDomain(), -- 2.43.0 From 65709e1f83d968e97a3677f4a2aa2f6a8e4b3020 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 7 Sep 2024 04:28:07 +0300 Subject: [PATCH 301/474] refactor: move files to another namespace --- .../ApiVersioningConfiguration.cs | 2 +- .../CacheConfiguration.cs | 2 +- .../EnvironmentConfiguration.cs | 2 +- .../JwtConfiguration.cs | 2 +- .../LoggerConfiguration.cs | 2 +- .../SecureConfiguration.cs | 2 +- .../SwaggerConfiguration.cs | 4 ++-- .../{Swagger => SwaggerOptions}/ConfigureSwaggerOptions.cs | 2 +- .../{Swagger => SwaggerOptions}/SwaggerDefaultValues.cs | 2 +- .../{Swagger => SwaggerOptions}/SwaggerExampleFilter.cs | 2 +- Endpoint/Program.cs | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) rename Endpoint/Configuration/{AppConfig => ApplicationConfiguration}/ApiVersioningConfiguration.cs (91%) rename Endpoint/Configuration/{AppConfig => ApplicationConfiguration}/CacheConfiguration.cs (91%) rename Endpoint/Configuration/{AppConfig => ApplicationConfiguration}/EnvironmentConfiguration.cs (97%) rename Endpoint/Configuration/{AppConfig => ApplicationConfiguration}/JwtConfiguration.cs (97%) rename Endpoint/Configuration/{AppConfig => ApplicationConfiguration}/LoggerConfiguration.cs (97%) rename Endpoint/Configuration/{AppConfig => ApplicationConfiguration}/SecureConfiguration.cs (92%) rename Endpoint/Configuration/{AppConfig => ApplicationConfiguration}/SwaggerConfiguration.cs (95%) rename Endpoint/Configuration/{Swagger => SwaggerOptions}/ConfigureSwaggerOptions.cs (95%) rename Endpoint/Configuration/{Swagger => SwaggerOptions}/SwaggerDefaultValues.cs (96%) rename Endpoint/Configuration/{Swagger => SwaggerOptions}/SwaggerExampleFilter.cs (88%) diff --git a/Endpoint/Configuration/AppConfig/ApiVersioningConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs similarity index 91% rename from Endpoint/Configuration/AppConfig/ApiVersioningConfiguration.cs rename to Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs index dd35369..f6abe4b 100644 --- a/Endpoint/Configuration/AppConfig/ApiVersioningConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.Extensions.DependencyInjection; -namespace Mirea.Api.Endpoint.Configuration.AppConfig; +namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; public static class ApiVersioningConfiguration { diff --git a/Endpoint/Configuration/AppConfig/CacheConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/CacheConfiguration.cs similarity index 91% rename from Endpoint/Configuration/AppConfig/CacheConfiguration.cs rename to Endpoint/Configuration/ApplicationConfiguration/CacheConfiguration.cs index d81c3cb..ccee805 100644 --- a/Endpoint/Configuration/AppConfig/CacheConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/CacheConfiguration.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Mirea.Api.Endpoint.Common.Settings; using Mirea.Api.Endpoint.Configuration.General.Settings; -namespace Mirea.Api.Endpoint.Configuration.AppConfig; +namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; public static class CacheConfiguration { diff --git a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/EnvironmentConfiguration.cs similarity index 97% rename from Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs rename to Endpoint/Configuration/ApplicationConfiguration/EnvironmentConfiguration.cs index 796ee3f..6ac90c6 100644 --- a/Endpoint/Configuration/AppConfig/EnvironmentConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/EnvironmentConfiguration.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace Mirea.Api.Endpoint.Configuration.AppConfig; +namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; public static class EnvironmentConfiguration { diff --git a/Endpoint/Configuration/AppConfig/JwtConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/JwtConfiguration.cs similarity index 97% rename from Endpoint/Configuration/AppConfig/JwtConfiguration.cs rename to Endpoint/Configuration/ApplicationConfiguration/JwtConfiguration.cs index 213e244..307fa34 100644 --- a/Endpoint/Configuration/AppConfig/JwtConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/JwtConfiguration.cs @@ -7,7 +7,7 @@ using Mirea.Api.Security.Common.Interfaces; using System; using System.Text; -namespace Mirea.Api.Endpoint.Configuration.AppConfig; +namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; public static class JwtConfiguration { diff --git a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/LoggerConfiguration.cs similarity index 97% rename from Endpoint/Configuration/AppConfig/LoggerConfiguration.cs rename to Endpoint/Configuration/ApplicationConfiguration/LoggerConfiguration.cs index cd9583a..6fc9e3c 100644 --- a/Endpoint/Configuration/AppConfig/LoggerConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/LoggerConfiguration.cs @@ -9,7 +9,7 @@ using Serilog.Filters; using Serilog.Formatting.Compact; using System.IO; -namespace Mirea.Api.Endpoint.Configuration.AppConfig; +namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; public static class LoggerConfiguration { diff --git a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/SecureConfiguration.cs similarity index 92% rename from Endpoint/Configuration/AppConfig/SecureConfiguration.cs rename to Endpoint/Configuration/ApplicationConfiguration/SecureConfiguration.cs index 5ea2fca..2b1f853 100644 --- a/Endpoint/Configuration/AppConfig/SecureConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/SecureConfiguration.cs @@ -6,7 +6,7 @@ using Mirea.Api.Endpoint.Configuration.General.Settings; using Mirea.Api.Security; using Mirea.Api.Security.Common.Interfaces; -namespace Mirea.Api.Endpoint.Configuration.AppConfig; +namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; public static class SecureConfiguration { diff --git a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs similarity index 95% rename from Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs rename to Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs index f0ddea2..ad13435 100644 --- a/Endpoint/Configuration/AppConfig/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs @@ -4,12 +4,12 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Mirea.Api.Endpoint.Common.Services; -using Mirea.Api.Endpoint.Configuration.Swagger; +using Mirea.Api.Endpoint.Configuration.SwaggerOptions; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.IO; -namespace Mirea.Api.Endpoint.Configuration.AppConfig; +namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; public static class SwaggerConfiguration { diff --git a/Endpoint/Configuration/Swagger/ConfigureSwaggerOptions.cs b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs similarity index 95% rename from Endpoint/Configuration/Swagger/ConfigureSwaggerOptions.cs rename to Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs index bf47325..40f302b 100644 --- a/Endpoint/Configuration/Swagger/ConfigureSwaggerOptions.cs +++ b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs @@ -5,7 +5,7 @@ using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using System; -namespace Mirea.Api.Endpoint.Configuration.Swagger; +namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions { diff --git a/Endpoint/Configuration/Swagger/SwaggerDefaultValues.cs b/Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs similarity index 96% rename from Endpoint/Configuration/Swagger/SwaggerDefaultValues.cs rename to Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs index 581393e..290f981 100644 --- a/Endpoint/Configuration/Swagger/SwaggerDefaultValues.cs +++ b/Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs @@ -6,7 +6,7 @@ using System; using System.Linq; using System.Text.Json; -namespace Mirea.Api.Endpoint.Configuration.Swagger; +namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; public class SwaggerDefaultValues : IOperationFilter { diff --git a/Endpoint/Configuration/Swagger/SwaggerExampleFilter.cs b/Endpoint/Configuration/SwaggerOptions/SwaggerExampleFilter.cs similarity index 88% rename from Endpoint/Configuration/Swagger/SwaggerExampleFilter.cs rename to Endpoint/Configuration/SwaggerOptions/SwaggerExampleFilter.cs index 2af76f4..0309293 100644 --- a/Endpoint/Configuration/Swagger/SwaggerExampleFilter.cs +++ b/Endpoint/Configuration/SwaggerOptions/SwaggerExampleFilter.cs @@ -3,7 +3,7 @@ using Mirea.Api.Endpoint.Common.Attributes; using Swashbuckle.AspNetCore.SwaggerGen; using System.Reflection; -namespace Mirea.Api.Endpoint.Configuration.Swagger; +namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; public class SwaggerExampleFilter : ISchemaFilter { diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 1319332..dbf26e7 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -11,7 +11,7 @@ using Mirea.Api.DataAccess.Persistence.Common; using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Common.Settings; -using Mirea.Api.Endpoint.Configuration.AppConfig; +using Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; using Mirea.Api.Endpoint.Configuration.General; using Mirea.Api.Endpoint.Configuration.General.Validators; using Mirea.Api.Endpoint.Middleware; -- 2.43.0 From e8450400c7fbd90833595bf05f5dd2a432d5ac17 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 7 Sep 2024 04:56:41 +0300 Subject: [PATCH 302/474] build: update ref --- ApiDto/ApiDto.csproj | 6 +++--- Endpoint/Endpoint.csproj | 10 +++++----- Security/Security.csproj | 6 +++--- SqlData/Application/Application.csproj | 6 +++--- SqlData/Persistence/Persistence.csproj | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ApiDto/ApiDto.csproj b/ApiDto/ApiDto.csproj index ffeb91a..70b9ee2 100644 --- a/ApiDto/ApiDto.csproj +++ b/ApiDto/ApiDto.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-a0 - 1.0.0.0 - 1.0.0.0 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.Dto $(AssemblyName) True diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index e9ea786..946a6fa 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-b3 - 1.0.1.3 - 1.0.1.3 + 1.0.0-rc0 + 1.0.2.0 + 1.0.2.0 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -34,8 +34,8 @@ - - + + diff --git a/Security/Security.csproj b/Security/Security.csproj index 1727334..5287b7a 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-rc0 - 1.0.2.0 - 1.0.2.0 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.Security $(AssemblyName) diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index b72be72..9c7f6dd 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-rc0 - 1.0.2.0 - 1.0.2.0 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.DataAccess.Application $(AssemblyName) diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 7af3aa8..661c648 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-rc0 - 1.0.2.0 - 1.0.2.0 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.DataAccess.Persistence $(AssemblyName) -- 2.43.0 From 332e5a013b4fc1b99f0651aa7330113710417997 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 8 Sep 2024 03:24:32 +0300 Subject: [PATCH 303/474] fix: add forgotten changes --- Endpoint/Controllers/Configuration/SetupController.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index dafa4a6..a7c1643 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -90,10 +90,11 @@ public class SetupController( { try { - using var connection = new TConnection(); - connection.ConnectionString = connectionString; - connection.Open(); - connection.Close(); + using (var connection = new TConnection()) + { + connection.ConnectionString = connectionString; + connection.Open(); + connection.Close(); if (connection is SqliteConnection) SqliteConnection.ClearAllPools(); -- 2.43.0 From 88d78dfab3fa98ae3959da01de04a54ab4f9e935 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Sep 2024 06:00:07 +0300 Subject: [PATCH 304/474] build: update ref --- .../ApiVersioningConfiguration.cs | 9 ++----- .../SwaggerConfiguration.cs | 4 +-- .../SwaggerOptions/ConfigureSwaggerOptions.cs | 2 +- .../Configuration/SetupController.cs | 3 ++- Endpoint/Controllers/V1/AuthController.cs | 3 ++- Endpoint/Controllers/V1/CampusController.cs | 3 ++- .../Controllers/V1/DisciplineController.cs | 3 ++- Endpoint/Controllers/V1/FacultyController.cs | 3 ++- Endpoint/Controllers/V1/GroupController.cs | 3 ++- .../Controllers/V1/LectureHallController.cs | 3 ++- .../Controllers/V1/ProfessorController.cs | 3 ++- Endpoint/Controllers/V1/ScheduleController.cs | 3 ++- Endpoint/Endpoint.csproj | 26 +++++++++++++++---- SqlData/Application/Application.csproj | 12 ++++----- 14 files changed, 50 insertions(+), 30 deletions(-) diff --git a/Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs index f6abe4b..f92706e 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Versioning; +using Asp.Versioning; using Microsoft.Extensions.DependencyInjection; namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; @@ -14,14 +13,10 @@ public static class ApiVersioningConfiguration options.AssumeDefaultVersionWhenUnspecified = true; options.ReportApiVersions = true; options.ApiVersionReader = new UrlSegmentApiVersionReader(); - }); - - services.AddVersionedApiExplorer(options => + }).AddApiExplorer(options => { options.GroupNameFormat = "'v'VVV"; options.SubstituteApiVersionInUrl = true; }); - - services.AddEndpointsApiExplorer(); } } \ No newline at end of file diff --git a/Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs b/Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs index ad13435..74a0e7f 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs @@ -1,5 +1,5 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Asp.Versioning.ApiExplorer; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; diff --git a/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs index 40f302b..09520fa 100644 --- a/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs +++ b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Asp.Versioning.ApiExplorer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index a7c1643..a0beff6 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -1,4 +1,5 @@ -using Cronos; +using Asp.Versioning; +using Cronos; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 9ee7f7d..e07868a 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; diff --git a/Endpoint/Controllers/V1/CampusController.cs b/Endpoint/Controllers/V1/CampusController.cs index a5dce32..c0f22f9 100644 --- a/Endpoint/Controllers/V1/CampusController.cs +++ b/Endpoint/Controllers/V1/CampusController.cs @@ -1,4 +1,5 @@ -using MediatR; +using Asp.Versioning; +using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Mirea.Api.DataAccess.Application.Cqrs.Campus.Queries.GetCampusBasicInfoList; diff --git a/Endpoint/Controllers/V1/DisciplineController.cs b/Endpoint/Controllers/V1/DisciplineController.cs index 7ab686a..d0528aa 100644 --- a/Endpoint/Controllers/V1/DisciplineController.cs +++ b/Endpoint/Controllers/V1/DisciplineController.cs @@ -1,4 +1,5 @@ -using MediatR; +using Asp.Versioning; +using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineDetails; diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index 7d71fd1..6e0ffda 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -1,4 +1,5 @@ -using MediatR; +using Asp.Versioning; +using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index fe6f079..d0daa28 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -1,4 +1,5 @@ -using MediatR; +using Asp.Versioning; +using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Mirea.Api.DataAccess.Application.Cqrs.Group.Queries.GetGroupDetails; diff --git a/Endpoint/Controllers/V1/LectureHallController.cs b/Endpoint/Controllers/V1/LectureHallController.cs index a5e62ba..d8fb077 100644 --- a/Endpoint/Controllers/V1/LectureHallController.cs +++ b/Endpoint/Controllers/V1/LectureHallController.cs @@ -1,4 +1,5 @@ -using MediatR; +using Asp.Versioning; +using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Mirea.Api.DataAccess.Application.Cqrs.LectureHall.Queries.GetLectureHallDetails; diff --git a/Endpoint/Controllers/V1/ProfessorController.cs b/Endpoint/Controllers/V1/ProfessorController.cs index 7e3d8b6..46b1794 100644 --- a/Endpoint/Controllers/V1/ProfessorController.cs +++ b/Endpoint/Controllers/V1/ProfessorController.cs @@ -1,4 +1,5 @@ -using MediatR; +using Asp.Versioning; +using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails; diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index c4a5941..109e61e 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -1,4 +1,5 @@ -using MediatR; +using Asp.Versioning; +using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 946a6fa..06b626c 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-rc0 - 1.0.2.0 - 1.0.2.0 + 1.0.0-rc1 + 1.0.2.1 + 1.0.2.1 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -22,22 +22,38 @@ + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + - + + + + + + diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 9c7f6dd..889c41c 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -5,17 +5,17 @@ disable enable Winsomnia - 1.0.0 - 1.0.3.0 - 1.0.3.0 + 1.0.1 + 1.0.3.1 + 1.0.3.1 Mirea.Api.DataAccess.Application $(AssemblyName) - - - + + + -- 2.43.0 From 076d6498a17b6d6cbce2538439fd5981969bce38 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Sep 2024 06:05:40 +0300 Subject: [PATCH 305/474] fix: set to correct produces --- Endpoint/Controllers/V1/ScheduleController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 109e61e..53b7a0d 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -23,6 +23,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot GetStartTerm() => config.Value.ScheduleSettings!.StartTerm; [CacheMaxAge(1, 0)] -- 2.43.0 From 412751e30f441a8b2e979055867d95d9adb2626b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 30 Sep 2024 01:10:37 +0300 Subject: [PATCH 306/474] build: update ref --- Endpoint/Endpoint.csproj | 10 +++++----- SqlData/Persistence/Persistence.csproj | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 06b626c..0e578fc 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -37,7 +37,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -45,15 +45,15 @@ - - + + - + - + diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 661c648..48bf5ba 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -15,7 +15,7 @@ - + -- 2.43.0 From c5ecf00932772c1285367d4f86ba26329ddccd9b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 7 Oct 2024 01:20:10 +0300 Subject: [PATCH 307/474] revert: remove produced --- Endpoint/Controllers/V1/ScheduleController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 53b7a0d..2167309 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -23,7 +23,6 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot GetStartTerm() => config.Value.ScheduleSettings!.StartTerm; [CacheMaxAge(1, 0)] @@ -66,7 +65,8 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot new ScheduleResponse { -- 2.43.0 From de5dc274d792804d692d6155ce9e018cffaa9f28 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 7 Oct 2024 01:42:00 +0300 Subject: [PATCH 308/474] build: add cors for debugging frontend --- Endpoint/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index dbf26e7..90d4844 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -64,6 +64,9 @@ public class Program policy.AllowAnyMethod(); policy.AllowAnyHeader(); policy.AllowCredentials(); +#if DEBUG + policy.WithOrigins("http://localhost:4200"); +#endif }); }); -- 2.43.0 From 1c981fb7bf06179ce650023a44388a3c255af37f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 7 Oct 2024 02:13:35 +0300 Subject: [PATCH 309/474] refactor: code restructuring --- Endpoint/Common/Services/PairPeriodTimeConverter.cs | 2 +- .../Core}/Middleware/CacheMaxAgeMiddleware.cs | 2 +- .../Core}/Middleware/CustomExceptionHandlerMiddleware.cs | 0 .../Core}/Middleware/JwtRevocationMiddleware.cs | 0 .../Core}/Middleware/MaintenanceModeMiddleware.cs | 0 .../Startup}/ApiVersioningConfiguration.cs | 2 +- .../Startup}/CacheConfiguration.cs | 6 +++--- .../Startup}/EnvironmentConfiguration.cs | 2 +- .../Startup}/JwtConfiguration.cs | 2 +- .../Startup}/LoggerConfiguration.cs | 4 ++-- .../Startup}/SecureConfiguration.cs | 6 +++--- .../Startup}/SwaggerConfiguration.cs | 2 +- .../Configuration/General/Interfaces/IIsConfigured.cs | 6 ------ .../{Common/Settings => Configuration}/ISaveSettings.cs | 2 +- .../{Common/Settings => Configuration/Model}/Admin.cs | 5 +++-- .../Settings => Configuration/Model}/GeneralConfig.cs | 7 ++++--- .../Attributes/RequiredSettingsAttribute.cs | 2 +- .../Configuration/Validation/Interfaces/IIsConfigured.cs | 6 ++++++ .../{General => Validation}/Settings/CacheSettings.cs | 6 +++--- .../{General => Validation}/Settings/DbSettings.cs | 6 +++--- .../{General => Validation}/Settings/EmailSettings.cs | 4 ++-- .../{General => Validation}/Settings/LogSettings.cs | 6 +++--- .../{General => Validation}/Settings/ScheduleSettings.cs | 6 +++--- .../{General => Validation}/SetupTokenService.cs | 2 +- .../Validators/SettingsRequiredValidator.cs | 8 ++++---- Endpoint/Controllers/Configuration/SetupController.cs | 7 +++---- Endpoint/Controllers/V1/AuthController.cs | 2 +- Endpoint/Controllers/V1/ScheduleController.cs | 2 +- Endpoint/Program.cs | 8 ++++---- 29 files changed, 57 insertions(+), 56 deletions(-) rename Endpoint/{ => Configuration/Core}/Middleware/CacheMaxAgeMiddleware.cs (98%) rename Endpoint/{ => Configuration/Core}/Middleware/CustomExceptionHandlerMiddleware.cs (100%) rename Endpoint/{ => Configuration/Core}/Middleware/JwtRevocationMiddleware.cs (100%) rename Endpoint/{ => Configuration/Core}/Middleware/MaintenanceModeMiddleware.cs (100%) rename Endpoint/Configuration/{ApplicationConfiguration => Core/Startup}/ApiVersioningConfiguration.cs (90%) rename Endpoint/Configuration/{ApplicationConfiguration => Core/Startup}/CacheConfiguration.cs (78%) rename Endpoint/Configuration/{ApplicationConfiguration => Core/Startup}/EnvironmentConfiguration.cs (97%) rename Endpoint/Configuration/{ApplicationConfiguration => Core/Startup}/JwtConfiguration.cs (97%) rename Endpoint/Configuration/{ApplicationConfiguration => Core/Startup}/LoggerConfiguration.cs (96%) rename Endpoint/Configuration/{ApplicationConfiguration => Core/Startup}/SecureConfiguration.cs (82%) rename Endpoint/Configuration/{ApplicationConfiguration => Core/Startup}/SwaggerConfiguration.cs (97%) delete mode 100644 Endpoint/Configuration/General/Interfaces/IIsConfigured.cs rename Endpoint/{Common/Settings => Configuration}/ISaveSettings.cs (53%) rename Endpoint/{Common/Settings => Configuration/Model}/Admin.cs (80%) rename Endpoint/{Common/Settings => Configuration/Model}/GeneralConfig.cs (79%) rename Endpoint/Configuration/{General => Validation}/Attributes/RequiredSettingsAttribute.cs (70%) create mode 100644 Endpoint/Configuration/Validation/Interfaces/IIsConfigured.cs rename Endpoint/Configuration/{General => Validation}/Settings/CacheSettings.cs (64%) rename Endpoint/Configuration/{General => Validation}/Settings/DbSettings.cs (79%) rename Endpoint/Configuration/{General => Validation}/Settings/EmailSettings.cs (79%) rename Endpoint/Configuration/{General => Validation}/Settings/LogSettings.cs (63%) rename Endpoint/Configuration/{General => Validation}/Settings/ScheduleSettings.cs (82%) rename Endpoint/Configuration/{General => Validation}/SetupTokenService.cs (90%) rename Endpoint/Configuration/{General => Validation}/Validators/SettingsRequiredValidator.cs (79%) diff --git a/Endpoint/Common/Services/PairPeriodTimeConverter.cs b/Endpoint/Common/Services/PairPeriodTimeConverter.cs index ad46295..d4efc40 100644 --- a/Endpoint/Common/Services/PairPeriodTimeConverter.cs +++ b/Endpoint/Common/Services/PairPeriodTimeConverter.cs @@ -1,4 +1,4 @@ -using Mirea.Api.Endpoint.Configuration.General.Settings; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; using System.Collections.Generic; using System.Linq; diff --git a/Endpoint/Middleware/CacheMaxAgeMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CacheMaxAgeMiddleware.cs similarity index 98% rename from Endpoint/Middleware/CacheMaxAgeMiddleware.cs rename to Endpoint/Configuration/Core/Middleware/CacheMaxAgeMiddleware.cs index 0a8f788..03b4c5a 100644 --- a/Endpoint/Middleware/CacheMaxAgeMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CacheMaxAgeMiddleware.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Mirea.Api.Endpoint.Common.Attributes; -using Mirea.Api.Endpoint.Common.Settings; +using Mirea.Api.Endpoint.Configuration.Model; using System; using System.Reflection; using System.Threading.Tasks; diff --git a/Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs similarity index 100% rename from Endpoint/Middleware/CustomExceptionHandlerMiddleware.cs rename to Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs diff --git a/Endpoint/Middleware/JwtRevocationMiddleware.cs b/Endpoint/Configuration/Core/Middleware/JwtRevocationMiddleware.cs similarity index 100% rename from Endpoint/Middleware/JwtRevocationMiddleware.cs rename to Endpoint/Configuration/Core/Middleware/JwtRevocationMiddleware.cs diff --git a/Endpoint/Middleware/MaintenanceModeMiddleware.cs b/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs similarity index 100% rename from Endpoint/Middleware/MaintenanceModeMiddleware.cs rename to Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs diff --git a/Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs b/Endpoint/Configuration/Core/Startup/ApiVersioningConfiguration.cs similarity index 90% rename from Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs rename to Endpoint/Configuration/Core/Startup/ApiVersioningConfiguration.cs index f92706e..151d5fb 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/ApiVersioningConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/ApiVersioningConfiguration.cs @@ -1,7 +1,7 @@ using Asp.Versioning; using Microsoft.Extensions.DependencyInjection; -namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; +namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class ApiVersioningConfiguration { diff --git a/Endpoint/Configuration/ApplicationConfiguration/CacheConfiguration.cs b/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs similarity index 78% rename from Endpoint/Configuration/ApplicationConfiguration/CacheConfiguration.cs rename to Endpoint/Configuration/Core/Startup/CacheConfiguration.cs index ccee805..8239398 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/CacheConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs @@ -1,9 +1,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Mirea.Api.Endpoint.Common.Settings; -using Mirea.Api.Endpoint.Configuration.General.Settings; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +using Mirea.Api.Endpoint.Configuration.Model; -namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; +namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class CacheConfiguration { diff --git a/Endpoint/Configuration/ApplicationConfiguration/EnvironmentConfiguration.cs b/Endpoint/Configuration/Core/Startup/EnvironmentConfiguration.cs similarity index 97% rename from Endpoint/Configuration/ApplicationConfiguration/EnvironmentConfiguration.cs rename to Endpoint/Configuration/Core/Startup/EnvironmentConfiguration.cs index 6ac90c6..2c10eb3 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/EnvironmentConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/EnvironmentConfiguration.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; +namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class EnvironmentConfiguration { diff --git a/Endpoint/Configuration/ApplicationConfiguration/JwtConfiguration.cs b/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs similarity index 97% rename from Endpoint/Configuration/ApplicationConfiguration/JwtConfiguration.cs rename to Endpoint/Configuration/Core/Startup/JwtConfiguration.cs index 307fa34..191a328 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/JwtConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs @@ -7,7 +7,7 @@ using Mirea.Api.Security.Common.Interfaces; using System; using System.Text; -namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; +namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class JwtConfiguration { diff --git a/Endpoint/Configuration/ApplicationConfiguration/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs similarity index 96% rename from Endpoint/Configuration/ApplicationConfiguration/LoggerConfiguration.cs rename to Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index 6fc9e3c..aece72b 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -2,14 +2,14 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Mirea.Api.Endpoint.Common.Services; -using Mirea.Api.Endpoint.Common.Settings; +using Mirea.Api.Endpoint.Configuration.Model; using Serilog; using Serilog.Events; using Serilog.Filters; using Serilog.Formatting.Compact; using System.IO; -namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; +namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class LoggerConfiguration { diff --git a/Endpoint/Configuration/ApplicationConfiguration/SecureConfiguration.cs b/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs similarity index 82% rename from Endpoint/Configuration/ApplicationConfiguration/SecureConfiguration.cs rename to Endpoint/Configuration/Core/Startup/SecureConfiguration.cs index 2b1f853..3452751 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/SecureConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs @@ -1,12 +1,12 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Mirea.Api.Endpoint.Common.Services.Security; -using Mirea.Api.Endpoint.Common.Settings; -using Mirea.Api.Endpoint.Configuration.General.Settings; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Security; using Mirea.Api.Security.Common.Interfaces; -namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; +namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class SecureConfiguration { diff --git a/Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs similarity index 97% rename from Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs rename to Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs index 74a0e7f..1fc5b91 100644 --- a/Endpoint/Configuration/ApplicationConfiguration/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs @@ -9,7 +9,7 @@ using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.IO; -namespace Mirea.Api.Endpoint.Configuration.ApplicationConfiguration; +namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class SwaggerConfiguration { diff --git a/Endpoint/Configuration/General/Interfaces/IIsConfigured.cs b/Endpoint/Configuration/General/Interfaces/IIsConfigured.cs deleted file mode 100644 index 60c09e0..0000000 --- a/Endpoint/Configuration/General/Interfaces/IIsConfigured.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Mirea.Api.Endpoint.Configuration.General.Interfaces; - -public interface IIsConfigured -{ - bool IsConfigured(); -} \ No newline at end of file diff --git a/Endpoint/Common/Settings/ISaveSettings.cs b/Endpoint/Configuration/ISaveSettings.cs similarity index 53% rename from Endpoint/Common/Settings/ISaveSettings.cs rename to Endpoint/Configuration/ISaveSettings.cs index 0b51bde..4b0fd92 100644 --- a/Endpoint/Common/Settings/ISaveSettings.cs +++ b/Endpoint/Configuration/ISaveSettings.cs @@ -1,4 +1,4 @@ -namespace Mirea.Api.Endpoint.Common.Settings; +namespace Mirea.Api.Endpoint.Common.Interfaces; public interface ISaveSettings { void SaveSetting(); diff --git a/Endpoint/Common/Settings/Admin.cs b/Endpoint/Configuration/Model/Admin.cs similarity index 80% rename from Endpoint/Common/Settings/Admin.cs rename to Endpoint/Configuration/Model/Admin.cs index 0e41c36..99f798f 100644 --- a/Endpoint/Common/Settings/Admin.cs +++ b/Endpoint/Configuration/Model/Admin.cs @@ -1,9 +1,10 @@ -using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Common.Interfaces; +using Mirea.Api.Endpoint.Common.Services; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; -namespace Mirea.Api.Endpoint.Common.Settings; +namespace Mirea.Api.Endpoint.Configuration.Model; public class Admin : ISaveSettings { diff --git a/Endpoint/Common/Settings/GeneralConfig.cs b/Endpoint/Configuration/Model/GeneralConfig.cs similarity index 79% rename from Endpoint/Common/Settings/GeneralConfig.cs rename to Endpoint/Configuration/Model/GeneralConfig.cs index dcf218e..5674169 100644 --- a/Endpoint/Common/Settings/GeneralConfig.cs +++ b/Endpoint/Configuration/Model/GeneralConfig.cs @@ -1,10 +1,11 @@ -using Mirea.Api.Endpoint.Common.Services; -using Mirea.Api.Endpoint.Configuration.General.Settings; +using Mirea.Api.Endpoint.Common.Interfaces; +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; -namespace Mirea.Api.Endpoint.Common.Settings; +namespace Mirea.Api.Endpoint.Configuration.Model; public class GeneralConfig : ISaveSettings { diff --git a/Endpoint/Configuration/General/Attributes/RequiredSettingsAttribute.cs b/Endpoint/Configuration/Validation/Attributes/RequiredSettingsAttribute.cs similarity index 70% rename from Endpoint/Configuration/General/Attributes/RequiredSettingsAttribute.cs rename to Endpoint/Configuration/Validation/Attributes/RequiredSettingsAttribute.cs index dcb13ac..000a4cb 100644 --- a/Endpoint/Configuration/General/Attributes/RequiredSettingsAttribute.cs +++ b/Endpoint/Configuration/Validation/Attributes/RequiredSettingsAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Mirea.Api.Endpoint.Configuration.General.Attributes; +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class RequiredSettingsAttribute : Attribute; diff --git a/Endpoint/Configuration/Validation/Interfaces/IIsConfigured.cs b/Endpoint/Configuration/Validation/Interfaces/IIsConfigured.cs new file mode 100644 index 0000000..1aa1ec9 --- /dev/null +++ b/Endpoint/Configuration/Validation/Interfaces/IIsConfigured.cs @@ -0,0 +1,6 @@ +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; + +public interface IIsConfigured +{ + bool IsConfigured(); +} \ No newline at end of file diff --git a/Endpoint/Configuration/General/Settings/CacheSettings.cs b/Endpoint/Configuration/Validation/Settings/CacheSettings.cs similarity index 64% rename from Endpoint/Configuration/General/Settings/CacheSettings.cs rename to Endpoint/Configuration/Validation/Settings/CacheSettings.cs index a0a7802..cf229ce 100644 --- a/Endpoint/Configuration/General/Settings/CacheSettings.cs +++ b/Endpoint/Configuration/Validation/Settings/CacheSettings.cs @@ -1,7 +1,7 @@ -using Mirea.Api.Endpoint.Configuration.General.Attributes; -using Mirea.Api.Endpoint.Configuration.General.Interfaces; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; -namespace Mirea.Api.Endpoint.Configuration.General.Settings; +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; [RequiredSettings] public class CacheSettings : IIsConfigured diff --git a/Endpoint/Configuration/General/Settings/DbSettings.cs b/Endpoint/Configuration/Validation/Settings/DbSettings.cs similarity index 79% rename from Endpoint/Configuration/General/Settings/DbSettings.cs rename to Endpoint/Configuration/Validation/Settings/DbSettings.cs index 09eace8..e251eb2 100644 --- a/Endpoint/Configuration/General/Settings/DbSettings.cs +++ b/Endpoint/Configuration/Validation/Settings/DbSettings.cs @@ -1,10 +1,10 @@ using Mirea.Api.DataAccess.Persistence.Common; -using Mirea.Api.Endpoint.Configuration.General.Attributes; -using Mirea.Api.Endpoint.Configuration.General.Interfaces; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; using System; using System.Text.Json.Serialization; -namespace Mirea.Api.Endpoint.Configuration.General.Settings; +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; [RequiredSettings] public class DbSettings : IIsConfigured diff --git a/Endpoint/Configuration/General/Settings/EmailSettings.cs b/Endpoint/Configuration/Validation/Settings/EmailSettings.cs similarity index 79% rename from Endpoint/Configuration/General/Settings/EmailSettings.cs rename to Endpoint/Configuration/Validation/Settings/EmailSettings.cs index bdd5179..9729e8e 100644 --- a/Endpoint/Configuration/General/Settings/EmailSettings.cs +++ b/Endpoint/Configuration/Validation/Settings/EmailSettings.cs @@ -1,6 +1,6 @@ -using Mirea.Api.Endpoint.Configuration.General.Interfaces; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; -namespace Mirea.Api.Endpoint.Configuration.General.Settings; +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; public class EmailSettings : IIsConfigured { diff --git a/Endpoint/Configuration/General/Settings/LogSettings.cs b/Endpoint/Configuration/Validation/Settings/LogSettings.cs similarity index 63% rename from Endpoint/Configuration/General/Settings/LogSettings.cs rename to Endpoint/Configuration/Validation/Settings/LogSettings.cs index 4a42d28..9c00d93 100644 --- a/Endpoint/Configuration/General/Settings/LogSettings.cs +++ b/Endpoint/Configuration/Validation/Settings/LogSettings.cs @@ -1,7 +1,7 @@ -using Mirea.Api.Endpoint.Configuration.General.Attributes; -using Mirea.Api.Endpoint.Configuration.General.Interfaces; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; -namespace Mirea.Api.Endpoint.Configuration.General.Settings; +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; [RequiredSettings] public class LogSettings : IIsConfigured diff --git a/Endpoint/Configuration/General/Settings/ScheduleSettings.cs b/Endpoint/Configuration/Validation/Settings/ScheduleSettings.cs similarity index 82% rename from Endpoint/Configuration/General/Settings/ScheduleSettings.cs rename to Endpoint/Configuration/Validation/Settings/ScheduleSettings.cs index a45b41f..936c4da 100644 --- a/Endpoint/Configuration/General/Settings/ScheduleSettings.cs +++ b/Endpoint/Configuration/Validation/Settings/ScheduleSettings.cs @@ -1,10 +1,10 @@ -using Mirea.Api.Endpoint.Configuration.General.Attributes; -using Mirea.Api.Endpoint.Configuration.General.Interfaces; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; using System; using System.Collections.Generic; using System.Linq; -namespace Mirea.Api.Endpoint.Configuration.General.Settings; +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; [RequiredSettings] public class ScheduleSettings : IIsConfigured diff --git a/Endpoint/Configuration/General/SetupTokenService.cs b/Endpoint/Configuration/Validation/SetupTokenService.cs similarity index 90% rename from Endpoint/Configuration/General/SetupTokenService.cs rename to Endpoint/Configuration/Validation/SetupTokenService.cs index 4cc1216..58b1ad1 100644 --- a/Endpoint/Configuration/General/SetupTokenService.cs +++ b/Endpoint/Configuration/Validation/SetupTokenService.cs @@ -1,7 +1,7 @@ using Mirea.Api.Endpoint.Common.Interfaces; using System; -namespace Mirea.Api.Endpoint.Configuration.General; +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks; public class SetupTokenService : ISetupToken { diff --git a/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs b/Endpoint/Configuration/Validation/Validators/SettingsRequiredValidator.cs similarity index 79% rename from Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs rename to Endpoint/Configuration/Validation/Validators/SettingsRequiredValidator.cs index 323ea7c..8ab315f 100644 --- a/Endpoint/Configuration/General/Validators/SettingsRequiredValidator.cs +++ b/Endpoint/Configuration/Validation/Validators/SettingsRequiredValidator.cs @@ -1,11 +1,11 @@ using Microsoft.Extensions.Options; -using Mirea.Api.Endpoint.Common.Settings; -using Mirea.Api.Endpoint.Configuration.General.Attributes; -using Mirea.Api.Endpoint.Configuration.General.Interfaces; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; +using Mirea.Api.Endpoint.Configuration.Model; using System; using System.Reflection; -namespace Mirea.Api.Endpoint.Configuration.General.Validators; +namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Validators; public class SettingsRequiredValidator { diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index a0beff6..68fbd3d 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -10,9 +10,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.Common.Settings; -using Mirea.Api.Endpoint.Configuration.General.Settings; -using Mirea.Api.Endpoint.Configuration.General.Validators; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Validators; +using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Security.Services; using MySqlConnector; using Npgsql; @@ -29,7 +29,6 @@ using System.Security.Cryptography; namespace Mirea.Api.Endpoint.Controllers.Configuration; [ApiVersion("1.0")] -[ApiController] [MaintenanceModeIgnore] [ApiExplorerSettings(IgnoreApi = true)] public class SetupController( diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index e07868a..81f0fdb 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -10,7 +10,7 @@ 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.Common.Settings; +using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Security.Common.Dto.Requests; using Mirea.Api.Security.Services; using System; diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 2167309..bcac4d2 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -9,7 +9,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.Common.Settings; +using Mirea.Api.Endpoint.Configuration.Model; using System; using System.Collections.Generic; using System.Linq; diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 90d4844..f038d3c 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -10,10 +10,10 @@ 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.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.Configuration.ConfigurationChecks; +using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Validators; +using Mirea.Api.Endpoint.Configuration.Core.Startup; +using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Endpoint.Middleware; using Mirea.Api.Security.Services; using System; -- 2.43.0 From 2b89dd07a9e2af012e87665957ed4af57b6cf42f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 7 Oct 2024 02:16:20 +0300 Subject: [PATCH 310/474] feat: output a token when generating a token for the header --- Endpoint/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index f038d3c..74adcd0 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -84,6 +84,7 @@ 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; -- 2.43.0 From 26dbf608b99821e3f8d98aefba7a14f52cb86887 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 7 Oct 2024 02:25:36 +0300 Subject: [PATCH 311/474] refactor: sync namespace --- Endpoint/Common/Services/PairPeriodTimeConverter.cs | 2 +- .../Configuration/Core/Middleware/CacheMaxAgeMiddleware.cs | 2 +- .../Core/Middleware/CustomExceptionHandlerMiddleware.cs | 2 +- .../Core/Middleware/JwtRevocationMiddleware.cs | 2 +- .../Core/Middleware/MaintenanceModeMiddleware.cs | 2 +- Endpoint/Configuration/Core/Startup/CacheConfiguration.cs | 2 +- Endpoint/Configuration/Core/Startup/SecureConfiguration.cs | 2 +- Endpoint/Configuration/ISaveSettings.cs | 2 +- Endpoint/Configuration/Model/Admin.cs | 3 +-- Endpoint/Configuration/Model/GeneralConfig.cs | 5 ++--- .../Settings => Model/GeneralSettings}/CacheSettings.cs | 6 +++--- .../Settings => Model/GeneralSettings}/DbSettings.cs | 6 +++--- .../Settings => Model/GeneralSettings}/EmailSettings.cs | 4 ++-- .../Settings => Model/GeneralSettings}/LogSettings.cs | 6 +++--- .../Settings => Model/GeneralSettings}/ScheduleSettings.cs | 6 +++--- .../Validation/Attributes/RequiredSettingsAttribute.cs | 2 +- .../Configuration/Validation/Interfaces/IIsConfigured.cs | 2 +- Endpoint/Configuration/Validation/SetupTokenService.cs | 2 +- .../Validation/Validators/SettingsRequiredValidator.cs | 6 +++--- Endpoint/Controllers/Configuration/SetupController.cs | 4 ++-- Endpoint/Program.cs | 6 +++--- 21 files changed, 36 insertions(+), 38 deletions(-) rename Endpoint/Configuration/{Validation/Settings => Model/GeneralSettings}/CacheSettings.cs (64%) rename Endpoint/Configuration/{Validation/Settings => Model/GeneralSettings}/DbSettings.cs (79%) rename Endpoint/Configuration/{Validation/Settings => Model/GeneralSettings}/EmailSettings.cs (79%) rename Endpoint/Configuration/{Validation/Settings => Model/GeneralSettings}/LogSettings.cs (63%) rename Endpoint/Configuration/{Validation/Settings => Model/GeneralSettings}/ScheduleSettings.cs (82%) diff --git a/Endpoint/Common/Services/PairPeriodTimeConverter.cs b/Endpoint/Common/Services/PairPeriodTimeConverter.cs index d4efc40..5a4db03 100644 --- a/Endpoint/Common/Services/PairPeriodTimeConverter.cs +++ b/Endpoint/Common/Services/PairPeriodTimeConverter.cs @@ -1,4 +1,4 @@ -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; using System.Collections.Generic; using System.Linq; diff --git a/Endpoint/Configuration/Core/Middleware/CacheMaxAgeMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CacheMaxAgeMiddleware.cs index 03b4c5a..ee68ee0 100644 --- a/Endpoint/Configuration/Core/Middleware/CacheMaxAgeMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CacheMaxAgeMiddleware.cs @@ -8,7 +8,7 @@ using System; using System.Reflection; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Middleware; +namespace Mirea.Api.Endpoint.Configuration.Core.Middleware; public class CacheMaxAgeMiddleware(RequestDelegate next, IServiceProvider serviceProvider) { diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index 5800f7d..337e5dc 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -7,7 +7,7 @@ using System; using System.Text.Json; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Middleware; +namespace Mirea.Api.Endpoint.Configuration.Core.Middleware; public class CustomExceptionHandlerMiddleware(RequestDelegate next) { diff --git a/Endpoint/Configuration/Core/Middleware/JwtRevocationMiddleware.cs b/Endpoint/Configuration/Core/Middleware/JwtRevocationMiddleware.cs index 5d657f8..9cf244a 100644 --- a/Endpoint/Configuration/Core/Middleware/JwtRevocationMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/JwtRevocationMiddleware.cs @@ -2,7 +2,7 @@ using Mirea.Api.Security.Common.Interfaces; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Middleware; +namespace Mirea.Api.Endpoint.Configuration.Core.Middleware; public class JwtRevocationMiddleware(RequestDelegate next) { diff --git a/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs b/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs index 128ed7c..2687eb9 100644 --- a/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs @@ -3,7 +3,7 @@ using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Interfaces; using System.Threading.Tasks; -namespace Mirea.Api.Endpoint.Middleware; +namespace Mirea.Api.Endpoint.Configuration.Core.Middleware; public class MaintenanceModeMiddleware(RequestDelegate next, IMaintenanceModeService maintenanceModeService, IMaintenanceModeNotConfigureService maintenanceModeNotConfigureService) { diff --git a/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs b/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs index 8239398..504a655 100644 --- a/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; namespace Mirea.Api.Endpoint.Configuration.Core.Startup; diff --git a/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs b/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs index 3452751..f9f7a81 100644 --- a/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Mirea.Api.Endpoint.Common.Services.Security; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; using Mirea.Api.Security; using Mirea.Api.Security.Common.Interfaces; diff --git a/Endpoint/Configuration/ISaveSettings.cs b/Endpoint/Configuration/ISaveSettings.cs index 4b0fd92..860cf5a 100644 --- a/Endpoint/Configuration/ISaveSettings.cs +++ b/Endpoint/Configuration/ISaveSettings.cs @@ -1,4 +1,4 @@ -namespace Mirea.Api.Endpoint.Common.Interfaces; +namespace Mirea.Api.Endpoint.Configuration; public interface ISaveSettings { void SaveSetting(); diff --git a/Endpoint/Configuration/Model/Admin.cs b/Endpoint/Configuration/Model/Admin.cs index 99f798f..73db8d7 100644 --- a/Endpoint/Configuration/Model/Admin.cs +++ b/Endpoint/Configuration/Model/Admin.cs @@ -1,5 +1,4 @@ -using Mirea.Api.Endpoint.Common.Interfaces; -using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Common.Services; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Endpoint/Configuration/Model/GeneralConfig.cs b/Endpoint/Configuration/Model/GeneralConfig.cs index 5674169..454ee90 100644 --- a/Endpoint/Configuration/Model/GeneralConfig.cs +++ b/Endpoint/Configuration/Model/GeneralConfig.cs @@ -1,6 +1,5 @@ -using Mirea.Api.Endpoint.Common.Interfaces; -using Mirea.Api.Endpoint.Common.Services; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Endpoint/Configuration/Validation/Settings/CacheSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/CacheSettings.cs similarity index 64% rename from Endpoint/Configuration/Validation/Settings/CacheSettings.cs rename to Endpoint/Configuration/Model/GeneralSettings/CacheSettings.cs index cf229ce..c5e5dc9 100644 --- a/Endpoint/Configuration/Validation/Settings/CacheSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/CacheSettings.cs @@ -1,7 +1,7 @@ -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; +using Mirea.Api.Endpoint.Configuration.Validation.Attributes; +using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; [RequiredSettings] public class CacheSettings : IIsConfigured diff --git a/Endpoint/Configuration/Validation/Settings/DbSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/DbSettings.cs similarity index 79% rename from Endpoint/Configuration/Validation/Settings/DbSettings.cs rename to Endpoint/Configuration/Model/GeneralSettings/DbSettings.cs index e251eb2..28921e9 100644 --- a/Endpoint/Configuration/Validation/Settings/DbSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/DbSettings.cs @@ -1,10 +1,10 @@ using Mirea.Api.DataAccess.Persistence.Common; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; +using Mirea.Api.Endpoint.Configuration.Validation.Attributes; +using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; using System; using System.Text.Json.Serialization; -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; [RequiredSettings] public class DbSettings : IIsConfigured diff --git a/Endpoint/Configuration/Validation/Settings/EmailSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/EmailSettings.cs similarity index 79% rename from Endpoint/Configuration/Validation/Settings/EmailSettings.cs rename to Endpoint/Configuration/Model/GeneralSettings/EmailSettings.cs index 9729e8e..b572202 100644 --- a/Endpoint/Configuration/Validation/Settings/EmailSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/EmailSettings.cs @@ -1,6 +1,6 @@ -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; +using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; public class EmailSettings : IIsConfigured { diff --git a/Endpoint/Configuration/Validation/Settings/LogSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/LogSettings.cs similarity index 63% rename from Endpoint/Configuration/Validation/Settings/LogSettings.cs rename to Endpoint/Configuration/Model/GeneralSettings/LogSettings.cs index 9c00d93..3f8deb1 100644 --- a/Endpoint/Configuration/Validation/Settings/LogSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/LogSettings.cs @@ -1,7 +1,7 @@ -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; +using Mirea.Api.Endpoint.Configuration.Validation.Attributes; +using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; [RequiredSettings] public class LogSettings : IIsConfigured diff --git a/Endpoint/Configuration/Validation/Settings/ScheduleSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs similarity index 82% rename from Endpoint/Configuration/Validation/Settings/ScheduleSettings.cs rename to Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs index 936c4da..ac1e6d4 100644 --- a/Endpoint/Configuration/Validation/Settings/ScheduleSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs @@ -1,10 +1,10 @@ -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; +using Mirea.Api.Endpoint.Configuration.Validation.Attributes; +using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; using System; using System.Collections.Generic; using System.Linq; -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Settings; +namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; [RequiredSettings] public class ScheduleSettings : IIsConfigured diff --git a/Endpoint/Configuration/Validation/Attributes/RequiredSettingsAttribute.cs b/Endpoint/Configuration/Validation/Attributes/RequiredSettingsAttribute.cs index 000a4cb..0cf246f 100644 --- a/Endpoint/Configuration/Validation/Attributes/RequiredSettingsAttribute.cs +++ b/Endpoint/Configuration/Validation/Attributes/RequiredSettingsAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; +namespace Mirea.Api.Endpoint.Configuration.Validation.Attributes; [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class RequiredSettingsAttribute : Attribute; diff --git a/Endpoint/Configuration/Validation/Interfaces/IIsConfigured.cs b/Endpoint/Configuration/Validation/Interfaces/IIsConfigured.cs index 1aa1ec9..3c6fabb 100644 --- a/Endpoint/Configuration/Validation/Interfaces/IIsConfigured.cs +++ b/Endpoint/Configuration/Validation/Interfaces/IIsConfigured.cs @@ -1,4 +1,4 @@ -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; +namespace Mirea.Api.Endpoint.Configuration.Validation.Interfaces; public interface IIsConfigured { diff --git a/Endpoint/Configuration/Validation/SetupTokenService.cs b/Endpoint/Configuration/Validation/SetupTokenService.cs index 58b1ad1..5ef3559 100644 --- a/Endpoint/Configuration/Validation/SetupTokenService.cs +++ b/Endpoint/Configuration/Validation/SetupTokenService.cs @@ -1,7 +1,7 @@ using Mirea.Api.Endpoint.Common.Interfaces; using System; -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks; +namespace Mirea.Api.Endpoint.Configuration.Validation; public class SetupTokenService : ISetupToken { diff --git a/Endpoint/Configuration/Validation/Validators/SettingsRequiredValidator.cs b/Endpoint/Configuration/Validation/Validators/SettingsRequiredValidator.cs index 8ab315f..1e0ec77 100644 --- a/Endpoint/Configuration/Validation/Validators/SettingsRequiredValidator.cs +++ b/Endpoint/Configuration/Validation/Validators/SettingsRequiredValidator.cs @@ -1,11 +1,11 @@ using Microsoft.Extensions.Options; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Attributes; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Interfaces; using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Configuration.Validation.Attributes; +using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; using System; using System.Reflection; -namespace Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Validators; +namespace Mirea.Api.Endpoint.Configuration.Validation.Validators; public class SettingsRequiredValidator { diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 68fbd3d..e306092 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -10,9 +10,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.ConfigurationChecks.Settings; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Validators; using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; +using Mirea.Api.Endpoint.Configuration.Validation.Validators; using Mirea.Api.Security.Services; using MySqlConnector; using Npgsql; diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 74adcd0..7843cd3 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -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.ConfigurationChecks; -using Mirea.Api.Endpoint.Configuration.ConfigurationChecks.Validators; +using Mirea.Api.Endpoint.Configuration.Core.Middleware; using Mirea.Api.Endpoint.Configuration.Core.Startup; using Mirea.Api.Endpoint.Configuration.Model; -using Mirea.Api.Endpoint.Middleware; +using Mirea.Api.Endpoint.Configuration.Validation; +using Mirea.Api.Endpoint.Configuration.Validation.Validators; using Mirea.Api.Security.Services; using System; using System.IO; -- 2.43.0 From f5739647b2e1bef4a2e0c9f2acf52805155f1ee0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 7 Oct 2024 02:45:38 +0300 Subject: [PATCH 312/474] refactor: remove default produce 200 code --- Endpoint/Controllers/V1/CampusController.cs | 3 --- Endpoint/Controllers/V1/DisciplineController.cs | 3 --- Endpoint/Controllers/V1/FacultyController.cs | 3 --- Endpoint/Controllers/V1/GroupController.cs | 4 ---- Endpoint/Controllers/V1/LectureHallController.cs | 4 ---- Endpoint/Controllers/V1/ProfessorController.cs | 3 --- Endpoint/Controllers/V1/ScheduleController.cs | 6 ------ 7 files changed, 26 deletions(-) diff --git a/Endpoint/Controllers/V1/CampusController.cs b/Endpoint/Controllers/V1/CampusController.cs index c0f22f9..ddfcf5f 100644 --- a/Endpoint/Controllers/V1/CampusController.cs +++ b/Endpoint/Controllers/V1/CampusController.cs @@ -1,6 +1,5 @@ using Asp.Versioning; 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; @@ -21,7 +20,6 @@ public class CampusController(IMediator mediator) : BaseController /// /// Basic information about campuses. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> Get() { var result = await mediator.Send(new GetCampusBasicInfoListQuery()); @@ -42,7 +40,6 @@ public class CampusController(IMediator mediator) : BaseController /// Campus ID. /// Details of the specified campus. [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] public async Task> GetDetails(int id) diff --git a/Endpoint/Controllers/V1/DisciplineController.cs b/Endpoint/Controllers/V1/DisciplineController.cs index d0528aa..e02b94d 100644 --- a/Endpoint/Controllers/V1/DisciplineController.cs +++ b/Endpoint/Controllers/V1/DisciplineController.cs @@ -1,6 +1,5 @@ using Asp.Versioning; 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; @@ -23,7 +22,6 @@ public class DisciplineController(IMediator mediator) : BaseController /// Number of items per page. /// Paginated list of disciplines. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) { @@ -48,7 +46,6 @@ public class DisciplineController(IMediator mediator) : BaseController /// Discipline ID. /// Details of the specified discipline. [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] public async Task> GetDetails(int id) diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index 6e0ffda..56c88ad 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -1,6 +1,5 @@ using Asp.Versioning; 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; @@ -23,7 +22,6 @@ public class FacultyController(IMediator mediator) : BaseController /// Number of items per page. /// Paginated list of faculties. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) { @@ -49,7 +47,6 @@ public class FacultyController(IMediator mediator) : BaseController /// Faculty ID. /// Details of the specified faculty. [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] public async Task> GetDetails(int id) diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index d0daa28..ebac4cb 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -1,6 +1,5 @@ using Asp.Versioning; 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; @@ -37,7 +36,6 @@ public class GroupController(IMediator mediator) : BaseController /// The page size for pagination (optional). /// A list of groups. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) { @@ -64,7 +62,6 @@ public class GroupController(IMediator mediator) : BaseController /// The ID of the group to retrieve. /// Detailed information about the group. [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] public async Task> GetDetails(int id) @@ -90,7 +87,6 @@ public class GroupController(IMediator mediator) : BaseController /// The ID of the faculty. /// A list of groups belonging to the specified faculty. [HttpGet("GetByFaculty/{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] public async Task>> GetByFaculty(int id) diff --git a/Endpoint/Controllers/V1/LectureHallController.cs b/Endpoint/Controllers/V1/LectureHallController.cs index d8fb077..d648128 100644 --- a/Endpoint/Controllers/V1/LectureHallController.cs +++ b/Endpoint/Controllers/V1/LectureHallController.cs @@ -1,6 +1,5 @@ using Asp.Versioning; 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; @@ -21,7 +20,6 @@ public class LectureHallController(IMediator mediator) : BaseController /// /// A list of lecture halls. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> Get() { var result = await mediator.Send(new GetLectureHallListQuery()); @@ -42,7 +40,6 @@ public class LectureHallController(IMediator mediator) : BaseController /// The ID of the lecture hall to retrieve. /// The details of the specified lecture hall. [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] public async Task> GetDetails(int id) @@ -68,7 +65,6 @@ public class LectureHallController(IMediator mediator) : BaseController /// The ID of the campus. /// A list of lecture halls in the specified campus. [HttpGet("GetByCampus/{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] public async Task>> GetByCampus(int id) diff --git a/Endpoint/Controllers/V1/ProfessorController.cs b/Endpoint/Controllers/V1/ProfessorController.cs index 46b1794..8b1a58d 100644 --- a/Endpoint/Controllers/V1/ProfessorController.cs +++ b/Endpoint/Controllers/V1/ProfessorController.cs @@ -1,6 +1,5 @@ using Asp.Versioning; 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; @@ -23,7 +22,6 @@ public class ProfessorController(IMediator mediator) : BaseController /// The page size for pagination (optional). /// A list of professors. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) { @@ -49,7 +47,6 @@ public class ProfessorController(IMediator mediator) : BaseController /// The ID of the professor to retrieve. /// Detailed information about the professor. [HttpGet("{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [BadRequestResponse] [NotFoundResponse] public async Task> GetDetails(int id) diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index bcac4d2..a3fec0a 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -35,10 +35,8 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshotThe request object containing filter criteria. /// A list of schedules matching the filter criteria. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] - [NotFoundResponse] public async Task>> Get([FromBody] ScheduleRequest request) { if ((request.Groups == null || request.Groups.Length == 0) && @@ -100,7 +98,6 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshotAn array of lecture hall IDs. /// A response containing schedules for the specified group. [HttpGet("GetByGroup/{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] [NotFoundResponse] @@ -128,7 +125,6 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshotAn array of lecture hall IDs. /// A response containing schedules for the specified professor. [HttpGet("GetByProfessor/{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] [NotFoundResponse] @@ -156,7 +152,6 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshotAn array of group IDs. /// A response containing schedules for the specified lecture hall. [HttpGet("GetByLectureHall/{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] [NotFoundResponse] @@ -184,7 +179,6 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshotAn array of lecture hall IDs. /// A response containing schedules for the specified discipline. [HttpGet("GetByDiscipline/{id:int}")] - [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status204NoContent)] [BadRequestResponse] [NotFoundResponse] -- 2.43.0 From c9ebddf27a389e31bc05a68331a9c2cade9fdb47 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 9 Oct 2024 02:58:09 +0300 Subject: [PATCH 313/474] fix: remove cache for method --- Endpoint/Controllers/V1/AuthController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 81f0fdb..a7392a2 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -165,7 +165,6 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [HttpGet("GetRole")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize] - [CacheMaxAge(0, 0, 1)] public ActionResult GetRole() => Ok(AuthRoles.Admin); [HttpPost("RenewPassword")] -- 2.43.0 From b49df925d4f7a3463c148f51bc04bd5665a4925f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 9 Oct 2024 02:58:52 +0300 Subject: [PATCH 314/474] fix: doesn't set expire time for fingerpring --- Endpoint/Controllers/V1/AuthController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index a7392a2..6081656 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -46,13 +46,13 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass private void SetRefreshToken(string value, DateTimeOffset? expires = null) { SetCookie("refresh_token", value, expires); - SetCookie("user_key", Fingerprint, expires); + SetCookie("user_key", Fingerprint); } private void SetFirstToken(string value, DateTimeOffset? expires = null) { SetCookie("authentication_token", value, expires); - SetCookie("user_key", Fingerprint, expires); + SetCookie("user_key", Fingerprint); } [ApiExplorerSettings(IgnoreApi = true)] -- 2.43.0 From 1f3aaca3cfea98d089de317d12a4dc6b0b1c2826 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 9 Oct 2024 03:00:26 +0300 Subject: [PATCH 315/474] sec: move token from responce to cookie --- ApiDto/Responses/TokenResponse.cs | 23 ----------- .../CookieAuthorizationMiddleware.cs | 17 +++++++++ Endpoint/Controllers/V1/AuthController.cs | 38 ++++++++++--------- Endpoint/Program.cs | 10 +++-- 4 files changed, 43 insertions(+), 45 deletions(-) delete mode 100644 ApiDto/Responses/TokenResponse.cs create mode 100644 Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs diff --git a/ApiDto/Responses/TokenResponse.cs b/ApiDto/Responses/TokenResponse.cs deleted file mode 100644 index 9761b87..0000000 --- a/ApiDto/Responses/TokenResponse.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Mirea.Api.Dto.Responses; - -/// -/// Provides a JWT and RT token. -/// -public class TokenResponse -{ - /// - /// A JWT token for accessing protected resources. - /// - [Required] - public required string AccessToken { get; set; } - - /// - /// The date and time when the JWT token expires. - /// - /// After this date, a new JWT token must be requested. - [Required] - public required DateTime ExpiresIn { get; set; } -} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs new file mode 100644 index 0000000..c3e0cf5 --- /dev/null +++ b/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Http; +using Mirea.Api.Security.Common.Interfaces; +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, IRevokedToken revokedTokenStore) + { + if (context.Request.Cookies.ContainsKey(JwtAuthorizationName)) + context.Request.Headers.Authorization = "Bearer " + context.Request.Cookies[JwtAuthorizationName]; + + await next(context); + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 6081656..2954985 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -6,10 +6,10 @@ 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.Security.Common.Dto.Requests; using Mirea.Api.Security.Services; @@ -55,6 +55,12 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass SetCookie("user_key", Fingerprint); } + private void SetAuthToken(string value, DateTimeOffset? expires = null) + { + SetCookie(CookieAuthorizationMiddleware.JwtAuthorizationName, value, expires); + SetCookie("user_key", Fingerprint); + } + [ApiExplorerSettings(IgnoreApi = true)] public void OnActionExecuting(ActionExecutingContext context) { @@ -76,17 +82,17 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass /// then generating and returning an authentication token if successful. /// /// The login request containing the username/email and password. - /// A TokenResponse containing the access token and its expiry if successful, otherwise an Unauthorized response. + /// User's AuthRoles. [HttpPost("Login")] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task> Login([FromBody] LoginRequest request) + [BadRequestResponse] + public async Task> 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 Unauthorized("Invalid username/email or password"); + return BadRequest("Invalid username/email or password"); var token = await auth.GenerateAuthTokensAsync(new TokenRequest { @@ -96,21 +102,19 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass }, "1"); SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); + SetAuthToken(token.AccessToken, token.AccessExpiresIn); - return Ok(new TokenResponse - { - AccessToken = token.AccessToken, - ExpiresIn = token.AccessExpiresIn - }); + return Ok(AuthRoles.Admin); } /// /// Refreshes the authentication token using the existing refresh token. /// - /// A TokenResponse containing the new access token and its expiry if successful, otherwise an Unauthorized response. + /// User's AuthRoles. [HttpGet("ReLogin")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task> ReLogin() + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> ReLogin() { if (string.IsNullOrEmpty(RefreshToken)) return Unauthorized(); @@ -128,16 +132,13 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass ); SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); + SetAuthToken(token.AccessToken, token.AccessExpiresIn); - return Ok(new TokenResponse - { - AccessToken = token.AccessToken, - ExpiresIn = token.AccessExpiresIn - }); + return Ok(AuthRoles.Admin); } catch (SecurityException) { - return Unauthorized(); + return Forbid(); } } @@ -152,6 +153,7 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass { SetRefreshToken("", DateTimeOffset.MinValue); SetFirstToken("", DateTimeOffset.MinValue); + SetAuthToken("", DateTimeOffset.MinValue); await auth.LogoutAsync(Fingerprint); diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 7843cd3..0b39b2a 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -128,16 +128,18 @@ public class Program } app.UseCustomSwagger(app.Services); + app.UseHttpsRedirection(); app.UseMiddleware(); app.UseMiddleware(); + app.UseMiddleware(); + + app.UseAuthentication(); + app.UseAuthorization(); + app.UseMiddleware(); app.UseMiddleware(); - app.UseHttpsRedirection(); - - app.UseAuthorization(); - app.MapControllers(); app.Run(); -- 2.43.0 From 71e8eca5f4b98b7b9d92c92cecf85f88885d8841 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 9 Oct 2024 03:02:35 +0300 Subject: [PATCH 316/474] refactor: remove unused param --- .../Core/Middleware/CookieAuthorizationMiddleware.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs index c3e0cf5..1b97741 100644 --- a/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Http; -using Mirea.Api.Security.Common.Interfaces; using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Configuration.Core.Middleware; @@ -7,7 +6,7 @@ namespace Mirea.Api.Endpoint.Configuration.Core.Middleware; public class CookieAuthorizationMiddleware(RequestDelegate next) { public const string JwtAuthorizationName = "_ajwt"; - public async Task InvokeAsync(HttpContext context, IRevokedToken revokedTokenStore) + public async Task InvokeAsync(HttpContext context) { if (context.Request.Cookies.ContainsKey(JwtAuthorizationName)) context.Request.Headers.Authorization = "Bearer " + context.Request.Cookies[JwtAuthorizationName]; -- 2.43.0 From 2e48b0067f0dec2d1f7afbbf2e83596131ba0c7b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 9 Oct 2024 03:08:15 +0300 Subject: [PATCH 317/474] build: update ref --- Endpoint/Endpoint.csproj | 26 +++++++++++++------------- Security/Security.csproj | 2 +- SqlData/Application/Application.csproj | 2 +- SqlData/Persistence/Persistence.csproj | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 0e578fc..1bb1199 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-rc1 - 1.0.2.1 - 1.0.2.1 + 1.0.0-rc2 + 1.0.2.2 + 1.0.2.2 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -25,35 +25,35 @@ - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - - + + - + - + diff --git a/Security/Security.csproj b/Security/Security.csproj index 5287b7a..f8dcc16 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -15,7 +15,7 @@ - + diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 889c41c..822bf16 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -16,7 +16,7 @@ - + diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 48bf5ba..1342939 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -13,8 +13,8 @@ - - + + -- 2.43.0 From 4970dd782ad67c3a5235ec0a86cb57c41fbf3246 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 01:54:56 +0300 Subject: [PATCH 318/474] build: update ref --- Endpoint/Endpoint.csproj | 17 +++++++++-------- SqlData/Persistence/Persistence.csproj | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 1bb1199..c61dd9e 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0-rc2 - 1.0.2.2 - 1.0.2.2 + 1.0-rc3 + 1.0.2.3 + 1.0.2.3 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -25,7 +25,7 @@ - + @@ -39,21 +39,22 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + + - + diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 1342939..5cb3dde 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -15,7 +15,7 @@ - + -- 2.43.0 From 428c2dc3ba5dd1e50fe010fe9b72bb82a3f133f3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 02:22:42 +0300 Subject: [PATCH 319/474] refactor: return the modified interfaces for further modification --- .../Core/Startup/ApiVersioningConfiguration.cs | 4 ++-- Endpoint/Configuration/Core/Startup/JwtConfiguration.cs | 9 ++++----- .../Configuration/Core/Startup/LoggerConfiguration.cs | 8 ++------ .../Configuration/Core/Startup/SwaggerConfiguration.cs | 8 ++------ 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Endpoint/Configuration/Core/Startup/ApiVersioningConfiguration.cs b/Endpoint/Configuration/Core/Startup/ApiVersioningConfiguration.cs index 151d5fb..1f60624 100644 --- a/Endpoint/Configuration/Core/Startup/ApiVersioningConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/ApiVersioningConfiguration.cs @@ -5,9 +5,9 @@ namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class ApiVersioningConfiguration { - public static void AddCustomApiVersioning(this IServiceCollection services) + public static IApiVersioningBuilder AddCustomApiVersioning(this IServiceCollection services) { - services.AddApiVersioning(options => + return services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; diff --git a/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs b/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs index 191a328..c6009d0 100644 --- a/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; @@ -11,7 +12,7 @@ namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class JwtConfiguration { - public static IServiceCollection AddJwtToken(this IServiceCollection services, IConfiguration configuration) + public static AuthenticationBuilder AddJwtToken(this IServiceCollection services, IConfiguration configuration) { var lifeTimeJwt = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_JWT"]!)); @@ -40,7 +41,7 @@ public static class JwtConfiguration SigningKey = jwtKey }); - services.AddAuthentication(options => + return services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; @@ -60,7 +61,5 @@ public static class JwtConfiguration TokenDecryptionKey = new SymmetricSecurityKey(jwtDecrypt) }; }); - - return services; } } diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index aece72b..a8e7f59 100644 --- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -15,7 +15,7 @@ public static class LoggerConfiguration { public static IHostBuilder AddCustomSerilog(this IHostBuilder hostBuilder) { - hostBuilder.UseSerilog((context, _, configuration) => + return hostBuilder.UseSerilog((context, _, configuration) => { var generalConfig = context.Configuration.Get()?.LogSettings; configuration @@ -51,13 +51,11 @@ public static class LoggerConfiguration configuration.Filter.ByExcluding(Matching.WithProperty("SourceContext", sc => sc.Contains("Microsoft.EntityFrameworkCore.Database.Command"))); }); - - return hostBuilder; } public static IApplicationBuilder UseCustomSerilog(this IApplicationBuilder app) { - app.UseSerilogRequestLogging(options => + return app.UseSerilogRequestLogging(options => { options.MessageTemplate = "[{RequestMethod}] {RequestPath} [Client {RemoteIPAddress}] [{StatusCode}] in {Elapsed:0.0000} ms"; @@ -75,7 +73,5 @@ public static class LoggerConfiguration diagnosticContext.Set("RemoteIPAddress", httpContext.Connection.RemoteIpAddress?.ToString()); }; }); - - return app; } } diff --git a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs index 1fc5b91..0eddcac 100644 --- a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs @@ -51,15 +51,13 @@ public static class SwaggerConfiguration options.IncludeXmlComments(Path.Combine(basePath, "ApiDtoDocs.xml")); }); - services.AddTransient, ConfigureSwaggerOptions>(); - - return services; + return services.AddTransient, ConfigureSwaggerOptions>(); } public static IApplicationBuilder UseCustomSwagger(this IApplicationBuilder app, IServiceProvider services) { app.UseSwagger(); - app.UseSwaggerUI(options => + return app.UseSwaggerUI(options => { options.InjectStylesheet($"{UrlHelper.GetSubPath}css/swagger/SwaggerDark.css"); var provider = services.GetService(); @@ -72,7 +70,5 @@ public static class SwaggerConfiguration options.RoutePrefix = UrlHelper.GetSubPathSwagger.Trim('/'); } }); - - return app; } } \ No newline at end of file -- 2.43.0 From 98ee3c389cd1cc86849e95dbe6113d7d5795d9a7 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 02:35:36 +0300 Subject: [PATCH 320/474] feat: add healthcheck for databases --- SqlData/Persistence/DependencyInjection.cs | 11 +++++++++++ SqlData/Persistence/Persistence.csproj | 10 +++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/SqlData/Persistence/DependencyInjection.cs b/SqlData/Persistence/DependencyInjection.cs index f86cd87..b9376a9 100644 --- a/SqlData/Persistence/DependencyInjection.cs +++ b/SqlData/Persistence/DependencyInjection.cs @@ -86,4 +86,15 @@ public static class DependencyInjection }; } } + + public static IHealthChecksBuilder AddDatabaseHealthCheck(this IHealthChecksBuilder healthChecksBuilder, DatabaseProvider dbProvider, string connection) + { + return dbProvider switch + { + DatabaseProvider.Mysql => healthChecksBuilder.AddMySql(connection, name: "MySql"), + DatabaseProvider.Sqlite => healthChecksBuilder.AddSqlite(connection, name: "Sqlite"), + DatabaseProvider.Postgresql => healthChecksBuilder.AddNpgSql(connection, name: "PostgreSQL"), + _ => throw new ArgumentException("Unsupported database provider", Enum.GetName(dbProvider)) + }; + } } \ No newline at end of file diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 5cb3dde..3008d4e 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -5,16 +5,20 @@ disable enable Winsomnia - 1.0.0 - 1.0.3.0 - 1.0.3.0 + 1.0.1 + 1.0.3.1 + 1.0.3.1 Mirea.Api.DataAccess.Persistence $(AssemblyName) + + + + -- 2.43.0 From 4fc28378c5960a4a89d5c02d171169b12daad50c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 02:36:39 +0300 Subject: [PATCH 321/474] feat: add healthcheck for main project --- .../Core/Startup/CacheConfiguration.cs | 18 ++++++++++-------- Endpoint/Endpoint.csproj | 2 ++ Endpoint/Program.cs | 16 +++++++++++++--- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs b/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs index 504a655..ca3ddb8 100644 --- a/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs @@ -7,17 +7,19 @@ namespace Mirea.Api.Endpoint.Configuration.Core.Startup; public static class CacheConfiguration { - public static IServiceCollection AddCustomRedis(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddCustomRedis(this IServiceCollection services, IConfiguration configuration, IHealthChecksBuilder? healthChecksBuilder = null) { var cache = configuration.Get()?.CacheSettings; - if (cache?.TypeDatabase == CacheSettings.CacheEnum.Redis) + if (cache?.TypeDatabase != CacheSettings.CacheEnum.Redis) + return services; + + services.AddStackExchangeRedisCache(options => { - services.AddStackExchangeRedisCache(options => - { - options.Configuration = cache.ConnectionString; - options.InstanceName = "mirea_"; - }); - } + options.Configuration = cache.ConnectionString; + options.InstanceName = "mirea_"; + }); + + healthChecksBuilder?.AddRedis(cache.ConnectionString!, name: "Redis"); return services; } diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index c61dd9e..5e86f4e 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -24,6 +24,8 @@ + + diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 0b39b2a..5d16dc2 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -23,7 +23,7 @@ namespace Mirea.Api.Endpoint; public class Program { - public static IServiceCollection AddDatabase(IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddDatabase(IServiceCollection services, IConfiguration configuration, IHealthChecksBuilder? healthCheckBuilder = null) { var dbSettings = configuration.Get()?.DbSettings; services.AddApplication(); @@ -31,6 +31,10 @@ public class Program dbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, dbSettings?.ConnectionStringSql ?? string.Empty); + healthCheckBuilder?.AddDatabaseHealthCheck( + dbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, + dbSettings?.ConnectionStringSql ?? string.Empty); + return services; } @@ -40,13 +44,18 @@ public class Program var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddConfiguration(EnvironmentConfiguration.GetEnvironment()); + + var healthCheckBuilder = builder.Services.AddHealthChecks(); builder.Configuration.AddJsonFile(GeneralConfig.FilePath, optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); + healthCheckBuilder.AddFile(x => x.AddFile(GeneralConfig.FilePath), name: nameof(GeneralConfig)); + builder.Configuration.AddJsonFile(Admin.FilePath, optional: true, reloadOnChange: true); builder.Services.Configure(builder.Configuration); + healthCheckBuilder.AddFile(x => x.AddFile(Admin.FilePath), name: nameof(Admin)); builder.Host.AddCustomSerilog(); - AddDatabase(builder.Services, builder.Configuration); + AddDatabase(builder.Services, builder.Configuration, healthCheckBuilder); builder.Services.AddControllers(); @@ -55,7 +64,7 @@ public class Program builder.Services.AddSingleton(); builder.Services.AddMemoryCache(); - builder.Services.AddCustomRedis(builder.Configuration); + builder.Services.AddCustomRedis(builder.Configuration, healthCheckBuilder); builder.Services.AddCors(options => { @@ -108,6 +117,7 @@ public class Program app.UseStaticFiles(UrlHelper.GetSubPath.TrimEnd('/')); app.UseCors("AllowAll"); app.UseCustomSerilog(); + app.MapHealthChecks("/health"); using (var scope = app.Services.CreateScope()) { -- 2.43.0 From ebec0a2d2bdf12fdf2ab5d714a2f7867b7d4ee0b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 02:37:08 +0300 Subject: [PATCH 322/474] fix: remove request to /health from log --- .../Core/Startup/LoggerConfiguration.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index a8e7f59..da000b1 100644 --- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -59,19 +59,25 @@ public static class LoggerConfiguration { options.MessageTemplate = "[{RequestMethod}] {RequestPath} [Client {RemoteIPAddress}] [{StatusCode}] in {Elapsed:0.0000} ms"; - options.GetLevel = (_, elapsed, ex) => elapsed >= 2500 || ex != null - ? LogEventLevel.Warning - : elapsed >= 1000 - ? LogEventLevel.Information - : LogEventLevel.Debug; + options.GetLevel = (httpContext, elapsed, ex) => + { + if (httpContext.Request.Path.StartsWithSegments("/health")) + return LogEventLevel.Verbose; + + return elapsed >= 2500 || ex != null + ? LogEventLevel.Warning + : elapsed >= 1000 + ? LogEventLevel.Information + : LogEventLevel.Debug; + }; options.EnrichDiagnosticContext = (diagnosticContext, httpContext) => - { - diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value); - diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme); - diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent); - diagnosticContext.Set("RemoteIPAddress", httpContext.Connection.RemoteIpAddress?.ToString()); - }; + { + diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value); + diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme); + diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent); + diagnosticContext.Set("RemoteIPAddress", httpContext.Connection.RemoteIpAddress?.ToString()); + }; }); } } -- 2.43.0 From f5dbc4685626324552f2e43b38af9d0fdcee8e3a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 02:37:28 +0300 Subject: [PATCH 323/474] build: add healthcheck for docker --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 2f0c414..2740821 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,9 @@ LABEL company="Winsomnia" LABEL maintainer.name="Wesser" maintainer.email="support@winsomnia.net" WORKDIR /app +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl --fail http://localhost:8080/health || exit 1 + FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY . . -- 2.43.0 From 0788c36bd2f5105bf3f0155faa4a3255ba3a921b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 04:42:27 +0300 Subject: [PATCH 324/474] style: remove "id" from text --- SqlData/Application/Common/Exceptions/NotFoundException.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SqlData/Application/Common/Exceptions/NotFoundException.cs b/SqlData/Application/Common/Exceptions/NotFoundException.cs index c310b74..419c2ad 100644 --- a/SqlData/Application/Common/Exceptions/NotFoundException.cs +++ b/SqlData/Application/Common/Exceptions/NotFoundException.cs @@ -5,6 +5,6 @@ namespace Mirea.Api.DataAccess.Application.Common.Exceptions; public class NotFoundException : Exception { - public NotFoundException(MemberInfo entity, string name, object id) : base($"The entity \"{entity.Name}\" property \"{name}\" was not found by id \"{id}\".") { } - public NotFoundException(MemberInfo entity, object id) : base($"The entity \"{entity.Name}\" was not found by id \"{id}\".") { } + public NotFoundException(MemberInfo entity, string name, object id) : base($"The entity \"{entity.Name}\" property \"{name}\" was not found by \"{id}\".") { } + public NotFoundException(MemberInfo entity, object id) : base($"The entity \"{entity.Name}\" was not found by \"{id}\".") { } } \ No newline at end of file -- 2.43.0 From 4605c818951ada6f9f9a61fcf7bfe1efe4c9a302 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 04:43:18 +0300 Subject: [PATCH 325/474] docs: add some specification --- Endpoint/Controllers/V1/ScheduleController.cs | 8 ++++++++ .../Queries/GetProfessorDetails/GetProfessorInfoQuery.cs | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index a3fec0a..05931b1 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -21,10 +21,18 @@ namespace Mirea.Api.Endpoint.Controllers.V1; [CacheMaxAge(true)] public class ScheduleController(IMediator mediator, IOptionsSnapshot config) : BaseController { + /// + /// Retrieves the start term for the schedule. + /// + /// The start term as a value. [CacheMaxAge(1, 0)] [HttpGet("StartTerm")] public ActionResult GetStartTerm() => config.Value.ScheduleSettings!.StartTerm; + /// + /// Retrieves the pair periods. + /// + /// A dictionary of pair periods, where the key is an integer identifier and the value is a object. [CacheMaxAge(1, 0)] [HttpGet("PairPeriod")] public ActionResult> GetPairPeriod() => config.Value.ScheduleSettings!.PairPeriod.ConvertToDto(); diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs index 93f1aa8..4fa0a25 100644 --- a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQuery.cs @@ -2,7 +2,16 @@ namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails; +/// +/// Represents a query to retrieve information about a professor. +/// +/// +/// This query can be used to fetch details of a professor by either their unique identifier. +/// public class GetProfessorInfoQuery : IRequest { + /// + /// The unique identifier for the professor. + /// public required int Id { get; set; } } \ No newline at end of file -- 2.43.0 From 84d7b095f0b6c403b45ab288a8b7b457bfcbb05e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 04:43:30 +0300 Subject: [PATCH 326/474] fix: return altName --- .../GetProfessorDetails/GetProfessorInfoQueryHandler.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs index 868201d..2f833bf 100644 --- a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs @@ -11,12 +11,13 @@ public class GetProfessorInfoQueryHandler(IProfessorDbContext dbContext) : IRequ { public async Task Handle(GetProfessorInfoQuery request, CancellationToken cancellationToken) { - var discipline = await dbContext.Professors.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Professor), request.Id); + var professor = await dbContext.Professors.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Professor), request.Id); return new ProfessorInfoVm() { - Id = discipline.Id, - Name = discipline.Name, + Id = professor.Id, + Name = professor.Name, + AltName = professor.AltName }; } } \ No newline at end of file -- 2.43.0 From 2ccc476686daf8a191daf32216015ad9d9100f83 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 04:44:18 +0300 Subject: [PATCH 327/474] feat: add search professors by name --- .../Controllers/V1/ProfessorController.cs | 32 ++++++++++++++++++ .../GetProfessorInfoSearchQuery.cs | 17 ++++++++++ .../GetProfessorInfoSearchQueryHandler.cs | 33 +++++++++++++++++++ .../ProfessorInfoListVm.cs | 15 +++++++++ 4 files changed, 97 insertions(+) create mode 100644 SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQuery.cs create mode 100644 SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQueryHandler.cs create mode 100644 SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/ProfessorInfoListVm.cs diff --git a/Endpoint/Controllers/V1/ProfessorController.cs b/Endpoint/Controllers/V1/ProfessorController.cs index 8b1a58d..722fa8c 100644 --- a/Endpoint/Controllers/V1/ProfessorController.cs +++ b/Endpoint/Controllers/V1/ProfessorController.cs @@ -2,6 +2,7 @@ using MediatR; using Microsoft.AspNetCore.Mvc; using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails; +using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetailsBySearch; using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorList; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; @@ -63,4 +64,35 @@ public class ProfessorController(IMediator mediator) : BaseController AltName = result.AltName }); } + + /// + /// Retrieves detailed information about professors based on their name. + /// + /// + /// This method searches for professors whose name matches the provided search term. + /// + /// The name of the professor to search for. Must be at least 4 characters long. + /// + /// A list of objects containing the details of the matching professors. + /// + [HttpGet("{name:required}")] + [BadRequestResponse] + [NotFoundResponse] + public async Task>> GetDetails(string name) + { + if (string.IsNullOrEmpty(name) || name.Length < 4) + return BadRequest($"The minimum number of characters is 4 (current: {name.Length})."); + + var result = await mediator.Send(new GetProfessorInfoSearchQuery() + { + Name = name + }); + + return Ok(result.Details.Select(x => new ProfessorResponse() + { + Id = x.Id, + Name = x.Name, + AltName = x.AltName + })); + } } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQuery.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQuery.cs new file mode 100644 index 0000000..afd52d1 --- /dev/null +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQuery.cs @@ -0,0 +1,17 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetailsBySearch; + +/// +/// Represents a query to retrieve information about a professor. +/// +/// +/// This query can be used to fetch details of a professor by either their name. +/// +public class GetProfessorInfoSearchQuery : IRequest +{ + /// + /// The name of the professor. + /// + public required string Name { get; set; } +} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQueryHandler.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQueryHandler.cs new file mode 100644 index 0000000..c466adc --- /dev/null +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQueryHandler.cs @@ -0,0 +1,33 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Common.Exceptions; +using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetailsBySearch; + +public class GetProfessorInfoSearchQueryHandler(IProfessorDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetProfessorInfoSearchQuery request, CancellationToken cancellationToken) + { + var name = request.Name.ToLower(); + var professor = await dbContext.Professors + .Where(p => p.Name.ToLower().Contains(name) || + (p.AltName != null && p.AltName.ToLower().Contains(name))) + .ToListAsync(cancellationToken) + ?? throw new NotFoundException(typeof(Domain.Schedule.Professor), request.Name); + + return new ProfessorInfoListVm() + { + Details = professor.Select(x => new ProfessorInfoVm() + { + Id = x.Id, + Name = x.Name, + AltName = x.AltName + }).ToList() + }; + } +} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/ProfessorInfoListVm.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/ProfessorInfoListVm.cs new file mode 100644 index 0000000..9b2637f --- /dev/null +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/ProfessorInfoListVm.cs @@ -0,0 +1,15 @@ +using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetails; +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetailsBySearch; + +/// +/// Represents professors. +/// +public class ProfessorInfoListVm +{ + /// + /// List of + /// + public required IList Details { get; set; } +} \ No newline at end of file -- 2.43.0 From ed99fce9b8cc9fa148d920b67b94c894df695d6f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Fri, 25 Oct 2024 09:38:28 +0300 Subject: [PATCH 328/474] build: fix unhealth Signed-off-by: Polianin Nikita --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 2740821..386c02f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ LABEL company="Winsomnia" LABEL maintainer.name="Wesser" maintainer.email="support@winsomnia.net" WORKDIR /app +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl --fail http://localhost:8080/health || exit 1 -- 2.43.0 From fc5ec1fd5496113dea91f1b04090f0c85528aa5f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 03:02:25 +0300 Subject: [PATCH 329/474] fix: log exception --- .../CustomExceptionHandlerMiddleware.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index 337e5dc..288a222 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -1,5 +1,6 @@ using FluentValidation; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Mirea.Api.DataAccess.Application.Common.Exceptions; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Exceptions; @@ -9,7 +10,7 @@ using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Configuration.Core.Middleware; -public class CustomExceptionHandlerMiddleware(RequestDelegate next) +public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger logger) { public async Task InvokeAsync(HttpContext context) { @@ -23,7 +24,7 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next) } } - private static Task HandleExceptionAsync(HttpContext context, Exception exception) + private Task HandleExceptionAsync(HttpContext context, Exception exception) { var code = StatusCodes.Status500InternalServerError; var result = string.Empty; @@ -48,12 +49,23 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next) context.Response.ContentType = "application/json"; context.Response.StatusCode = code; - if (string.IsNullOrEmpty(result)) - result = JsonSerializer.Serialize(new ErrorResponse() - { - Error = exception.Message, - Code = code - }); + if (!string.IsNullOrEmpty(result)) + return context.Response.WriteAsync(result); + + string error; + if (code == StatusCodes.Status500InternalServerError) + { + error = "Internal Server Error"; + logger.LogError("Internal server error: {Message}\nStackTrace:\n{StackTrace}", exception.Message, exception.StackTrace); + } + else + error = exception.Message; + + result = JsonSerializer.Serialize(new ErrorResponse() + { + Error = error, + Code = code + }); return context.Response.WriteAsync(result); } -- 2.43.0 From 6c20713d81a8716ddd064b4acd9497deca6fe5ba Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 04:09:31 +0300 Subject: [PATCH 330/474] feat: add docker as localhost --- Endpoint/Common/Attributes/LocalhostAttribute.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Endpoint/Common/Attributes/LocalhostAttribute.cs b/Endpoint/Common/Attributes/LocalhostAttribute.cs index 734e0bf..7a483ca 100644 --- a/Endpoint/Common/Attributes/LocalhostAttribute.cs +++ b/Endpoint/Common/Attributes/LocalhostAttribute.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using System; using System.Net; namespace Mirea.Api.Endpoint.Common.Attributes; @@ -9,11 +10,21 @@ public class LocalhostAttribute : ActionFilterAttribute public override void OnActionExecuting(ActionExecutingContext context) { var ip = context.HttpContext.Connection.RemoteIpAddress; - if (ip == null || !IPAddress.IsLoopback(ip)) + + if (ip == null) { context.Result = new UnauthorizedResult(); return; } - base.OnActionExecuting(context); + + var isRunningInContainer = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER")?.ToLower() == "true"; + + if (IPAddress.IsLoopback(ip) || (isRunningInContainer && ip.ToString().StartsWith("172."))) + { + base.OnActionExecuting(context); + return; + } + + context.Result = new UnauthorizedResult(); } } \ No newline at end of file -- 2.43.0 From 8fad070a9c3ee94901e3bf136fd4cc544887ab1b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 04:36:20 +0300 Subject: [PATCH 331/474] refactor: error logging --- .../Core/Middleware/CustomExceptionHandlerMiddleware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index 288a222..ef6756c 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -56,7 +56,7 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger Date: Sun, 27 Oct 2024 05:41:49 +0300 Subject: [PATCH 332/474] feat: add sync and mapper schedule --- Endpoint/Sync/Common/DataRepository.cs | 42 ++++ Endpoint/Sync/ScheduleSynchronizer.cs | 288 +++++++++++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100644 Endpoint/Sync/Common/DataRepository.cs create mode 100644 Endpoint/Sync/ScheduleSynchronizer.cs diff --git a/Endpoint/Sync/Common/DataRepository.cs b/Endpoint/Sync/Common/DataRepository.cs new file mode 100644 index 0000000..c4de0b2 --- /dev/null +++ b/Endpoint/Sync/Common/DataRepository.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Mirea.Api.Endpoint.Sync.Common; + +internal class DataRepository where T : class +{ + private readonly ConcurrentBag _data = []; + private readonly object _lock = new(); + + public IEnumerable GetAll() => _data.ToList(); + + public DataRepository(List data) + { + foreach (var d in data) + _data.Add(d); + } + + public T? Get(Func predicate) + { + var entity = _data.FirstOrDefault(predicate); + return entity; + } + + public T Create(Func createEntity) + { + var entity = createEntity(); + _data.Add(entity); + return entity; + } + + public T GetOrCreate(Func predicate, Func createEntity) + { + lock (_lock) + { + var entity = Get(predicate); + return entity ?? Create(createEntity); + } + } +} \ No newline at end of file diff --git a/Endpoint/Sync/ScheduleSynchronizer.cs b/Endpoint/Sync/ScheduleSynchronizer.cs new file mode 100644 index 0000000..20d1e5d --- /dev/null +++ b/Endpoint/Sync/ScheduleSynchronizer.cs @@ -0,0 +1,288 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Mirea.Api.DataAccess.Domain.Schedule; +using Mirea.Api.DataAccess.Persistence; +using Mirea.Api.Endpoint.Common.Interfaces; +using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Sync.Common; +using Mirea.Tools.Schedule.WebParser; +using Mirea.Tools.Schedule.WebParser.Common.Domain; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Group = Mirea.Api.DataAccess.Domain.Schedule.Group; + +namespace Mirea.Api.Endpoint.Sync; + +internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSnapshot config, ILogger logger, IMaintenanceModeService maintenanceMode) +{ + private readonly DataRepository _campuses = new([.. dbContext.Campuses]); + private readonly DataRepository _disciplines = new([.. dbContext.Disciplines]); + private readonly DataRepository _faculties = new([.. dbContext.Faculties]); + private readonly DataRepository _groups = new([.. dbContext.Groups]); + private readonly DataRepository _lectureHalls = new([.. dbContext.LectureHalls]); + private readonly DataRepository _lessons = new([]); + private readonly DataRepository _lessonAssociation = new([]); + private readonly DataRepository _professors = new([.. dbContext.Professors]); + private readonly DataRepository _typeOfOccupations = new([.. dbContext.TypeOfOccupations]); + private readonly DataRepository _specificWeeks = new([]); + + // todo: transfer data to storage + private static string GetFaculty(char c) => + c switch + { + 'У' => "ИТУ", + 'Б' => "ИКБ", + 'Х' => "ИТХТ", + 'Э' => "ИПТИП", + 'Т' => "ИПТИП", + 'Р' => "ИРИ", + 'К' => "ИИИ", + 'И' => "ИИТ", + 'П' => "ИИТ", + _ => throw new ArgumentOutOfRangeException(nameof(c), c, null) + }; + + private void ParallelSync(GroupResult groupInfo) + { + var facultyName = GetFaculty(groupInfo.Group[0]); + + var faculty = _faculties.GetOrCreate( + f => f.Name.Equals(facultyName, StringComparison.OrdinalIgnoreCase), + () => new Faculty + { + Name = facultyName + }); + + var groupName = OnlyGroupName().Match(groupInfo.Group.ToUpper()).Value; + + var group = _groups.GetOrCreate( + g => g.Name.Equals(groupName, StringComparison.OrdinalIgnoreCase), + () => new Group + { + Name = groupName, + Faculty = faculty + }); + + var typeOfOccupation = _typeOfOccupations.GetOrCreate( + t => t.ShortName.Equals(groupInfo.TypeOfOccupation.Trim(), StringComparison.OrdinalIgnoreCase), + () => new TypeOfOccupation + { + ShortName = groupInfo.TypeOfOccupation.ToUpper() + }); + + List? professor = []; + if (groupInfo.Professor != null) + { + foreach (var prof in groupInfo.Professor) + { + var professorParts = prof.Split(' ').ToList(); + + string? altName = null; + + if (professorParts is { Count: >= 2 }) + { + altName = professorParts.ElementAtOrDefault(0); + + if (professorParts.ElementAtOrDefault(1) != null) + altName += $" {professorParts.ElementAtOrDefault(1)?[0]}."; + + if (professorParts.ElementAtOrDefault(2) != null) + altName += $"{professorParts.ElementAtOrDefault(2)?[0]}."; + } + + if (string.IsNullOrEmpty(altName)) + continue; + + var profDb = _professors.GetOrCreate(x => + (x.AltName == null || x.AltName.Equals(prof, StringComparison.OrdinalIgnoreCase)) && + x.Name.Equals(altName, StringComparison.OrdinalIgnoreCase), + () => new Professor + { + AltName = prof, + Name = altName + }); + + professor.Add(profDb); + } + } + else + professor = null; + + List? hall = null; + List? campuses; + if (groupInfo.Campuses != null && groupInfo.Campuses.Length != 0) + { + hall = []; + campuses = []; + for (int i = 0; i < groupInfo.Campuses.Length; i++) + { + var campus = groupInfo.Campuses[i]; + campuses.Add(_campuses.GetOrCreate( + c => c.CodeName.Equals(campus, StringComparison.OrdinalIgnoreCase), + () => new Campus + { + CodeName = campus.ToUpper() + })); + if (groupInfo.LectureHalls == null || groupInfo.LectureHalls.Length <= i) + continue; + var lectureHall = groupInfo.LectureHalls[i]; + + hall.Add(_lectureHalls.GetOrCreate(l => + l.Name.Equals(lectureHall, StringComparison.OrdinalIgnoreCase) && + string.Equals(l.Campus?.CodeName, campuses[^1].CodeName, StringComparison.CurrentCultureIgnoreCase), + () => new LectureHall + { + Name = lectureHall, + Campus = campuses[^1] + })); + } + } + + var discipline = _disciplines.GetOrCreate( + d => d.Name.Equals(groupInfo.Discipline, StringComparison.OrdinalIgnoreCase), + () => new Discipline + { + Name = groupInfo.Discipline + }); + + var lesson = _lessons.GetOrCreate(l => + l.IsEven == groupInfo.IsEven && + l.DayOfWeek == groupInfo.Day && + l.PairNumber == groupInfo.Pair && + l.Discipline?.Name == discipline.Name, + () => + { + var lesson = new Lesson + { + IsEven = groupInfo.IsEven, + DayOfWeek = groupInfo.Day, + PairNumber = groupInfo.Pair, + Discipline = discipline, + Group = group, + IsExcludedWeeks = groupInfo.IsExclude + }; + + if (groupInfo.SpecialWeek == null) + return lesson; + + foreach (var week in groupInfo.SpecialWeek) + _specificWeeks.Create(() => new SpecificWeek + { + Lesson = lesson, + WeekNumber = week + }); + + return lesson; + }); + + int maxValue = int.Max(int.Max(professor?.Count ?? -1, hall?.Count ?? -1), 1); + + for (int i = 0; i < maxValue; i++) + { + var prof = professor?.ElementAtOrDefault(i); + var lectureHall = hall?.ElementAtOrDefault(i); + _lessonAssociation.Create(() => new LessonAssociation + { + Professor = prof, + Lesson = lesson, + LectureHall = lectureHall, + TypeOfOccupation = typeOfOccupation + }); + } + } + + private async Task SaveChanges(CancellationToken cancellationToken) + { + foreach (var group in _groups.GetAll()) + { + var existingGroup = await dbContext.Groups.FirstOrDefaultAsync(g => g.Id == group.Id, cancellationToken); + if (existingGroup != null) + dbContext.Remove(existingGroup); + } + + await dbContext.Disciplines.BulkSynchronizeAsync(_disciplines.GetAll(), bulkOperation => bulkOperation.BatchSize = 1000, cancellationToken); + await dbContext.Professors.BulkSynchronizeAsync(_professors.GetAll(), bulkOperation => bulkOperation.BatchSize = 1000, cancellationToken); + await dbContext.TypeOfOccupations.BulkSynchronizeAsync(_typeOfOccupations.GetAll(), bulkOperation => bulkOperation.BatchSize = 100, cancellationToken); + await dbContext.Faculties.BulkSynchronizeAsync(_faculties.GetAll(), bulkOperation => bulkOperation.BatchSize = 100, cancellationToken); + await dbContext.Campuses.BulkSynchronizeAsync(_campuses.GetAll(), bulkOperation => bulkOperation.BatchSize = 10, cancellationToken); + await dbContext.LectureHalls.BulkSynchronizeAsync(_lectureHalls.GetAll(), bulkOperation => bulkOperation.BatchSize = 1000, cancellationToken); + await dbContext.Groups.BulkSynchronizeAsync(_groups.GetAll(), bulkOperation => bulkOperation.BatchSize = 100, cancellationToken); + await dbContext.Lessons.BulkSynchronizeAsync(_lessons.GetAll(), bulkOperation => bulkOperation.BatchSize = 1000, cancellationToken); + await dbContext.SpecificWeeks.BulkSynchronizeAsync(_specificWeeks.GetAll(), bulkOperation => bulkOperation.BatchSize = 1000, cancellationToken); + await dbContext.LessonAssociations.BulkSynchronizeAsync(_lessonAssociation.GetAll(), bulkOperation => bulkOperation.BatchSize = 1000, cancellationToken); + } + + public async Task StartSync(CancellationToken cancellationToken) + { + var pairPeriods = config.Value.ScheduleSettings?.PairPeriod; + var startTerm = config.Value.ScheduleSettings?.StartTerm; + + if (pairPeriods == null || startTerm == null) + { + logger.LogWarning("It is not possible to synchronize the schedule due to the fact that the {Arg1} or {Arg2} variable is not initialized.", nameof(pairPeriods), nameof(startTerm)); + + return; + } + + Stopwatch watch = new(); + watch.Start(); + + var parser = new Parser + { + Pairs = pairPeriods + .ToDictionary(x => x.Key, + x => (x.Value.Start, x.Value.End)), + TermStart = startTerm.Value.ToDateTime(new TimeOnly(0, 0, 0)) + }; + + try + { + logger.LogDebug("Start parsing schedule"); + var data = await parser.ParseAsync(cancellationToken); + + watch.Stop(); + var parsingTime = watch.ElapsedMilliseconds; + watch.Restart(); + + ParallelOptions options = new() + { + CancellationToken = cancellationToken, + MaxDegreeOfParallelism = Environment.ProcessorCount + }; + + logger.LogDebug("Start mapping parsed data"); + Parallel.ForEach(data, options, ParallelSync); + + watch.Stop(); + var mappingTime = watch.ElapsedMilliseconds; + watch.Restart(); + + maintenanceMode.EnableMaintenanceMode(); + + logger.LogDebug("Start saving changing"); + await SaveChanges(cancellationToken); + + maintenanceMode.DisableMaintenanceMode(); + + watch.Stop(); + + logger.LogInformation("Parsing time: {ParsingTime}ms Mapping time: {MappingTime}ms Saving time: {SavingTime}ms Total time: {TotalTime}ms", + parsingTime, mappingTime, watch.ElapsedMilliseconds, parsingTime + mappingTime + watch.ElapsedMilliseconds); + } + catch (Exception ex) + { + logger.LogError(ex, "An error occurred during synchronization."); + maintenanceMode.DisableMaintenanceMode(); + throw; + } + } + + [GeneratedRegex(@"\w{4}-\d{2}-\d{2}(?=\s?\d?\s?[Пп]/?[Гг]\s?\d?)?")] + private static partial Regex OnlyGroupName(); +} \ No newline at end of file -- 2.43.0 From 80e74b34c1d3d442f8121c66063020ec0ce951df Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 05:42:50 +0300 Subject: [PATCH 333/474] feat: add background task --- .../Common/Services/ScheduleSyncManager.cs | 15 +++ .../BackgroundTasks/ScheduleSyncService.cs | 121 ++++++++++++++++++ Endpoint/Endpoint.csproj | 6 +- Endpoint/Program.cs | 3 + 4 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 Endpoint/Common/Services/ScheduleSyncManager.cs create mode 100644 Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs diff --git a/Endpoint/Common/Services/ScheduleSyncManager.cs b/Endpoint/Common/Services/ScheduleSyncManager.cs new file mode 100644 index 0000000..7bff018 --- /dev/null +++ b/Endpoint/Common/Services/ScheduleSyncManager.cs @@ -0,0 +1,15 @@ +using System; + +namespace Mirea.Api.Endpoint.Common.Services; + +public static class ScheduleSyncManager +{ + public static event Action? OnUpdateIntervalRequested; + public static event Action? OnForceSyncRequested; + + public static void RequestIntervalUpdate() => + OnUpdateIntervalRequested?.Invoke(); + + public static void RequestForceSync() => + OnForceSyncRequested?.Invoke(); +} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs new file mode 100644 index 0000000..6186f3e --- /dev/null +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -0,0 +1,121 @@ +using Cronos; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Sync; +using System; +using System.Threading; +using System.Threading.Tasks; +using IServiceProvider = System.IServiceProvider; + +namespace Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks; + +public class ScheduleSyncService : IHostedService, IDisposable +{ + private Timer? _timer; + private readonly IOptionsMonitor _generalConfigMonitor; + private readonly ILogger _logger; + private CancellationTokenSource _cancellationTokenSource = new(); + private readonly IServiceProvider _serviceProvider; + + public ScheduleSyncService(IOptionsMonitor generalConfigMonitor, ILogger logger, IServiceProvider serviceProvider) + { + _generalConfigMonitor = generalConfigMonitor; + _logger = logger; + _serviceProvider = serviceProvider; + + ScheduleSyncManager.OnForceSyncRequested += OnForceSyncRequested; + ScheduleSyncManager.OnUpdateIntervalRequested += OnUpdateIntervalRequested; + } + + private void OnForceSyncRequested() + { + StopAsync(default).ContinueWith(_ => + { + _cancellationTokenSource = new CancellationTokenSource(); + ExecuteTask(null); + }); + } + + private void OnUpdateIntervalRequested() + { + StopAsync(default).ContinueWith(_ => + { + StartAsync(default); + }); + } + + private void ScheduleNextRun() + { + var cronExpression = _generalConfigMonitor.CurrentValue.ScheduleSettings?.CronUpdateSchedule; + if (string.IsNullOrEmpty(cronExpression)) + { + _logger.LogWarning("Cron expression is not set. The scheduled task will not run."); + return; + } + + var nextRunTime = CronExpression.Parse(cronExpression).GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local); + + if (!nextRunTime.HasValue) + { + _logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}", TimeZoneInfo.Local.DisplayName); + return; + } + + _logger.LogInformation("Next task run in {Time}", nextRunTime.Value.ToString("G")); + + var delay = (nextRunTime.Value - DateTimeOffset.Now).TotalMilliseconds; + + // The chance is small, but it's better to check + if (delay <= 0) + delay = 1; + + _cancellationTokenSource = new CancellationTokenSource(); + _timer = new Timer(ExecuteTask, null, (int)delay, Timeout.Infinite); + } + + private async void ExecuteTask(object? state) + { + try + { + using var scope = _serviceProvider.CreateScope(); + var syncService = ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider); + await syncService.StartSync(_cancellationTokenSource.Token); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occurred during schedule synchronization."); + } + finally + { + ScheduleNextRun(); + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + ScheduleNextRun(); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _cancellationTokenSource.Cancel(); + _timer?.Change(Timeout.Infinite, 0); + return Task.CompletedTask; + } + + public void Dispose() + { + StopAsync(default).GetAwaiter().GetResult(); + _timer?.Dispose(); + ScheduleSyncManager.OnForceSyncRequested -= OnForceSyncRequested; + ScheduleSyncManager.OnUpdateIntervalRequested -= OnUpdateIntervalRequested; + _cancellationTokenSource.Dispose(); + + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 5e86f4e..da40f60 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0-rc3 - 1.0.2.3 - 1.0.2.3 + 1.0-rc4 + 1.0.2.4 + 1.0.2.4 Mirea.Api.Endpoint $(AssemblyName) Exe diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 5d16dc2..4ce387a 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -10,6 +10,7 @@ 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.BackgroundTasks; using Mirea.Api.Endpoint.Configuration.Core.Middleware; using Mirea.Api.Endpoint.Configuration.Core.Startup; using Mirea.Api.Endpoint.Configuration.Model; @@ -63,6 +64,8 @@ public class Program builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddHostedService(); + builder.Services.AddMemoryCache(); builder.Services.AddCustomRedis(builder.Configuration, healthCheckBuilder); -- 2.43.0 From 8c932cf0bece5e56869793dadaaa1b356acfe8f2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 06:09:35 +0300 Subject: [PATCH 334/474] docs: update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d2d82f6..ab2c7d0 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Please note that the application will not work correctly if you do not fill in t | Variable | Default | Description | Required | |---------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| -| PATH_TO_SAVE | ❌ | The path to save the data. Saving logs (if the full path is not specified), databases (if Sqlite), and other data that should be saved in a place other than where the program is launched. | ✔ | +| PATH_TO_SAVE | Current folder | The path to save the data. Saving logs (if the full path is not specified), databases (if Sqlite), and other data that should be saved in a place other than where the program is launched. | ✔ | | SECURITY_SIGNING_TOKEN | ❌ | JWT signature token. This token will be used to create and verify the signature of JWT tokens. The token must be equal to 64 characters. | ✔ | | SECURITY_ENCRYPTION_TOKEN | ❌ | Token for JWT encryption. This token will be used to encrypt and decrypt JWT tokens. The token must be equal to 32 characters. | ✔ | | SECURITY_LIFE_TIME_RT | 1440 | Time in minutes after which the Refresh Token will become invalid. | ❌ | @@ -170,7 +170,7 @@ Install git in advance or clone the repository in another way. ```bash git clone https://git.winsomnia.net/Winsomnia/MireaBackend.git \ -cd DoctorTelegramBot +cd MireaBackend ``` ### Build Self Release -- 2.43.0 From dead9f89bb3562f1e77149dd1a5770f951d70b2f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 06:50:47 +0300 Subject: [PATCH 335/474] feat: remove unused ref campus --- ApiDto/Responses/FacultyDetailsResponse.cs | 36 -- ApiDto/Responses/FacultyResponse.cs | 5 - Endpoint/Controllers/V1/FacultyController.cs | 29 +- SqlData/Application/Application.csproj | 6 +- .../GetFacultyDetails/FacultyInfoVm.cs | 32 -- .../GetFacultyDetails/GetFacultyInfoQuery.cs | 8 - .../GetFacultyInfoQueryHandler.cs | 27 -- .../GetFacultyListQueryHandler.cs | 3 +- SqlData/Domain/Domain.csproj | 6 +- SqlData/Domain/Schedule/Campus.cs | 1 - SqlData/Domain/Schedule/Faculty.cs | 3 - ...20241027034820_RemoveUnusedRef.Designer.cs | 425 ++++++++++++++++++ .../20241027034820_RemoveUnusedRef.cs | 83 ++++ .../Migrations/UberDbContextModelSnapshot.cs | 23 +- ...20241027032753_RemoveUnusedRef.Designer.cs | 425 ++++++++++++++++++ .../20241027032753_RemoveUnusedRef.cs | 49 ++ .../Migrations/UberDbContextModelSnapshot.cs | 19 +- ...20241027032931_RemoveUnusedRef.Designer.cs | 400 +++++++++++++++++ .../20241027032931_RemoveUnusedRef.cs | 49 ++ .../Migrations/UberDbContextModelSnapshot.cs | 19 +- .../Mysql/Schedule/FacultyConfiguration.cs | 8 - .../Schedule/FacultyConfiguration.cs | 8 - .../Sqlite/Schedule/FacultyConfiguration.cs | 8 - SqlData/Persistence/Persistence.csproj | 6 +- 24 files changed, 1447 insertions(+), 231 deletions(-) delete mode 100644 ApiDto/Responses/FacultyDetailsResponse.cs delete mode 100644 SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs delete mode 100644 SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs delete mode 100644 SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs create mode 100644 SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.Designer.cs create mode 100644 SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.cs create mode 100644 SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.Designer.cs create mode 100644 SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs create mode 100644 SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.Designer.cs create mode 100644 SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.cs diff --git a/ApiDto/Responses/FacultyDetailsResponse.cs b/ApiDto/Responses/FacultyDetailsResponse.cs deleted file mode 100644 index 09c863b..0000000 --- a/ApiDto/Responses/FacultyDetailsResponse.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Mirea.Api.Dto.Responses; - -/// -/// Represents detailed information about a faculty. -/// -public class FacultyDetailsResponse -{ - /// - /// Gets or sets the unique identifier of the faculty. - /// - [Required] - public int Id { get; set; } - - /// - /// Gets or sets the name of the faculty. - /// - [Required] - public required string Name { get; set; } - - /// - /// Gets or sets the unique identifier of the campus to which the faculty belongs (optional). - /// - public int? CampusId { get; set; } - - /// - /// Gets or sets the name of the campus to which the faculty belongs (optional). - /// - public string? CampusName { get; set; } - - /// - /// Gets or sets the code name of the campus to which the faculty belongs (optional). - /// - public string? CampusCode { get; set; } -} \ No newline at end of file diff --git a/ApiDto/Responses/FacultyResponse.cs b/ApiDto/Responses/FacultyResponse.cs index adcf127..1e172c2 100644 --- a/ApiDto/Responses/FacultyResponse.cs +++ b/ApiDto/Responses/FacultyResponse.cs @@ -18,9 +18,4 @@ public class FacultyResponse /// [Required] public required string Name { get; set; } - - /// - /// Gets or sets the unique identifier of the campus to which the faculty belongs (optional). - /// - public int? CampusId { get; set; } } \ No newline at end of file diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index 56c88ad..3b5af11 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -1,7 +1,6 @@ using Asp.Versioning; using MediatR; using Microsoft.AspNetCore.Mvc; -using Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; using Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyList; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; @@ -35,34 +34,8 @@ public class FacultyController(IMediator mediator) : BaseController .Select(f => new FacultyResponse() { Id = f.Id, - Name = f.Name, - CampusId = f.CampusId + Name = f.Name }) ); } - - /// - /// Gets details of a specific faculty by ID. - /// - /// Faculty ID. - /// Details of the specified faculty. - [HttpGet("{id:int}")] - [BadRequestResponse] - [NotFoundResponse] - public async Task> GetDetails(int id) - { - var result = await mediator.Send(new GetFacultyInfoQuery() - { - Id = id - }); - - return Ok(new FacultyDetailsResponse() - { - Id = result.Id, - Name = result.Name, - CampusId = result.CampusId, - CampusCode = result.CampusCode, - CampusName = result.CampusName - }); - } } \ No newline at end of file diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 822bf16..1062253 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.1 - 1.0.3.1 - 1.0.3.1 + 1.0.2 + 1.0.3.2 + 1.0.3.2 Mirea.Api.DataAccess.Application $(AssemblyName) diff --git a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs deleted file mode 100644 index f7fb805..0000000 --- a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/FacultyInfoVm.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; - -/// -/// Represents faculties. -/// -public class FacultyInfoVm -{ - /// - /// The unique identifier for the faculty. - /// - public int Id { get; set; } - - /// - /// The name of the faculty. - /// - public required string Name { get; set; } - - /// - /// ID indicating the faculty's affiliation to the campus. - /// - public int? CampusId { get; set; } - - /// - /// Campus name indicating the faculty's affiliation to the campus. - /// - public string? CampusName { get; set; } - - /// - /// Campus code indicating the faculty's affiliation to the campus. - /// - public string? CampusCode { get; set; } -} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs deleted file mode 100644 index 2517b14..0000000 --- a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQuery.cs +++ /dev/null @@ -1,8 +0,0 @@ -using MediatR; - -namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; - -public class GetFacultyInfoQuery : IRequest -{ - public required int Id { get; set; } -} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs deleted file mode 100644 index a2f6486..0000000 --- a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyDetails/GetFacultyInfoQueryHandler.cs +++ /dev/null @@ -1,27 +0,0 @@ -using MediatR; -using Microsoft.EntityFrameworkCore; -using Mirea.Api.DataAccess.Application.Common.Exceptions; -using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; -using System.Threading; -using System.Threading.Tasks; - -namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyDetails; - -public class GetFacultyInfoQueryHandler(IFacultyDbContext dbContext) : IRequestHandler -{ - public async Task Handle(GetFacultyInfoQuery request, CancellationToken cancellationToken) - { - var faculty = await dbContext.Faculties - .Include(f => f.Campus) - .FirstOrDefaultAsync(f => f.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Faculty), request.Id); - - return new FacultyInfoVm() - { - Id = faculty.Id, - Name = faculty.Name, - CampusId = faculty.CampusId, - CampusName = faculty.Campus?.FullName, - CampusCode = faculty.Campus?.CodeName - }; - } -} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs index 1249fe7..d84b3a4 100644 --- a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs +++ b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/GetFacultyListQueryHandler.cs @@ -14,8 +14,7 @@ public class GetFacultyListQueryHandler(IFacultyDbContext dbContext) : IRequestH var dtos = dbContext.Faculties.OrderBy(f => f.Id).Select(f => new FacultyLookupDto() { Id = f.Id, - Name = f.Name, - CampusId = f.CampusId + Name = f.Name }); if (request is { PageSize: not null, Page: not null }) diff --git a/SqlData/Domain/Domain.csproj b/SqlData/Domain/Domain.csproj index 940e1f5..a3244e6 100644 --- a/SqlData/Domain/Domain.csproj +++ b/SqlData/Domain/Domain.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.0 - 1.0.3.0 - 1.0.3.0 + 1.0.1 + 1.0.3.1 + 1.0.3.1 Mirea.Api.DataAccess.Domain $(AssemblyName) diff --git a/SqlData/Domain/Schedule/Campus.cs b/SqlData/Domain/Schedule/Campus.cs index dde6d2d..fc6b158 100644 --- a/SqlData/Domain/Schedule/Campus.cs +++ b/SqlData/Domain/Schedule/Campus.cs @@ -9,6 +9,5 @@ public class Campus public string? FullName { get; set; } public string? Address { get; set; } - public List? Faculties { get; set; } public List? LectureHalls { get; set; } } \ No newline at end of file diff --git a/SqlData/Domain/Schedule/Faculty.cs b/SqlData/Domain/Schedule/Faculty.cs index cf87fa7..1f7682f 100644 --- a/SqlData/Domain/Schedule/Faculty.cs +++ b/SqlData/Domain/Schedule/Faculty.cs @@ -7,8 +7,5 @@ public class Faculty public int Id { get; set; } public required string Name { get; set; } - public int? CampusId { get; set; } - public Campus? Campus { get; set; } - public List? Groups { get; set; } } \ No newline at end of file diff --git a/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.Designer.cs b/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.Designer.cs new file mode 100644 index 0000000..ef398f9 --- /dev/null +++ b/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.Designer.cs @@ -0,0 +1,425 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; + +#nullable disable + +namespace MysqlMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + [Migration("20241027034820_RemoveUnusedRef")] + partial class RemoveUnusedRef + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BOOLEAN"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BOOLEAN"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.cs b/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.cs new file mode 100644 index 0000000..e990751 --- /dev/null +++ b/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MysqlMigrations.Migrations +{ + /// + public partial class RemoveUnusedRef : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Faculty_Campus_CampusId", + table: "Faculty"); + + migrationBuilder.DropIndex( + name: "IX_Faculty_CampusId", + table: "Faculty"); + + migrationBuilder.DropColumn( + name: "CampusId", + table: "Faculty"); + + migrationBuilder.AlterColumn( + name: "IsExcludedWeeks", + table: "Lesson", + type: "BOOLEAN(1)", + nullable: true, + oldClrType: typeof(ulong), + oldType: "BIT(1)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "IsEven", + table: "Lesson", + type: "BOOLEAN(1)", + nullable: false, + oldClrType: typeof(ulong), + oldType: "BIT(1)"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "IsExcludedWeeks", + table: "Lesson", + type: "BIT(1)", + nullable: true, + oldClrType: typeof(bool), + oldType: "BOOLEAN(1)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "IsEven", + table: "Lesson", + type: "BIT(1)", + nullable: false, + oldClrType: typeof(bool), + oldType: "BOOLEAN(1)"); + + migrationBuilder.AddColumn( + name: "CampusId", + table: "Faculty", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_CampusId", + table: "Faculty", + column: "CampusId"); + + migrationBuilder.AddForeignKey( + name: "FK_Faculty_Campus_CampusId", + table: "Faculty", + column: "CampusId", + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + } +} diff --git a/SqlData/Migrations/MysqlMigrations/Migrations/UberDbContextModelSnapshot.cs b/SqlData/Migrations/MysqlMigrations/Migrations/UberDbContextModelSnapshot.cs index 5e0b48f..dcd0999 100644 --- a/SqlData/Migrations/MysqlMigrations/Migrations/UberDbContextModelSnapshot.cs +++ b/SqlData/Migrations/MysqlMigrations/Migrations/UberDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace MysqlMigrations.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 64); MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); @@ -80,9 +80,6 @@ namespace MysqlMigrations.Migrations MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - b.Property("CampusId") - .HasColumnType("INTEGER"); - b.Property("Name") .IsRequired() .HasMaxLength(256) @@ -90,8 +87,6 @@ namespace MysqlMigrations.Migrations b.HasKey("Id"); - b.HasIndex("CampusId"); - b.HasIndex("Id") .IsUnique(); @@ -168,10 +163,10 @@ namespace MysqlMigrations.Migrations .HasColumnType("INTEGER"); b.Property("IsEven") - .HasColumnType("BIT(1)"); + .HasColumnType("BOOLEAN"); b.Property("IsExcludedWeeks") - .HasColumnType("BIT(1)"); + .HasColumnType("BOOLEAN"); b.Property("PairNumber") .HasColumnType("INTEGER"); @@ -296,16 +291,6 @@ namespace MysqlMigrations.Migrations b.ToTable("TypeOfOccupation", (string)null); }); - modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => - { - b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") - .WithMany("Faculties") - .HasForeignKey("CampusId") - .OnDelete(DeleteBehavior.SetNull); - - b.Navigation("Campus"); - }); - modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => { b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") @@ -392,8 +377,6 @@ namespace MysqlMigrations.Migrations modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => { - b.Navigation("Faculties"); - b.Navigation("LectureHalls"); }); diff --git a/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.Designer.cs b/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.Designer.cs new file mode 100644 index 0000000..3803c85 --- /dev/null +++ b/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.Designer.cs @@ -0,0 +1,425 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace PsqlMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + [Migration("20241027032753_RemoveUnusedRef")] + partial class RemoveUnusedRef + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BOOLEAN"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BOOLEAN"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs b/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs new file mode 100644 index 0000000..1589fd5 --- /dev/null +++ b/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PsqlMigrations.Migrations +{ + /// + public partial class RemoveUnusedRef : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Faculty_Campus_CampusId", + table: "Faculty"); + + migrationBuilder.DropIndex( + name: "IX_Faculty_CampusId", + table: "Faculty"); + + migrationBuilder.DropColumn( + name: "CampusId", + table: "Faculty"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CampusId", + table: "Faculty", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_CampusId", + table: "Faculty", + column: "CampusId"); + + migrationBuilder.AddForeignKey( + name: "FK_Faculty_Campus_CampusId", + table: "Faculty", + column: "CampusId", + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + } +} diff --git a/SqlData/Migrations/PsqlMigrations/Migrations/UberDbContextModelSnapshot.cs b/SqlData/Migrations/PsqlMigrations/Migrations/UberDbContextModelSnapshot.cs index 6039a03..10dcf26 100644 --- a/SqlData/Migrations/PsqlMigrations/Migrations/UberDbContextModelSnapshot.cs +++ b/SqlData/Migrations/PsqlMigrations/Migrations/UberDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace PsqlMigrations.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -80,9 +80,6 @@ namespace PsqlMigrations.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("CampusId") - .HasColumnType("INTEGER"); - b.Property("Name") .IsRequired() .HasMaxLength(256) @@ -90,8 +87,6 @@ namespace PsqlMigrations.Migrations b.HasKey("Id"); - b.HasIndex("CampusId"); - b.HasIndex("Id") .IsUnique(); @@ -296,16 +291,6 @@ namespace PsqlMigrations.Migrations b.ToTable("TypeOfOccupation", (string)null); }); - modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => - { - b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") - .WithMany("Faculties") - .HasForeignKey("CampusId") - .OnDelete(DeleteBehavior.SetNull); - - b.Navigation("Campus"); - }); - modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => { b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") @@ -392,8 +377,6 @@ namespace PsqlMigrations.Migrations modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => { - b.Navigation("Faculties"); - b.Navigation("LectureHalls"); }); diff --git a/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.Designer.cs b/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.Designer.cs new file mode 100644 index 0000000..9b53069 --- /dev/null +++ b/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.Designer.cs @@ -0,0 +1,400 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Mirea.Api.DataAccess.Persistence; + +#nullable disable + +namespace SqliteMigrations.Migrations +{ + [DbContext(typeof(UberDbContext))] + [Migration("20241027032931_RemoveUnusedRef")] + partial class RemoveUnusedRef + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("CodeName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FullName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Campus", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Discipline", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Faculty", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FacultyId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FacultyId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CampusId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("LectureHall", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("DisciplineId") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEven") + .HasColumnType("BOOLEAN"); + + b.Property("IsExcludedWeeks") + .HasColumnType("BOOLEAN"); + + b.Property("PairNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisciplineId"); + + b.HasIndex("GroupId"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Lesson", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LectureHallId") + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("LinkToMeet") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ProfessorId") + .HasColumnType("INTEGER"); + + b.Property("TypeOfOccupationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LectureHallId"); + + b.HasIndex("LessonId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("TypeOfOccupationId"); + + b.ToTable("LessonAssociation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AltName") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Professor", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LessonId") + .HasColumnType("INTEGER"); + + b.Property("WeekNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LessonId"); + + b.ToTable("SpecificWeek", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ShortName") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("TypeOfOccupation", (string)null); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") + .WithMany("Groups") + .HasForeignKey("FacultyId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Faculty"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") + .WithMany("LectureHalls") + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Discipline", "Discipline") + .WithMany("Lessons") + .HasForeignKey("DisciplineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Group", "Group") + .WithMany("Lessons") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Discipline"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LessonAssociation", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", "LectureHall") + .WithMany("LessonAssociations") + .HasForeignKey("LectureHallId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("LessonAssociations") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Professor", "Professor") + .WithMany("LessonAssociations") + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", "TypeOfOccupation") + .WithMany("Lessons") + .HasForeignKey("TypeOfOccupationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LectureHall"); + + b.Navigation("Lesson"); + + b.Navigation("Professor"); + + b.Navigation("TypeOfOccupation"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.SpecificWeek", b => + { + b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Lesson", "Lesson") + .WithMany("SpecificWeeks") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => + { + b.Navigation("LectureHalls"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Discipline", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.LectureHall", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Lesson", b => + { + b.Navigation("LessonAssociations"); + + b.Navigation("SpecificWeeks"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Professor", b => + { + b.Navigation("LessonAssociations"); + }); + + modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.TypeOfOccupation", b => + { + b.Navigation("Lessons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.cs b/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.cs new file mode 100644 index 0000000..4fcdfcc --- /dev/null +++ b/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SqliteMigrations.Migrations +{ + /// + public partial class RemoveUnusedRef : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Faculty_Campus_CampusId", + table: "Faculty"); + + migrationBuilder.DropIndex( + name: "IX_Faculty_CampusId", + table: "Faculty"); + + migrationBuilder.DropColumn( + name: "CampusId", + table: "Faculty"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CampusId", + table: "Faculty", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Faculty_CampusId", + table: "Faculty", + column: "CampusId"); + + migrationBuilder.AddForeignKey( + name: "FK_Faculty_Campus_CampusId", + table: "Faculty", + column: "CampusId", + principalTable: "Campus", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + } +} diff --git a/SqlData/Migrations/SqliteMigrations/Migrations/UberDbContextModelSnapshot.cs b/SqlData/Migrations/SqliteMigrations/Migrations/UberDbContextModelSnapshot.cs index c463963..2834b48 100644 --- a/SqlData/Migrations/SqliteMigrations/Migrations/UberDbContextModelSnapshot.cs +++ b/SqlData/Migrations/SqliteMigrations/Migrations/UberDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace SqliteMigrations.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => { @@ -69,9 +69,6 @@ namespace SqliteMigrations.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("CampusId") - .HasColumnType("INTEGER"); - b.Property("Name") .IsRequired() .HasMaxLength(256) @@ -79,8 +76,6 @@ namespace SqliteMigrations.Migrations b.HasKey("Id"); - b.HasIndex("CampusId"); - b.HasIndex("Id") .IsUnique(); @@ -271,16 +266,6 @@ namespace SqliteMigrations.Migrations b.ToTable("TypeOfOccupation", (string)null); }); - modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Faculty", b => - { - b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Campus", "Campus") - .WithMany("Faculties") - .HasForeignKey("CampusId") - .OnDelete(DeleteBehavior.SetNull); - - b.Navigation("Campus"); - }); - modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Group", b => { b.HasOne("Mirea.Api.DataAccess.Domain.Schedule.Faculty", "Faculty") @@ -367,8 +352,6 @@ namespace SqliteMigrations.Migrations modelBuilder.Entity("Mirea.Api.DataAccess.Domain.Schedule.Campus", b => { - b.Navigation("Faculties"); - b.Navigation("LectureHalls"); }); diff --git a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/FacultyConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/FacultyConfiguration.cs index 3e2e24d..8eede85 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/FacultyConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Mysql/Schedule/FacultyConfiguration.cs @@ -14,13 +14,5 @@ public class FacultyConfiguration : IEntityTypeConfiguration builder.Property(f => f.Id).HasColumnType("INT").IsRequired().ValueGeneratedOnAdd(); builder.Property(f => f.Name).HasColumnType("VARCHAR(256)").IsRequired().HasMaxLength(256); - - builder.Property(f => f.CampusId).HasColumnType("INT"); - - builder - .HasOne(f => f.Campus) - .WithMany(c => c.Faculties) - .HasForeignKey(c => c.CampusId) - .OnDelete(DeleteBehavior.SetNull); } } \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/FacultyConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/FacultyConfiguration.cs index 05f622f..a270346 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/FacultyConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Postgresql/Schedule/FacultyConfiguration.cs @@ -14,13 +14,5 @@ public class FacultyConfiguration : IEntityTypeConfiguration builder.Property(f => f.Id).HasColumnType("serial").IsRequired().ValueGeneratedOnAdd(); builder.Property(f => f.Name).HasColumnType("text").IsRequired().HasMaxLength(256); - - builder.Property(f => f.CampusId).HasColumnType("serial"); - - builder - .HasOne(f => f.Campus) - .WithMany(c => c.Faculties) - .HasForeignKey(c => c.CampusId) - .OnDelete(DeleteBehavior.SetNull); } } \ No newline at end of file diff --git a/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs index c7cde69..31d974f 100644 --- a/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs @@ -14,13 +14,5 @@ public class FacultyConfiguration : IEntityTypeConfiguration builder.Property(f => f.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd(); builder.Property(f => f.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(256); - - builder.Property(f => f.CampusId).HasColumnType("INTEGER"); - - builder - .HasOne(f => f.Campus) - .WithMany(c => c.Faculties) - .HasForeignKey(c => c.CampusId) - .OnDelete(DeleteBehavior.SetNull); } } \ No newline at end of file diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 3008d4e..433daa1 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.1 - 1.0.3.1 - 1.0.3.1 + 1.0.2 + 1.0.3.2 + 1.0.3.2 Mirea.Api.DataAccess.Persistence $(AssemblyName) -- 2.43.0 From d8dbf1562f6be947077330b2bfe6d61ccda18fd0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 06:51:05 +0300 Subject: [PATCH 336/474] refactor: clean code --- .../Configuration/Core/BackgroundTasks/ScheduleSyncService.cs | 1 - Endpoint/Controllers/Configuration/SetupController.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs index 6186f3e..8d897ff 100644 --- a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -9,7 +9,6 @@ using Mirea.Api.Endpoint.Sync; using System; using System.Threading; using System.Threading.Tasks; -using IServiceProvider = System.IServiceProvider; namespace Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks; diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index e306092..d0033ea 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -161,13 +161,13 @@ public class SetupController( } else if (Directory.GetDirectories(path).Length != 0 || !Directory.GetFiles(path).Select(x => string.Equals(Path.GetFileName(x), "database.db3")).All(x => x)) + { throw new ControllerArgumentException("Such a folder exists. Enter a different name"); + } var filePath = Path.Combine(path, "database.db3"); var connectionString = $"Data Source={filePath}"; - //System.IO.File.Create(filePath); - var result = SetDatabase(connectionString, DbSettings.DatabaseEnum.Sqlite); foreach (var file in Directory.GetFiles(path)) -- 2.43.0 From f203ee71f02a9bd0f9738cba2e6246ee6c9a4e64 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 07:14:30 +0300 Subject: [PATCH 337/474] fix: add an additional condition --- Endpoint/Sync/ScheduleSynchronizer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Endpoint/Sync/ScheduleSynchronizer.cs b/Endpoint/Sync/ScheduleSynchronizer.cs index 20d1e5d..d6cee1f 100644 --- a/Endpoint/Sync/ScheduleSynchronizer.cs +++ b/Endpoint/Sync/ScheduleSynchronizer.cs @@ -155,7 +155,8 @@ internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSna l.IsEven == groupInfo.IsEven && l.DayOfWeek == groupInfo.Day && l.PairNumber == groupInfo.Pair && - l.Discipline?.Name == discipline.Name, + l.Discipline?.Name == discipline.Name && + l.Group?.Name == group.Name, () => { var lesson = new Lesson -- 2.43.0 From 665544236f7adf6d8f254529224c717fb85be1e2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 07:34:26 +0300 Subject: [PATCH 338/474] build: add timezone --- .gitea/workflows/deploy-stage.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/deploy-stage.yaml b/.gitea/workflows/deploy-stage.yaml index 4da1ace..85abe79 100644 --- a/.gitea/workflows/deploy-stage.yaml +++ b/.gitea/workflows/deploy-stage.yaml @@ -77,6 +77,7 @@ jobs: -e SECURITY_SALT_SIZE=$SECURITY_SALT_SIZE \ -e ACTUAL_SUB_PATH=api \ -e SWAGGER_SUB_PATH=swagger \ + -e TZ=Europe/Moscow \ $DOCKER_IMAGE " -- 2.43.0 From 5317b7b563ba1354fe8825c5fe4fe52bc5a7bd64 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 27 Oct 2024 08:25:46 +0300 Subject: [PATCH 339/474] feat: add import to excel Made at the request of the customer --- Endpoint/Controllers/V1/ImportController.cs | 165 ++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 Endpoint/Controllers/V1/ImportController.cs diff --git a/Endpoint/Controllers/V1/ImportController.cs b/Endpoint/Controllers/V1/ImportController.cs new file mode 100644 index 0000000..c674d6b --- /dev/null +++ b/Endpoint/Controllers/V1/ImportController.cs @@ -0,0 +1,165 @@ +using Asp.Versioning; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Mirea.Api.DataAccess.Application.Cqrs.Schedule.Queries.GetScheduleList; +using Mirea.Api.Dto.Requests; +using Mirea.Api.Endpoint.Configuration.Model; +using OfficeOpenXml; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class ImportController(IMediator mediator, IOptionsSnapshot config) : BaseController +{ + // todo: transfer data to storage + private static string GetFaculty(char c) => + c switch + { + 'У' => "ИТУ", + 'Б' => "ИКБ", + 'Х' => "ИТХТ", + 'Э' => "ИПТИП", + 'Т' => "ИПТИП", + 'Р' => "ИРИ", + 'К' => "ИИИ", + 'И' => "ИИТ", + 'П' => "ИИТ", + _ => throw new ArgumentOutOfRangeException(nameof(c), c, null) + }; + + /// + /// Creates an Excel file based on a schedule filter + /// + /// The request object containing filter criteria. + /// Excel file + [HttpPost("ImportToExcel")] + [Produces("application/vnd.ms-excel")] + public async Task ImportToExcel([FromBody] ScheduleRequest request) + { + var result = (await mediator.Send(new GetScheduleListQuery + { + IsEven = request.IsEven, + DisciplineIds = request.Disciplines, + GroupIds = request.Groups, + LectureHallIds = request.LectureHalls, + ProfessorIds = request.Professors + })).Schedules; + + if (result.Count == 0) + return NoContent(); + + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + using var package = new ExcelPackage(); + var worksheet = package.Workbook.Worksheets.Add("Расписание"); + + int row = 1; + int col = 1; + + worksheet.Cells[row, col++].Value = "День"; + worksheet.Cells[row, col++].Value = "Пара"; + worksheet.Cells[row, col++].Value = "Неделя"; + worksheet.Cells[row, col++].Value = "Время"; + worksheet.Cells[row, col++].Value = "Группа"; + worksheet.Cells[row, col++].Value = "Институт"; + worksheet.Cells[row, col++].Value = "Курс"; + worksheet.Cells[row, col++].Value = "Дисциплина"; + worksheet.Cells[row, col++].Value = "Преподаватель"; + worksheet.Cells[row, col++].Value = "Вид"; + worksheet.Cells[row, col++].Value = "Кампус"; + worksheet.Cells[row, col].Value = "Ауд."; + + row++; + col = 1; + + var pairsDictionary = config.Value.ScheduleSettings!.PairPeriod; + + foreach (var dto in result.GroupBy(s => new + { + s.DayOfWeek, + s.PairNumber, + s.IsEven, + s.DisciplineId, + TypeOfOccupations = string.Join(',', s.TypeOfOccupations.OrderBy(x => x)), + LectureHalls = string.Join(',', s.LectureHalls.OrderBy(x => x)), + Campus = string.Join(',', s.Campus.OrderBy(x => x)), + Professors = string.Join(',', s.Professors.OrderBy(x => x)) + }) + .Select(g => new + { + g.Key.DayOfWeek, + g.Key.PairNumber, + g.Key.IsEven, + g.First().Discipline, + g.First().LectureHalls, + g.First().Campus, + g.First().Professors, + Groups = string.Join('\n', g.Select(x => x.Group)), + + IsExclude = g.First().IsExcludedWeeks, + g.First().TypeOfOccupations, + g.First().Weeks + }) + .ToList()) + { + // День + worksheet.Cells[row, col++].Value = + $"{(int)dto.DayOfWeek} [{CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedDayName(dto.DayOfWeek).ToUpper()}]"; + + // Пара + worksheet.Cells[row, col++].Value = dto.PairNumber + " п"; + + // Неделя + worksheet.Cells[row, col++].Value = $"[{(dto.IsEven ? 2 : 1)}] {(dto.IsEven ? "Четная" : "Нечетная")}"; + + // Время + worksheet.Cells[row, col++].Value = pairsDictionary[dto.PairNumber].Start.ToString(CultureInfo.CurrentCulture); + + // Группа + worksheet.Cells[row, col].Style.WrapText = true; + worksheet.Cells[row, col++].Value = dto.Groups; + + var groupTemplate = dto.Groups.Split('\n')[0]; + + // Институт + worksheet.Cells[row, col++].Value = GetFaculty(groupTemplate[0]); + // Курс + worksheet.Cells[row, col++].Value = groupTemplate[2] == 'М' ? + 'М' : + (24 - int.Parse(groupTemplate.Split(' ')[0].Split('-').TakeLast(1).ElementAt(0)) + 1).ToString(); + + var disciplineAdditional = string.Empty; + + if (dto.IsExclude.HasValue && dto.Weeks != null && dto.Weeks.Any()) + disciplineAdditional += $"{(dto.IsExclude.Value ? "Кр. " : "")}{string.Join(", ", dto.Weeks.OrderBy(x => x))} н. "; + + // Дисциплина + worksheet.Cells[row, col++].Value = disciplineAdditional + dto.Discipline; + + // Преподаватель + worksheet.Cells[row, col++].Value = dto.Professors; + // Вид + worksheet.Cells[row, col++].Value = dto.TypeOfOccupations.FirstOrDefault(); + // Кампус + worksheet.Cells[row, col++].Value = dto.Campus.FirstOrDefault()?.Replace("С-20", "С20").Replace("В-78", "В78"); + // Ауд. + worksheet.Cells[row, col].Value = dto.LectureHalls; + + col = 1; + row++; + } + + worksheet.Cells[1, 1, 1, 12].AutoFilter = true; + worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns(); + + var stream = new MemoryStream(); + await package.SaveAsAsync(stream); + stream.Position = 0; + return File(stream, "application/vnd.ms-excel", "data.xlsx"); + } +} \ No newline at end of file -- 2.43.0 From 5bc729eb666cc99fb771c7ba1f2cbf49b1d02f1d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 31 Oct 2024 04:05:40 +0300 Subject: [PATCH 340/474] fix: add an implementation for saving primitive data --- .../Security/DistributedCacheService.cs | 31 +++++++++++++++++ .../Services/Security/MemoryCacheService.cs | 34 ++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Endpoint/Common/Services/Security/DistributedCacheService.cs b/Endpoint/Common/Services/Security/DistributedCacheService.cs index 1d4a32b..3630ae0 100644 --- a/Endpoint/Common/Services/Security/DistributedCacheService.cs +++ b/Endpoint/Common/Services/Security/DistributedCacheService.cs @@ -17,12 +17,43 @@ public class DistributedCacheService(IDistributedCache cache) : ICacheService SlidingExpiration = slidingExpiration }; + var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + if (type.IsPrimitive || type == typeof(string) || type == typeof(DateTime)) + { + await cache.SetStringAsync(key, value?.ToString() ?? string.Empty, options, cancellationToken); + return; + } + var serializedValue = value as byte[] ?? JsonSerializer.SerializeToUtf8Bytes(value); await cache.SetAsync(key, serializedValue, options, cancellationToken); } public async Task GetAsync(string key, CancellationToken cancellationToken = default) { + var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + + if (type.IsPrimitive || type == typeof(string) || type == typeof(DateTime)) + { + var primitiveValue = await cache.GetStringAsync(key, cancellationToken); + + if (string.IsNullOrEmpty(primitiveValue)) + return default; + + if (type == typeof(string)) + return (T?)(object?)primitiveValue; + + var tryParseMethod = type.GetMethod("TryParse", [typeof(string), type.MakeByRefType()]) + ?? throw new NotSupportedException($"Type {type.Name} does not support TryParse."); + + var parameters = new[] { primitiveValue, Activator.CreateInstance(type) }; + var success = (bool)tryParseMethod.Invoke(null, parameters)!; + + if (success) + return (T)parameters[1]!; + + return default; + } + var cachedValue = await cache.GetAsync(key, cancellationToken); return cachedValue == null ? default : JsonSerializer.Deserialize(cachedValue); } diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs index 6c5b8f6..4c7f7fd 100644 --- a/Endpoint/Common/Services/Security/MemoryCacheService.cs +++ b/Endpoint/Common/Services/Security/MemoryCacheService.cs @@ -17,17 +17,41 @@ public class MemoryCacheService(IMemoryCache cache) : ICacheService SlidingExpiration = slidingExpiration }; + var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + if (type.IsPrimitive || type == typeof(string) || type == typeof(DateTime)) + { + cache.Set(key, value?.ToString() ?? string.Empty, options); + return Task.CompletedTask; + } + cache.Set(key, value as byte[] ?? JsonSerializer.SerializeToUtf8Bytes(value), options); return Task.CompletedTask; } public Task GetAsync(string key, CancellationToken cancellationToken = default) { - return Task.FromResult( - cache.TryGetValue(key, out byte[]? value) ? - JsonSerializer.Deserialize(value) : - default - ); + var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + + if (!type.IsPrimitive && type != typeof(string) && type != typeof(DateTime)) + return Task.FromResult( + cache.TryGetValue(key, out byte[]? value) ? JsonSerializer.Deserialize(value) : default + ); + + var primitiveValue = cache.Get(key); + + if (string.IsNullOrEmpty(primitiveValue?.ToString())) + return Task.FromResult(default); + + if (type == typeof(string)) + return Task.FromResult((T?)primitiveValue); + + var tryParseMethod = type.GetMethod("TryParse", [typeof(string), type.MakeByRefType()]) + ?? throw new NotSupportedException($"Type {type.Name} does not support TryParse."); + + var parameters = new[] { primitiveValue, Activator.CreateInstance(type) }; + var success = (bool)tryParseMethod.Invoke(null, parameters)!; + + return success ? Task.FromResult((T?)parameters[1]) : Task.FromResult(default); } public Task RemoveAsync(string key, CancellationToken cancellationToken = default) -- 2.43.0 From 3279ef594bed6ec5d7d866ca03296d57a91a0f33 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 31 Oct 2024 04:06:58 +0300 Subject: [PATCH 341/474] fix: change current culture to russian for import --- Endpoint/Controllers/V1/ImportController.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/V1/ImportController.cs b/Endpoint/Controllers/V1/ImportController.cs index c674d6b..4800c1a 100644 --- a/Endpoint/Controllers/V1/ImportController.cs +++ b/Endpoint/Controllers/V1/ImportController.cs @@ -79,6 +79,8 @@ public class ImportController(IMediator mediator, IOptionsSnapshot new { s.DayOfWeek, @@ -100,7 +102,7 @@ public class ImportController(IMediator mediator, IOptionsSnapshot x.Group)), - + IsExclude = g.First().IsExcludedWeeks, g.First().TypeOfOccupations, g.First().Weeks @@ -109,7 +111,7 @@ public class ImportController(IMediator mediator, IOptionsSnapshot Date: Thu, 31 Oct 2024 04:07:35 +0300 Subject: [PATCH 342/474] feat: return security exception --- .../Core/Middleware/CustomExceptionHandlerMiddleware.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index ef6756c..d5aff77 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -5,6 +5,7 @@ using Mirea.Api.DataAccess.Application.Common.Exceptions; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Exceptions; using System; +using System.Security; using System.Text.Json; using System.Threading.Tasks; @@ -44,6 +45,9 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger Date: Thu, 31 Oct 2024 04:12:02 +0300 Subject: [PATCH 343/474] refactor: transfer logic All logic related to token manipulation has been transferred to the AuthService. Also added TOTP 2FA and rethought the logic of logging into the application --- ApiDto/Responses/AuthenticationStep.cs | 17 ++ Endpoint/Configuration/Model/Admin.cs | 4 +- Endpoint/Controllers/V1/AuthController.cs | 135 +++---------- Security/Common/CookieNames.cs | 8 + Security/Common/Domain/AuthToken.cs | 22 +- .../Common/Domain/CookieOptionsParameters.cs | 29 +++ Security/Common/Domain/FirstAuthToken.cs | 22 ++ Security/Common/Domain/PreAuthToken.cs | 10 - Security/Common/Domain/RequestContextInfo.cs | 38 ++++ Security/Common/Domain/User.cs | 18 ++ Security/Common/Dto/Requests/TokenRequest.cs | 8 - .../Common/Dto/Responses/AuthTokenResponse.cs | 11 - .../Dto/Responses/PreAuthTokenResponse.cs | 9 - Security/DependencyInjection.cs | 21 +- Security/Properties/launchSettings.json | 12 ++ Security/Security.csproj | 10 +- Security/Services/AuthService.cs | 191 +++++++++++++----- Security/Services/PreAuthService.cs | 64 ------ Security/Services/TotpService.cs | 20 ++ 19 files changed, 371 insertions(+), 278 deletions(-) create mode 100644 ApiDto/Responses/AuthenticationStep.cs create mode 100644 Security/Common/CookieNames.cs create mode 100644 Security/Common/Domain/CookieOptionsParameters.cs create mode 100644 Security/Common/Domain/FirstAuthToken.cs delete mode 100644 Security/Common/Domain/PreAuthToken.cs create mode 100644 Security/Common/Domain/RequestContextInfo.cs create mode 100644 Security/Common/Domain/User.cs delete mode 100644 Security/Common/Dto/Requests/TokenRequest.cs delete mode 100644 Security/Common/Dto/Responses/AuthTokenResponse.cs delete mode 100644 Security/Common/Dto/Responses/PreAuthTokenResponse.cs create mode 100644 Security/Properties/launchSettings.json delete mode 100644 Security/Services/PreAuthService.cs create mode 100644 Security/Services/TotpService.cs diff --git a/ApiDto/Responses/AuthenticationStep.cs b/ApiDto/Responses/AuthenticationStep.cs new file mode 100644 index 0000000..8cab1ab --- /dev/null +++ b/ApiDto/Responses/AuthenticationStep.cs @@ -0,0 +1,17 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents the steps required after a login attempt. +/// +public enum AuthenticationStep +{ + /// + /// No additional steps required; the user is successfully logged in. + /// + None, + + /// + /// TOTP (Time-based One-Time Password) is required for additional verification. + /// + TotpRequired, +} diff --git a/Endpoint/Configuration/Model/Admin.cs b/Endpoint/Configuration/Model/Admin.cs index 73db8d7..377b179 100644 --- a/Endpoint/Configuration/Model/Admin.cs +++ b/Endpoint/Configuration/Model/Admin.cs @@ -1,4 +1,5 @@ using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Security.Common.Domain; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; @@ -16,7 +17,8 @@ public class Admin : ISaveSettings public required string Email { get; set; } public required string PasswordHash { get; set; } public required string Salt { get; set; } - + public SecondFactor SecondFactor { get; set; } = SecondFactor.None; + public string? Secret { get; set; } public void SaveSetting() { diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 2954985..737d9c5 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -2,109 +2,64 @@ 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.Security.Common.Dto.Requests; +using Mirea.Api.Security.Common.Domain; using Mirea.Api.Security.Services; using System; -using System.Security; using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] -public class AuthController(IOptionsSnapshot user, AuthService auth, PasswordHashService passwordService) : BaseController, IActionFilter +public class AuthController(IOptionsSnapshot user, AuthService auth, PasswordHashService passwordService) : BaseController { - private string Fingerprint { get; set; } = string.Empty; - private string Ip { get; set; } = string.Empty; - private string UserAgent { get; set; } = string.Empty; - private string RefreshToken { get; set; } = string.Empty; - - private void SetCookie(string name, string value, DateTimeOffset? expires = null) - { - var cookieOptions = new CookieOptions + private CookieOptionsParameters GetCookieParams() => + new() { - Expires = expires, - Path = UrlHelper.GetSubPathWithoutFirstApiName + "api", Domain = HttpContext.GetCurrentDomain(), - HttpOnly = true, -#if !DEBUG - Secure = true -#endif + Path = UrlHelper.GetSubPathWithoutFirstApiName + "api" }; - Response.Cookies.Append(name, value, cookieOptions); - } - - private void SetRefreshToken(string value, DateTimeOffset? expires = null) - { - SetCookie("refresh_token", value, expires); - SetCookie("user_key", Fingerprint); - } - - 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); - } - - [ApiExplorerSettings(IgnoreApi = true)] - public void OnActionExecuting(ActionExecutingContext context) - { - Ip = HttpContext.Connection.RemoteIpAddress?.ToString()!; - UserAgent = Request.Headers.UserAgent.ToString(); - Fingerprint = Request.Cookies["user_key"] ?? string.Empty; - RefreshToken = Request.Cookies["refresh_token"] ?? string.Empty; - - if (!string.IsNullOrWhiteSpace(Fingerprint)) return; - - Fingerprint = Guid.NewGuid().ToString().Replace("-", ""); - } - - [ApiExplorerSettings(IgnoreApi = true)] - public void OnActionExecuted(ActionExecutedContext context) { } - - /// - /// Handles user authentication by verifying the username/email and password, - /// then generating and returning an authentication token if successful. - /// - /// The login request containing the username/email and password. - /// User's AuthRoles. [HttpPost("Login")] [BadRequestResponse] - public async Task> Login([FromBody] LoginRequest request) + public async Task> 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)) + !userEntity.Email.Equals(request.Username, StringComparison.OrdinalIgnoreCase)) return BadRequest("Invalid username/email or password"); - var token = await auth.GenerateAuthTokensAsync(new TokenRequest - { - Fingerprint = Fingerprint, - Ip = Ip, - UserAgent = UserAgent - }, "1"); + var tokenResult = await auth.LoginAsync( + GetCookieParams(), + new User + { + Id = 1, + Username = userEntity.Username, + Email = userEntity.Email, + PasswordHash = userEntity.PasswordHash, + Salt = userEntity.Salt, + SecondFactor = userEntity.SecondFactor, + SecondFactorToken = userEntity.Secret + }, + HttpContext, request.Password); - SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); - SetAuthToken(token.AccessToken, token.AccessExpiresIn); + return Ok(tokenResult ? AuthenticationStep.None : AuthenticationStep.TotpRequired); + } - return Ok(AuthRoles.Admin); + [HttpGet("Login")] + [BadRequestResponse] + public async Task> Login([FromQuery] string code) + { + var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, code); + return Ok(tokenResult ? AuthenticationStep.None : AuthenticationStep.TotpRequired); } /// @@ -116,30 +71,8 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> ReLogin() { - if (string.IsNullOrEmpty(RefreshToken)) - return Unauthorized(); - - try - { - var token = await auth.RefreshTokenAsync( - new TokenRequest - { - Ip = Ip, - UserAgent = UserAgent, - Fingerprint = Fingerprint - }, - RefreshToken - ); - - SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); - SetAuthToken(token.AccessToken, token.AccessExpiresIn); - - return Ok(AuthRoles.Admin); - } - catch (SecurityException) - { - return Forbid(); - } + await auth.RefreshTokenAsync(GetCookieParams(), HttpContext); + return Ok(AuthRoles.Admin); } /// @@ -151,11 +84,7 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [Authorize] public async Task Logout() { - SetRefreshToken("", DateTimeOffset.MinValue); - SetFirstToken("", DateTimeOffset.MinValue); - SetAuthToken("", DateTimeOffset.MinValue); - - await auth.LogoutAsync(Fingerprint); + await auth.LogoutAsync(GetCookieParams(), HttpContext); return Ok(); } diff --git a/Security/Common/CookieNames.cs b/Security/Common/CookieNames.cs new file mode 100644 index 0000000..7edb38b --- /dev/null +++ b/Security/Common/CookieNames.cs @@ -0,0 +1,8 @@ +namespace Mirea.Api.Security.Common; + +public class CookieNames +{ + public const string AccessToken = "access_token"; + public const string RefreshToken = "refresh_token"; + public const string FingerprintToken = "fingerprint"; +} \ No newline at end of file diff --git a/Security/Common/Domain/AuthToken.cs b/Security/Common/Domain/AuthToken.cs index 4572e62..ff78786 100644 --- a/Security/Common/Domain/AuthToken.cs +++ b/Security/Common/Domain/AuthToken.cs @@ -2,11 +2,25 @@ namespace Mirea.Api.Security.Common.Domain; -public class AuthToken +internal class AuthToken { - public required string RefreshToken { get; set; } - public required string UserAgent { get; set; } - public required string Ip { get; set; } + public AuthToken(RequestContextInfo context) + { + UserAgent = context.UserAgent; + Ip = context.Ip; + Fingerprint = context.Fingerprint; + RefreshToken = context.RefreshToken; + } + + public AuthToken() + { + } + + public string UserAgent { get; set; } = null!; + public string Ip { get; set; } = null!; + public string Fingerprint { get; set; } = null!; + public string RefreshToken { get; set; } = null!; + public required string UserId { get; set; } public required string AccessToken { get; set; } public DateTime CreatedAt { get; set; } diff --git a/Security/Common/Domain/CookieOptionsParameters.cs b/Security/Common/Domain/CookieOptionsParameters.cs new file mode 100644 index 0000000..5845b53 --- /dev/null +++ b/Security/Common/Domain/CookieOptionsParameters.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http; +using System; + +namespace Mirea.Api.Security.Common.Domain; + +public class CookieOptionsParameters +{ + public required string Domain { get; set; } + public required string Path { get; set; } + + internal void SetCookie(HttpContext context, string name, string value, DateTimeOffset? expires = null) + { + var cookieOptions = new CookieOptions + { + Expires = expires, + Path = Path, + Domain = Domain, + HttpOnly = true, +#if !DEBUG + Secure = true +#endif + }; + + context.Response.Cookies.Append(name, value, cookieOptions); + } + + internal void DropCookie(HttpContext context, string name) => + SetCookie(context, name, "", DateTimeOffset.MinValue); +} diff --git a/Security/Common/Domain/FirstAuthToken.cs b/Security/Common/Domain/FirstAuthToken.cs new file mode 100644 index 0000000..a3a37e5 --- /dev/null +++ b/Security/Common/Domain/FirstAuthToken.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.Security.Common.Domain; + +internal class FirstAuthToken +{ + public FirstAuthToken(RequestContextInfo context) + { + UserAgent = context.UserAgent; + Ip = context.Ip; + Fingerprint = context.Fingerprint; + } + + public FirstAuthToken() + { + } + + public string UserAgent { get; set; } = null!; + public string Ip { get; set; } = null!; + public string Fingerprint { get; set; } = null!; + public required string UserId { get; set; } + public required SecondFactor SecondFactor { get; set; } + public string? Secret { get; set; } +} \ No newline at end of file diff --git a/Security/Common/Domain/PreAuthToken.cs b/Security/Common/Domain/PreAuthToken.cs deleted file mode 100644 index f1f9684..0000000 --- a/Security/Common/Domain/PreAuthToken.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Mirea.Api.Security.Common.Domain; - -public class PreAuthToken -{ - public required string Fingerprint { get; set; } - public required string UserAgent { get; set; } - public required string UserId { get; set; } - public required string Ip { get; set; } - public required string Token { get; set; } -} \ No newline at end of file diff --git a/Security/Common/Domain/RequestContextInfo.cs b/Security/Common/Domain/RequestContextInfo.cs new file mode 100644 index 0000000..7eee08d --- /dev/null +++ b/Security/Common/Domain/RequestContextInfo.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Http; +using Mirea.Api.Security.Services; +using System; +using System.Security; + +namespace Mirea.Api.Security.Common.Domain; + +internal class RequestContextInfo +{ + public RequestContextInfo(HttpContext context, CookieOptionsParameters cookieOptions) + { + var ipEntity = context.Connection.RemoteIpAddress; + + if (string.IsNullOrEmpty(ipEntity?.ToString())) + throw new SecurityException("Ip is required for authorization."); + + var ip = ipEntity.MapToIPv4().ToString(); + + var userAgent = context.Request.Headers.UserAgent.ToString(); + var fingerprint = context.Request.Cookies[CookieNames.FingerprintToken]; + + if (string.IsNullOrEmpty(fingerprint)) + { + fingerprint = Guid.NewGuid().ToString().Replace("-", "") + GeneratorKey.GenerateString(32); + cookieOptions.SetCookie(context, CookieNames.FingerprintToken, fingerprint); + } + + UserAgent = userAgent; + Fingerprint = fingerprint; + Ip = ip; + RefreshToken = context.Request.Cookies["refresh_token"] ?? string.Empty; + } + + public string UserAgent { get; private set; } + public string Ip { get; private set; } + public string Fingerprint { get; private set; } + public string RefreshToken { get; private set; } +} \ No newline at end of file diff --git a/Security/Common/Domain/User.cs b/Security/Common/Domain/User.cs new file mode 100644 index 0000000..3465de2 --- /dev/null +++ b/Security/Common/Domain/User.cs @@ -0,0 +1,18 @@ +namespace Mirea.Api.Security.Common.Domain; + +public enum SecondFactor +{ + None, + Totp +} + +public class User +{ + public required int Id { get; set; } + public required string Username { get; set; } + public required string Email { get; set; } + public required string PasswordHash { get; set; } + public required string Salt { get; set; } + public required SecondFactor SecondFactor { get; set; } + public string? SecondFactorToken { get; set; } +} \ No newline at end of file diff --git a/Security/Common/Dto/Requests/TokenRequest.cs b/Security/Common/Dto/Requests/TokenRequest.cs deleted file mode 100644 index 8be8038..0000000 --- a/Security/Common/Dto/Requests/TokenRequest.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Mirea.Api.Security.Common.Dto.Requests; - -public class TokenRequest -{ - public required string Fingerprint { get; set; } - public required string UserAgent { get; set; } - public required string Ip { get; set; } -} \ No newline at end of file diff --git a/Security/Common/Dto/Responses/AuthTokenResponse.cs b/Security/Common/Dto/Responses/AuthTokenResponse.cs deleted file mode 100644 index 16aed38..0000000 --- a/Security/Common/Dto/Responses/AuthTokenResponse.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Mirea.Api.Security.Common.Dto.Responses; - -public class AuthTokenResponse -{ - public required string AccessToken { get; set; } - public DateTime AccessExpiresIn { get; set; } - public required string RefreshToken { get; set; } - public DateTime RefreshExpiresIn { get; set; } -} \ No newline at end of file diff --git a/Security/Common/Dto/Responses/PreAuthTokenResponse.cs b/Security/Common/Dto/Responses/PreAuthTokenResponse.cs deleted file mode 100644 index 9a7238f..0000000 --- a/Security/Common/Dto/Responses/PreAuthTokenResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Mirea.Api.Security.Common.Dto.Responses; - -public class PreAuthTokenResponse -{ - public required string Token { get; set; } - public DateTime ExpiresIn { get; set; } -} \ No newline at end of file diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs index ed16c5e..0549ff4 100644 --- a/Security/DependencyInjection.cs +++ b/Security/DependencyInjection.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Mirea.Api.Security.Common.Interfaces; using Mirea.Api.Security.Services; using System; @@ -26,29 +27,21 @@ public static class DependencyInjection Secret = configuration["SECURITY_HASH_TOKEN"] }); - var lifeTimePreAuthToken = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_1_FA"]!)); - - services.AddSingleton(provider => - { - var cache = provider.GetRequiredService(); - - return new PreAuthService(cache) - { - Lifetime = lifeTimePreAuthToken - }; - }); - var lifeTimeRefreshToken = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_RT"]!)); + var lifeTimeFirstAuthToken = TimeSpan.FromMinutes(int.Parse(configuration["SECURITY_LIFE_TIME_1_FA"]!)); services.AddSingleton(provider => { var cacheService = provider.GetRequiredService(); var accessTokenService = provider.GetRequiredService(); var revokedTokenService = provider.GetRequiredService(); + var logger = provider.GetRequiredService>(); + var passwordService = provider.GetRequiredService(); - return new AuthService(cacheService, accessTokenService, revokedTokenService) + return new AuthService(cacheService, accessTokenService, revokedTokenService, logger, passwordService) { - Lifetime = lifeTimeRefreshToken + Lifetime = lifeTimeRefreshToken, + LifetimeFirstAuth = lifeTimeFirstAuthToken }; }); diff --git a/Security/Properties/launchSettings.json b/Security/Properties/launchSettings.json new file mode 100644 index 0000000..faa4f46 --- /dev/null +++ b/Security/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Security": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:58871;http://localhost:58872" + } + } +} \ No newline at end of file diff --git a/Security/Security.csproj b/Security/Security.csproj index f8dcc16..8e35bb3 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -1,21 +1,23 @@ - + net8.0 disable enable Winsomnia - 1.0.0 - 1.0.3.0 - 1.0.3.0 + 1.1.0 + 1.1.3.0 + 1.1.3.0 Mirea.Api.Security $(AssemblyName) + Library + diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 5b293b3..00fe9ca 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -1,6 +1,7 @@ -using Mirea.Api.Security.Common.Domain; -using Mirea.Api.Security.Common.Dto.Requests; -using Mirea.Api.Security.Common.Dto.Responses; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Mirea.Api.Security.Common; +using Mirea.Api.Security.Common.Domain; using Mirea.Api.Security.Common.Interfaces; using System; using System.Security; @@ -10,9 +11,10 @@ using System.Threading.Tasks; namespace Mirea.Api.Security.Services; -public class AuthService(ICacheService cache, IAccessToken accessTokenService, IRevokedToken revokedToken) +public class AuthService(ICacheService cache, IAccessToken accessTokenService, IRevokedToken revokedToken, ILogger logger, PasswordHashService passwordService) { public TimeSpan Lifetime { private get; init; } + public TimeSpan LifetimeFirstAuth { private get; init; } private static string GenerateRefreshToken() => Guid.NewGuid().ToString().Replace("-", "") + GeneratorKey.GenerateString(32); @@ -20,10 +22,11 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I accessTokenService.GenerateToken(userId); private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token"; + private static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; - private Task SetAuthTokenDataToCache(string fingerprint, AuthToken data, CancellationToken cancellation) => + private Task SetAuthTokenDataToCache(AuthToken data, CancellationToken cancellation) => cache.SetAsync( - GetAuthCacheKey(fingerprint), + GetAuthCacheKey(data.Fingerprint), JsonSerializer.SerializeToUtf8Bytes(data), slidingExpiration: Lifetime, cancellationToken: cancellation); @@ -31,51 +34,141 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I private Task RevokeAccessToken(string token) => revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); - public async Task GenerateAuthTokensAsync(TokenRequest request, string userId, CancellationToken cancellation = default) + private async Task VerifyUserOrThrowError(RequestContextInfo requestContext, User user, string password, + CancellationToken cancellation = default) + { + if (passwordService.VerifyPassword(password, user.Salt, user.PasswordHash)) + return; + + var failedLoginCacheName = $"{requestContext.Fingerprint}_login_failed"; + var countFailedLogin = await cache.GetAsync(failedLoginCacheName, cancellation) ?? 1; + var cacheSaveTime = TimeSpan.FromHours(1); + + await cache.SetAsync(failedLoginCacheName, countFailedLogin + 1, slidingExpiration: cacheSaveTime, cancellationToken: cancellation); + + if (countFailedLogin > 5) + { + logger.LogWarning( + "Multiple failed login attempts detected for user ID {UserId} from IP {UserIp}. Attempt: #{AttemptNumber}. Possible account compromise.", + user.Id, + requestContext.Ip, + countFailedLogin); + + throw new SecurityException($"There are many incorrect attempts to access the account. Try again after {(int)cacheSaveTime.TotalMinutes} minutes."); + } + + logger.LogInformation( + "Failed login attempt for user ID {UserId} from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint} Attempt: #{AttemptNumber}.", + user.Id, + requestContext.Ip, + requestContext.UserAgent, + requestContext.Fingerprint, + countFailedLogin); + + throw new SecurityException("Invalid username/email or password"); + } + + private async Task GenerateAuthTokensAsync(CookieOptionsParameters cookieOptions, HttpContext context, RequestContextInfo requestContext, string userId, CancellationToken cancellation = default) { var refreshToken = GenerateRefreshToken(); var (token, expireIn) = GenerateAccessToken(userId); - var authTokenStruct = new AuthToken + var authToken = new AuthToken(requestContext) { CreatedAt = DateTime.UtcNow, - Ip = request.Ip, - RefreshToken = refreshToken, - UserAgent = request.UserAgent, UserId = userId, - AccessToken = token - }; - - await SetAuthTokenDataToCache(request.Fingerprint, authTokenStruct, cancellation); - - return new AuthTokenResponse - { AccessToken = token, - AccessExpiresIn = expireIn, - RefreshToken = authTokenStruct.RefreshToken, - RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime), }; + + await SetAuthTokenDataToCache(authToken, cancellation); + cookieOptions.SetCookie(context, CookieNames.AccessToken, token, expireIn); + cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); + + logger.LogInformation( + "Successful login attempt for user ID {UserId} from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint}.", + authToken.UserId, + authToken.Ip, + authToken.UserAgent, + authToken.Fingerprint); } - public async Task GenerateAuthTokensWithPreAuthAsync(TokenRequest request, string preAuthToken, - CancellationToken cancellation = default) => - await GenerateAuthTokensAsync(request, - await new PreAuthService(cache).MatchToken(request, preAuthToken, cancellation), - cancellation); - - public async Task RefreshTokenAsync(TokenRequest request, string refreshToken, CancellationToken cancellation = default) + public async Task LoginAsync(CookieOptionsParameters cookieOptions, HttpContext context, string code, CancellationToken cancellation = default) { - var authToken = await cache.GetAsync(GetAuthCacheKey(request.Fingerprint), cancellation) - ?? throw new SecurityException(request.Fingerprint); + var requestContext = new RequestContextInfo(context, cookieOptions); - if (authToken.RefreshToken != refreshToken || - authToken.UserAgent != request.UserAgent && - authToken.Ip != request.Ip) + var firstTokenAuth = await cache.GetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellation) + ?? throw new SecurityException("The session time has expired"); + + switch (firstTokenAuth.SecondFactor) { - await cache.RemoveAsync(GetAuthCacheKey(request.Fingerprint), cancellation); - await RevokeAccessToken(authToken.AccessToken); + case SecondFactor.Totp: + { + if (string.IsNullOrEmpty(firstTokenAuth.Secret)) + throw new InvalidOperationException("The user's secrets for data processing were not transferred."); - throw new SecurityException(request.Fingerprint); + var totp = new TotpService(firstTokenAuth.Secret); + + if (!totp.VerifyToken(code)) + throw new SecurityException("The entered code is incorrect."); + } + break; + default: + throw new InvalidOperationException("The system failed to understand the authorization method."); + } + + await GenerateAuthTokensAsync(cookieOptions, context, requestContext, firstTokenAuth.UserId, cancellation); + return true; + } + + public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, CancellationToken cancellation = default) + { + var requestContext = new RequestContextInfo(context, cookieOptions); + + await VerifyUserOrThrowError(requestContext, user, password, cancellation); + + if (user.SecondFactor == SecondFactor.None) + { + await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id.ToString(), cancellation); + return true; + } + + var firstAuthToken = new FirstAuthToken(requestContext) + { + UserId = user.Id.ToString(), + Secret = user.SecondFactorToken, + SecondFactor = user.SecondFactor + }; + + await cache.SetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), firstAuthToken, absoluteExpirationRelativeToNow: LifetimeFirstAuth, cancellationToken: cancellation); + + return false; + } + + public async Task RefreshTokenAsync(CookieOptionsParameters cookieOptions, HttpContext context, CancellationToken cancellation = default) + { + var requestContext = new RequestContextInfo(context, cookieOptions); + var authToken = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation) + ?? throw new SecurityException("The session time has expired"); + + if (authToken.RefreshToken != requestContext.RefreshToken || + authToken.UserAgent != requestContext.UserAgent && + authToken.Ip != requestContext.Ip) + { + await RevokeAccessToken(authToken.AccessToken); + await cache.RemoveAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation); + cookieOptions.DropCookie(context, CookieNames.AccessToken); + cookieOptions.DropCookie(context, CookieNames.RefreshToken); + + logger.LogWarning("Token validation failed for user ID {UserId}. Invalid token used from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint}. Possible account compromise. Reason: {Reason}.", + authToken.UserId, + authToken.Ip, + authToken.UserAgent, + authToken.Fingerprint, + authToken.RefreshToken != requestContext.RefreshToken ? + $"Cached refresh token '{authToken.RefreshToken}' does not match the provided refresh token '{requestContext.RefreshToken}'" : + $"User-Agent '{authToken.UserAgent}' and IP '{authToken.Ip}' in cache do not match the provided User-Agent '{requestContext.UserAgent}' and IP '{requestContext.Ip}'"); + + throw new SecurityException("The session time has expired"); } var (token, expireIn) = GenerateAccessToken(authToken.UserId); @@ -86,24 +179,22 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.AccessToken = token; authToken.RefreshToken = newRefreshToken; - await SetAuthTokenDataToCache(request.Fingerprint, authToken, cancellation); - - return new AuthTokenResponse - { - AccessToken = token, - AccessExpiresIn = expireIn, - RefreshToken = newRefreshToken, - RefreshExpiresIn = DateTime.UtcNow.Add(Lifetime) - }; + await SetAuthTokenDataToCache(authToken, cancellation); + cookieOptions.SetCookie(context, CookieNames.AccessToken, token, expireIn); + cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); } - public async Task LogoutAsync(string fingerprint, CancellationToken cancellation = default) + public async Task LogoutAsync(CookieOptionsParameters cookieOptions, HttpContext context, CancellationToken cancellation = default) { - var authTokenStruct = await cache.GetAsync(GetAuthCacheKey(fingerprint), cancellation); - if (authTokenStruct == null) return; + var requestContext = new RequestContextInfo(context, cookieOptions); + var authTokenStruct = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation); + + if (authTokenStruct == null) + return; await RevokeAccessToken(authTokenStruct.AccessToken); - - await cache.RemoveAsync(fingerprint, cancellation); + await cache.RemoveAsync(requestContext.Fingerprint, cancellation); + cookieOptions.DropCookie(context, CookieNames.AccessToken); + cookieOptions.DropCookie(context, CookieNames.RefreshToken); } } \ No newline at end of file diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs deleted file mode 100644 index 948b997..0000000 --- a/Security/Services/PreAuthService.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Mirea.Api.Security.Common.Domain; -using Mirea.Api.Security.Common.Dto.Requests; -using Mirea.Api.Security.Common.Dto.Responses; -using Mirea.Api.Security.Common.Interfaces; -using System; -using System.Security; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Mirea.Api.Security.Services; - -public class PreAuthService(ICacheService cache) -{ - public TimeSpan Lifetime { private get; init; } - - private static string GeneratePreAuthToken() => Guid.NewGuid().ToString().Replace("-", "") + - GeneratorKey.GenerateString(16); - - private static string GetPreAuthCacheKey(string fingerprint) => $"{fingerprint}_pre_auth_token"; - - public async Task GeneratePreAuthTokenAsync(TokenRequest request, string userId, CancellationToken cancellation = default) - { - var preAuthToken = GeneratePreAuthToken(); - - var preAuthTokenStruct = new PreAuthToken - { - Fingerprint = request.Fingerprint, - UserId = userId, - UserAgent = request.UserAgent, - Token = preAuthToken, - Ip = request.Ip - }; - - await cache.SetAsync( - GetPreAuthCacheKey(request.Fingerprint), - JsonSerializer.SerializeToUtf8Bytes(preAuthTokenStruct), - absoluteExpirationRelativeToNow: Lifetime, - cancellationToken: cancellation); - - return new PreAuthTokenResponse - { - Token = preAuthToken, - ExpiresIn = DateTime.UtcNow.Add(Lifetime) - }; - } - public async Task MatchToken(TokenRequest request, string preAuthToken, CancellationToken cancellation = default) - { - var preAuthTokenStruct = await cache.GetAsync(GetPreAuthCacheKey(request.Fingerprint), cancellation) - ?? throw new SecurityException($"The token was not found using fingerprint \"{request.Fingerprint}\""); - - if (preAuthTokenStruct == null || - preAuthTokenStruct.Token != preAuthToken || - (preAuthTokenStruct.UserAgent != request.UserAgent && - preAuthTokenStruct.Ip != request.Ip)) - { - throw new SecurityException("It was not possible to verify the authenticity of the token"); - } - - await cache.RemoveAsync(GetPreAuthCacheKey(request.Fingerprint), cancellation); - - return preAuthTokenStruct.UserId; - } -} \ No newline at end of file diff --git a/Security/Services/TotpService.cs b/Security/Services/TotpService.cs new file mode 100644 index 0000000..49cfb73 --- /dev/null +++ b/Security/Services/TotpService.cs @@ -0,0 +1,20 @@ +using OtpNet; + +namespace Mirea.Api.Security.Services; + +public class TotpService +{ + private readonly Totp _totp; + + public TotpService(string secret) + { + var secretBytes = Base32Encoding.ToBytes(secret); + _totp = new Totp(secretBytes); + } + + public string GenerateToken() => + _totp.ComputeTotp(); + + public bool VerifyToken(string token) => + _totp.VerifyTotp(token, out _, new VerificationWindow(2, 2)); +} \ No newline at end of file -- 2.43.0 From a0ff624481a2ea68a0bbb03e9c8c36265848ad37 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 31 Oct 2024 04:12:22 +0300 Subject: [PATCH 344/474] fix: add forgotten changes --- Security/Services/AuthService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 00fe9ca..afc03e8 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -76,8 +76,9 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I var authToken = new AuthToken(requestContext) { CreatedAt = DateTime.UtcNow, + RefreshToken = refreshToken, UserId = userId, - AccessToken = token, + AccessToken = token }; await SetAuthTokenDataToCache(authToken, cancellation); -- 2.43.0 From eb272baa385f6d6536f18c514ccc0ebb0400dbb4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 31 Oct 2024 04:23:43 +0300 Subject: [PATCH 345/474] fix: change cookie name --- .../Core/Middleware/CookieAuthorizationMiddleware.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs index 1b97741..1faad1a 100644 --- a/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs @@ -1,15 +1,15 @@ using Microsoft.AspNetCore.Http; +using Mirea.Api.Security.Common; 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]; + if (context.Request.Cookies.ContainsKey(CookieNames.AccessToken)) + context.Request.Headers.Authorization = "Bearer " + context.Request.Cookies[CookieNames.AccessToken]; await next(context); } -- 2.43.0 From 23f74b3bdf21101441396b948fa1786c067ba8b8 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 00:50:10 +0300 Subject: [PATCH 346/474] refactor: change name enums --- .../TwoFactorAuthentication.cs} | 4 ++-- Endpoint/Configuration/Model/Admin.cs | 2 +- Endpoint/Controllers/V1/AuthController.cs | 11 +++++------ Security/Common/Domain/FirstAuthToken.cs | 2 +- Security/Common/Domain/User.cs | 4 ++-- 5 files changed, 11 insertions(+), 12 deletions(-) rename ApiDto/{Responses/AuthenticationStep.cs => Common/TwoFactorAuthentication.cs} (83%) diff --git a/ApiDto/Responses/AuthenticationStep.cs b/ApiDto/Common/TwoFactorAuthentication.cs similarity index 83% rename from ApiDto/Responses/AuthenticationStep.cs rename to ApiDto/Common/TwoFactorAuthentication.cs index 8cab1ab..52dd09c 100644 --- a/ApiDto/Responses/AuthenticationStep.cs +++ b/ApiDto/Common/TwoFactorAuthentication.cs @@ -1,9 +1,9 @@ -namespace Mirea.Api.Dto.Responses; +namespace Mirea.Api.Dto.Common; /// /// Represents the steps required after a login attempt. /// -public enum AuthenticationStep +public enum TwoFactorAuthentication { /// /// No additional steps required; the user is successfully logged in. diff --git a/Endpoint/Configuration/Model/Admin.cs b/Endpoint/Configuration/Model/Admin.cs index 377b179..d31d7d0 100644 --- a/Endpoint/Configuration/Model/Admin.cs +++ b/Endpoint/Configuration/Model/Admin.cs @@ -17,7 +17,7 @@ public class Admin : ISaveSettings public required string Email { get; set; } public required string PasswordHash { get; set; } public required string Salt { get; set; } - public SecondFactor SecondFactor { get; set; } = SecondFactor.None; + public TwoFactorAuthenticator TwoFactorAuthenticator { get; set; } = TwoFactorAuthenticator.None; public string? Secret { get; set; } public void SaveSetting() diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 737d9c5..e689d8a 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Mvc; 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; @@ -29,7 +28,7 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [HttpPost("Login")] [BadRequestResponse] - public async Task> Login([FromBody] LoginRequest request) + public async Task> Login([FromBody] LoginRequest request) { var userEntity = user.Value; @@ -46,20 +45,20 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass Email = userEntity.Email, PasswordHash = userEntity.PasswordHash, Salt = userEntity.Salt, - SecondFactor = userEntity.SecondFactor, + TwoFactorAuthenticator = userEntity.TwoFactorAuthenticator, SecondFactorToken = userEntity.Secret }, HttpContext, request.Password); - return Ok(tokenResult ? AuthenticationStep.None : AuthenticationStep.TotpRequired); + return Ok(tokenResult ? TwoFactorAuthentication.None : TwoFactorAuthentication.TotpRequired); } [HttpGet("Login")] [BadRequestResponse] - public async Task> Login([FromQuery] string code) + public async Task> Login([FromQuery] string code) { var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, code); - return Ok(tokenResult ? AuthenticationStep.None : AuthenticationStep.TotpRequired); + return Ok(tokenResult ? TwoFactorAuthentication.None : TwoFactorAuthentication.TotpRequired); } /// diff --git a/Security/Common/Domain/FirstAuthToken.cs b/Security/Common/Domain/FirstAuthToken.cs index a3a37e5..29f229d 100644 --- a/Security/Common/Domain/FirstAuthToken.cs +++ b/Security/Common/Domain/FirstAuthToken.cs @@ -17,6 +17,6 @@ internal class FirstAuthToken public string Ip { get; set; } = null!; public string Fingerprint { get; set; } = null!; public required string UserId { get; set; } - public required SecondFactor SecondFactor { get; set; } + public required TwoFactorAuthenticator TwoFactorAuthenticator { get; set; } public string? Secret { get; set; } } \ No newline at end of file diff --git a/Security/Common/Domain/User.cs b/Security/Common/Domain/User.cs index 3465de2..bc8bf46 100644 --- a/Security/Common/Domain/User.cs +++ b/Security/Common/Domain/User.cs @@ -1,6 +1,6 @@ namespace Mirea.Api.Security.Common.Domain; -public enum SecondFactor +public enum TwoFactorAuthenticator { None, Totp @@ -13,6 +13,6 @@ public class User public required string Email { get; set; } public required string PasswordHash { get; set; } public required string Salt { get; set; } - public required SecondFactor SecondFactor { get; set; } + public required TwoFactorAuthenticator TwoFactorAuthenticator { get; set; } public string? SecondFactorToken { get; set; } } \ No newline at end of file -- 2.43.0 From 6c9af942f4ef8bdda573f23162b71104561179b4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 00:51:27 +0300 Subject: [PATCH 347/474] refactor: change token to instance token --- Security/Services/AuthService.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index afc03e8..5c71a9f 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -82,7 +82,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I }; await SetAuthTokenDataToCache(authToken, cancellation); - cookieOptions.SetCookie(context, CookieNames.AccessToken, token, expireIn); + cookieOptions.SetCookie(context, CookieNames.AccessToken, authToken.AccessToken, expireIn); cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); logger.LogInformation( @@ -100,9 +100,9 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I var firstTokenAuth = await cache.GetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellation) ?? throw new SecurityException("The session time has expired"); - switch (firstTokenAuth.SecondFactor) + switch (firstTokenAuth.TwoFactorAuthenticator) { - case SecondFactor.Totp: + case TwoFactorAuthenticator.Totp: { if (string.IsNullOrEmpty(firstTokenAuth.Secret)) throw new InvalidOperationException("The user's secrets for data processing were not transferred."); @@ -127,7 +127,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I await VerifyUserOrThrowError(requestContext, user, password, cancellation); - if (user.SecondFactor == SecondFactor.None) + if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) { await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id.ToString(), cancellation); return true; @@ -137,7 +137,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I { UserId = user.Id.ToString(), Secret = user.SecondFactorToken, - SecondFactor = user.SecondFactor + TwoFactorAuthenticator = user.TwoFactorAuthenticator }; await cache.SetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), firstAuthToken, absoluteExpirationRelativeToNow: LifetimeFirstAuth, cancellationToken: cancellation); @@ -181,7 +181,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.RefreshToken = newRefreshToken; await SetAuthTokenDataToCache(authToken, cancellation); - cookieOptions.SetCookie(context, CookieNames.AccessToken, token, expireIn); + cookieOptions.SetCookie(context, CookieNames.AccessToken, authToken.AccessToken, expireIn); cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); } -- 2.43.0 From b3b00aa9e15b9e84bb8073f10b53632d3027ebb7 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 00:59:37 +0300 Subject: [PATCH 348/474] refator: move converter to MapperDto --- .../Common/{Services => MapperDto}/PairPeriodTimeConverter.cs | 2 +- Endpoint/Controllers/V1/ScheduleController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Endpoint/Common/{Services => MapperDto}/PairPeriodTimeConverter.cs (93%) diff --git a/Endpoint/Common/Services/PairPeriodTimeConverter.cs b/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs similarity index 93% rename from Endpoint/Common/Services/PairPeriodTimeConverter.cs rename to Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs index 5a4db03..0f50b43 100644 --- a/Endpoint/Common/Services/PairPeriodTimeConverter.cs +++ b/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace Mirea.Api.Endpoint.Common.Services; +namespace Mirea.Api.Endpoint.Common.MapperDto; public static class PairPeriodTimeConverter { diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 05931b1..55a7f03 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -8,7 +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.Services; +using Mirea.Api.Endpoint.Common.MapperDto; using Mirea.Api.Endpoint.Configuration.Model; using System; using System.Collections.Generic; -- 2.43.0 From 61dc0a8bc419c7363daf936407ffbc2916602189 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 01:05:24 +0300 Subject: [PATCH 349/474] feat: add converter for two factor --- .../TwoFactorAuthenticationConverter.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Endpoint/Common/MapperDto/TwoFactorAuthenticationConverter.cs diff --git a/Endpoint/Common/MapperDto/TwoFactorAuthenticationConverter.cs b/Endpoint/Common/MapperDto/TwoFactorAuthenticationConverter.cs new file mode 100644 index 0000000..4086790 --- /dev/null +++ b/Endpoint/Common/MapperDto/TwoFactorAuthenticationConverter.cs @@ -0,0 +1,24 @@ +using Mirea.Api.Dto.Common; +using Mirea.Api.Security.Common.Domain; +using System; + +namespace Mirea.Api.Endpoint.Common.MapperDto; + +public static class TwoFactorAuthenticationConverter +{ + public static TwoFactorAuthentication ConvertToDto(this TwoFactorAuthenticator authenticator) => + authenticator switch + { + TwoFactorAuthenticator.None => TwoFactorAuthentication.None, + TwoFactorAuthenticator.Totp => TwoFactorAuthentication.TotpRequired, + _ => throw new ArgumentOutOfRangeException(nameof(authenticator), authenticator, null) + }; + + public static TwoFactorAuthenticator ConvertFromDto(this TwoFactorAuthentication authentication) => + authentication switch + { + TwoFactorAuthentication.None => TwoFactorAuthenticator.None, + TwoFactorAuthentication.TotpRequired => TwoFactorAuthenticator.Totp, + _ => throw new ArgumentOutOfRangeException(nameof(authentication), authentication, null) + }; +} \ No newline at end of file -- 2.43.0 From 3811d879ab83e9997b79d1edb15352c341479651 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 01:06:58 +0300 Subject: [PATCH 350/474] refactor: return next step from security --- Endpoint/Controllers/V1/AuthController.cs | 3 ++- Security/Services/AuthService.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index e689d8a..2639165 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -7,6 +7,7 @@ using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Exceptions; +using Mirea.Api.Endpoint.Common.MapperDto; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Security.Common.Domain; @@ -50,7 +51,7 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass }, HttpContext, request.Password); - return Ok(tokenResult ? TwoFactorAuthentication.None : TwoFactorAuthentication.TotpRequired); + return Ok(tokenResult.ConvertToDto()); } [HttpGet("Login")] diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 5c71a9f..6ace1c8 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -121,7 +121,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return true; } - public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, CancellationToken cancellation = default) + public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); @@ -130,7 +130,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) { await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id.ToString(), cancellation); - return true; + return TwoFactorAuthenticator.None; } var firstAuthToken = new FirstAuthToken(requestContext) @@ -142,7 +142,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I await cache.SetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), firstAuthToken, absoluteExpirationRelativeToNow: LifetimeFirstAuth, cancellationToken: cancellation); - return false; + return user.TwoFactorAuthenticator; } public async Task RefreshTokenAsync(CookieOptionsParameters cookieOptions, HttpContext context, CancellationToken cancellation = default) -- 2.43.0 From c5ba1cfcca0ee343b00a52aec717f77aa239034f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 01:09:15 +0300 Subject: [PATCH 351/474] refactor: transfer two factor method to security --- ApiDto/Requests/TwoFactorAuthRequest.cs | 19 +++++++++++++++++++ Endpoint/Controllers/V1/AuthController.cs | 6 +++--- Security/Services/AuthService.cs | 8 +++++--- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 ApiDto/Requests/TwoFactorAuthRequest.cs diff --git a/ApiDto/Requests/TwoFactorAuthRequest.cs b/ApiDto/Requests/TwoFactorAuthRequest.cs new file mode 100644 index 0000000..29811f1 --- /dev/null +++ b/ApiDto/Requests/TwoFactorAuthRequest.cs @@ -0,0 +1,19 @@ +using Mirea.Api.Dto.Common; + +namespace Mirea.Api.Dto.Requests; + +/// +/// Represents a request for verifying two-factor authentication. +/// +public class TwoFactorAuthRequest +{ + /// + /// Gets or sets the two-factor authentication code provided by the user. + /// + public required string Code { get; set; } + + /// + /// Gets or sets the type of the two-factor authentication method used (e.g., TOTP, Email). + /// + public TwoFactorAuthentication Method { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 2639165..b1abe5a 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -54,11 +54,11 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass return Ok(tokenResult.ConvertToDto()); } - [HttpGet("Login")] + [HttpPost("2FA")] [BadRequestResponse] - public async Task> Login([FromQuery] string code) + public async Task> TwoFactorAuth([FromBody] TwoFactorAuthRequest request) { - var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, code); + var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, request.Method.ConvertFromDto(), request.Code); return Ok(tokenResult ? TwoFactorAuthentication.None : TwoFactorAuthentication.TotpRequired); } diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 6ace1c8..8b542d6 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -93,12 +93,14 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.Fingerprint); } - public async Task LoginAsync(CookieOptionsParameters cookieOptions, HttpContext context, string code, CancellationToken cancellation = default) + public async Task LoginAsync(CookieOptionsParameters cookieOptions, HttpContext context, TwoFactorAuthenticator authenticator, string code, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); - var firstTokenAuth = await cache.GetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellation) - ?? throw new SecurityException("The session time has expired"); + var firstTokenAuth = await cache.GetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellation); + + if (firstTokenAuth == null || authenticator != firstTokenAuth.TwoFactorAuthenticator) + throw new SecurityException("The session time has expired"); switch (firstTokenAuth.TwoFactorAuthenticator) { -- 2.43.0 From 1b24954c3e7c99ff0a03207d1407298ca014cd00 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 20:21:46 +0300 Subject: [PATCH 352/474] refactor: change int to string for Id --- Security/Common/Domain/User.cs | 2 +- Security/Services/AuthService.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Security/Common/Domain/User.cs b/Security/Common/Domain/User.cs index bc8bf46..2dafb97 100644 --- a/Security/Common/Domain/User.cs +++ b/Security/Common/Domain/User.cs @@ -8,7 +8,7 @@ public enum TwoFactorAuthenticator public class User { - public required int Id { get; set; } + public required string Id { get; set; } public required string Username { get; set; } public required string Email { get; set; } public required string PasswordHash { get; set; } diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 8b542d6..93bd37a 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -131,13 +131,13 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) { - await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id.ToString(), cancellation); + await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id, cancellation); return TwoFactorAuthenticator.None; } var firstAuthToken = new FirstAuthToken(requestContext) { - UserId = user.Id.ToString(), + UserId = user.Id, Secret = user.SecondFactorToken, TwoFactorAuthenticator = user.TwoFactorAuthenticator }; -- 2.43.0 From 6831d9c7082649fdcf7008aa886b33388b3c85ef Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 22:09:40 +0300 Subject: [PATCH 353/474] fix: return bool instead --- Endpoint/Controllers/V1/AuthController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index b1abe5a..dafe3b7 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -41,7 +41,7 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass GetCookieParams(), new User { - Id = 1, + Id = 1.ToString(), Username = userEntity.Username, Email = userEntity.Email, PasswordHash = userEntity.PasswordHash, @@ -56,10 +56,10 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [HttpPost("2FA")] [BadRequestResponse] - public async Task> TwoFactorAuth([FromBody] TwoFactorAuthRequest request) + public async Task> TwoFactorAuth([FromBody] TwoFactorAuthRequest request) { var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, request.Method.ConvertFromDto(), request.Code); - return Ok(tokenResult ? TwoFactorAuthentication.None : TwoFactorAuthentication.TotpRequired); + return Ok(tokenResult); } /// -- 2.43.0 From db70e4dd96a6937749d51ad2236d84e37aa32192 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 22:10:46 +0300 Subject: [PATCH 354/474] refactor: change log text --- Security/Services/AuthService.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 93bd37a..47f3a46 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -22,7 +22,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I accessTokenService.GenerateToken(userId); private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token"; - private static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; + internal static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; private Task SetAuthTokenDataToCache(AuthToken data, CancellationToken cancellation) => cache.SetAsync( @@ -49,23 +49,23 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I if (countFailedLogin > 5) { logger.LogWarning( - "Multiple failed login attempts detected for user ID {UserId} from IP {UserIp}. Attempt: #{AttemptNumber}. Possible account compromise.", + "Multiple unsuccessful login attempts for user ID {UserId} from IP {UserIp}. Attempt count: {AttemptNumber}.", user.Id, requestContext.Ip, countFailedLogin); - throw new SecurityException($"There are many incorrect attempts to access the account. Try again after {(int)cacheSaveTime.TotalMinutes} minutes."); + throw new SecurityException("Too many unsuccessful login attempts. Please try again later."); } logger.LogInformation( - "Failed login attempt for user ID {UserId} from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint} Attempt: #{AttemptNumber}.", + "Login attempt failed for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.", user.Id, requestContext.Ip, requestContext.UserAgent, requestContext.Fingerprint, countFailedLogin); - throw new SecurityException("Invalid username/email or password"); + throw new SecurityException("Authentication failed. Please check your credentials."); } private async Task GenerateAuthTokensAsync(CookieOptionsParameters cookieOptions, HttpContext context, RequestContextInfo requestContext, string userId, CancellationToken cancellation = default) @@ -86,7 +86,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); logger.LogInformation( - "Successful login attempt for user ID {UserId} from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint}.", + "Login successful for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}.", authToken.UserId, authToken.Ip, authToken.UserAgent, @@ -100,23 +100,25 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I var firstTokenAuth = await cache.GetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellation); if (firstTokenAuth == null || authenticator != firstTokenAuth.TwoFactorAuthenticator) - throw new SecurityException("The session time has expired"); + throw new SecurityException("Session expired. Please log in again."); switch (firstTokenAuth.TwoFactorAuthenticator) { case TwoFactorAuthenticator.Totp: { if (string.IsNullOrEmpty(firstTokenAuth.Secret)) - throw new InvalidOperationException("The user's secrets for data processing were not transferred."); + throw new InvalidOperationException("Required authentication data is missing."); var totp = new TotpService(firstTokenAuth.Secret); if (!totp.VerifyToken(code)) - throw new SecurityException("The entered code is incorrect."); + throw new SecurityException("Invalid verification code. Please try again."); } break; + case TwoFactorAuthenticator.None: + break; default: - throw new InvalidOperationException("The system failed to understand the authorization method."); + throw new InvalidOperationException("Unsupported authorization method."); } await GenerateAuthTokensAsync(cookieOptions, context, requestContext, firstTokenAuth.UserId, cancellation); @@ -162,7 +164,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I cookieOptions.DropCookie(context, CookieNames.AccessToken); cookieOptions.DropCookie(context, CookieNames.RefreshToken); - logger.LogWarning("Token validation failed for user ID {UserId}. Invalid token used from IP {UserIp} with User-Agent {UserAgent} and Fingerprint {Fingerprint}. Possible account compromise. Reason: {Reason}.", + logger.LogWarning("Token validation failed for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}. Reason: {Reason}.", authToken.UserId, authToken.Ip, authToken.UserAgent, -- 2.43.0 From 727f5c276e4691777e10ce4909f6f4755cafd1fb Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 2 Nov 2024 23:34:23 +0300 Subject: [PATCH 355/474] refactor: move files --- Security/Common/Domain/{ => Caching}/AuthToken.cs | 2 +- Security/Common/Domain/{ => Caching}/FirstAuthToken.cs | 2 +- Security/Common/Domain/TwoFactorAuthenticator.cs | 7 +++++++ Security/Common/Domain/User.cs | 6 ------ Security/Services/AuthService.cs | 1 + 5 files changed, 10 insertions(+), 8 deletions(-) rename Security/Common/Domain/{ => Caching}/AuthToken.cs (92%) rename Security/Common/Domain/{ => Caching}/FirstAuthToken.cs (90%) create mode 100644 Security/Common/Domain/TwoFactorAuthenticator.cs diff --git a/Security/Common/Domain/AuthToken.cs b/Security/Common/Domain/Caching/AuthToken.cs similarity index 92% rename from Security/Common/Domain/AuthToken.cs rename to Security/Common/Domain/Caching/AuthToken.cs index ff78786..33c2bb6 100644 --- a/Security/Common/Domain/AuthToken.cs +++ b/Security/Common/Domain/Caching/AuthToken.cs @@ -1,6 +1,6 @@ using System; -namespace Mirea.Api.Security.Common.Domain; +namespace Mirea.Api.Security.Common.Domain.Caching; internal class AuthToken { diff --git a/Security/Common/Domain/FirstAuthToken.cs b/Security/Common/Domain/Caching/FirstAuthToken.cs similarity index 90% rename from Security/Common/Domain/FirstAuthToken.cs rename to Security/Common/Domain/Caching/FirstAuthToken.cs index 29f229d..2181ce3 100644 --- a/Security/Common/Domain/FirstAuthToken.cs +++ b/Security/Common/Domain/Caching/FirstAuthToken.cs @@ -1,4 +1,4 @@ -namespace Mirea.Api.Security.Common.Domain; +namespace Mirea.Api.Security.Common.Domain.Caching; internal class FirstAuthToken { diff --git a/Security/Common/Domain/TwoFactorAuthenticator.cs b/Security/Common/Domain/TwoFactorAuthenticator.cs new file mode 100644 index 0000000..1a30b28 --- /dev/null +++ b/Security/Common/Domain/TwoFactorAuthenticator.cs @@ -0,0 +1,7 @@ +namespace Mirea.Api.Security.Common.Domain; + +public enum TwoFactorAuthenticator +{ + None, + Totp +} \ No newline at end of file diff --git a/Security/Common/Domain/User.cs b/Security/Common/Domain/User.cs index 2dafb97..27ea522 100644 --- a/Security/Common/Domain/User.cs +++ b/Security/Common/Domain/User.cs @@ -1,11 +1,5 @@ namespace Mirea.Api.Security.Common.Domain; -public enum TwoFactorAuthenticator -{ - None, - Totp -} - public class User { public required string Id { get; set; } diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 47f3a46..cb50c5b 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Mirea.Api.Security.Common; using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Domain.Caching; using Mirea.Api.Security.Common.Interfaces; using System; using System.Security; -- 2.43.0 From 0dda336de1eb098528c8c113b25b833913fb8c27 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 02:32:13 +0300 Subject: [PATCH 356/474] fix: logout for all users to delete cookies --- Endpoint/Controllers/V1/AuthController.cs | 1 - Security/Services/AuthService.cs | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index dafe3b7..25961ed 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -85,7 +85,6 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass public async Task Logout() { await auth.LogoutAsync(GetCookieParams(), HttpContext); - return Ok(); } diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index cb50c5b..7da44ab 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -193,6 +193,10 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I public async Task LogoutAsync(CookieOptionsParameters cookieOptions, HttpContext context, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); + + cookieOptions.DropCookie(context, CookieNames.AccessToken); + cookieOptions.DropCookie(context, CookieNames.RefreshToken); + var authTokenStruct = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation); if (authTokenStruct == null) @@ -200,7 +204,5 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I await RevokeAccessToken(authTokenStruct.AccessToken); await cache.RemoveAsync(requestContext.Fingerprint, cancellation); - cookieOptions.DropCookie(context, CookieNames.AccessToken); - cookieOptions.DropCookie(context, CookieNames.RefreshToken); } } \ No newline at end of file -- 2.43.0 From dbd9e1a07068066ab428df619d433265fc68a30e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 02:33:56 +0300 Subject: [PATCH 357/474] refactor: change Name to NameIdentifier --- Endpoint/Common/Services/Security/JwtTokenService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Common/Services/Security/JwtTokenService.cs b/Endpoint/Common/Services/Security/JwtTokenService.cs index f5f7429..41a169f 100644 --- a/Endpoint/Common/Services/Security/JwtTokenService.cs +++ b/Endpoint/Common/Services/Security/JwtTokenService.cs @@ -33,7 +33,7 @@ public class JwtTokenService : IAccessToken SigningCredentials = signingCredentials, Subject = new ClaimsIdentity( [ - new Claim(ClaimTypes.Name, userId), + new Claim(ClaimTypes.NameIdentifier, userId), // todo: get role by userId new Claim(ClaimTypes.Role, "") ]), -- 2.43.0 From 6b5eda77565d5095eb4c2e39087894ac6cc1ac51 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 02:34:50 +0300 Subject: [PATCH 358/474] fix: remove the latest api --- Endpoint/Common/Services/UrlHelper.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Endpoint/Common/Services/UrlHelper.cs b/Endpoint/Common/Services/UrlHelper.cs index a8ecbd7..9769a25 100644 --- a/Endpoint/Common/Services/UrlHelper.cs +++ b/Endpoint/Common/Services/UrlHelper.cs @@ -30,13 +30,8 @@ public static class UrlHelper var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries); - for (int i = 0; i < parts.Length; i++) - { - if (!parts[i].Equals("api", StringComparison.CurrentCultureIgnoreCase)) continue; - - parts = parts.Take(i).Concat(parts.Skip(i + 1)).ToArray(); - break; - } + if (parts[^1].Equals("api", StringComparison.CurrentCultureIgnoreCase)) + parts = parts.Take(parts.Length - 1).ToArray(); return CreateSubPath(string.Join("/", parts)); } -- 2.43.0 From 713bbfa16f497e507f89fb1e89a667d76545dc5b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 02:35:43 +0300 Subject: [PATCH 359/474] feat: add calculate correct api url --- Endpoint/Common/Services/UrlHelper.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Endpoint/Common/Services/UrlHelper.cs b/Endpoint/Common/Services/UrlHelper.cs index 9769a25..3de08b9 100644 --- a/Endpoint/Common/Services/UrlHelper.cs +++ b/Endpoint/Common/Services/UrlHelper.cs @@ -6,6 +6,9 @@ namespace Mirea.Api.Endpoint.Common.Services; public static class UrlHelper { + public static string GetCurrentScheme(this HttpContext context) => + context.Request.Headers["X-Forwarded-Proto"].FirstOrDefault() ?? context.Request.Scheme; + public static string GetCurrentDomain(this HttpContext context) => context.Request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? context.Request.Host.Host; @@ -38,4 +41,15 @@ public static class UrlHelper } public static string GetSubPathSwagger => CreateSubPath(Environment.GetEnvironmentVariable("SWAGGER_SUB_PATH")); + + public static string GetApiUrl(this HttpContext context, string apiPath = "") + { + var scheme = GetCurrentScheme(context); + var domain = GetCurrentDomain(context).TrimEnd('/'); + + var port = context.Request.Host.Port; + var portString = port.HasValue && port != 80 && port != 443 ? $":{port}" : string.Empty; + + return $"{scheme}://{domain}{portString}{GetSubPathWithoutFirstApiName}{apiPath.Trim('/')}"; + } } \ No newline at end of file -- 2.43.0 From 65d928ec2dda6f26046614e35ee67410e98b97bc Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 02:36:22 +0300 Subject: [PATCH 360/474] fix: remove authorize --- Endpoint/Controllers/V1/AuthController.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 25961ed..1b969db 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -80,8 +80,6 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass /// /// An Ok response if the logout was successful. [HttpGet("Logout")] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [Authorize] public async Task Logout() { await auth.LogoutAsync(GetCookieParams(), HttpContext); -- 2.43.0 From e977de3e4fdeb8906699742e648dfe3bd3b10ff3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 02:39:10 +0300 Subject: [PATCH 361/474] feat: add authorize in OAuth --- Endpoint/Configuration/Model/Admin.cs | 3 + Endpoint/Controllers/V1/AuthController.cs | 4 +- .../Domain/OAuth2/OAuthProviderUrisData.cs | 13 ++ .../Domain/OAuth2/OAuthTokenResponse.cs | 12 ++ .../Domain/OAuth2/UserInfo/GoogleUserInfo.cs | 38 ++++ .../Domain/OAuth2/UserInfo/MailRuUserInfo.cs | 36 ++++ .../Domain/OAuth2/UserInfo/YandexUserInfo.cs | 41 +++++ Security/Common/Domain/OAuthProvider.cs | 8 + Security/Common/Domain/OAuthUser.cs | 11 ++ Security/Common/Domain/User.cs | 5 +- Security/Common/Interfaces/IUserInfo.cs | 8 + Security/DependencyInjection.cs | 18 ++ Security/Services/AuthService.cs | 40 +++-- Security/Services/OAuthService.cs | 166 ++++++++++++++++++ 14 files changed, 390 insertions(+), 13 deletions(-) create mode 100644 Security/Common/Domain/OAuth2/OAuthProviderUrisData.cs create mode 100644 Security/Common/Domain/OAuth2/OAuthTokenResponse.cs create mode 100644 Security/Common/Domain/OAuth2/UserInfo/GoogleUserInfo.cs create mode 100644 Security/Common/Domain/OAuth2/UserInfo/MailRuUserInfo.cs create mode 100644 Security/Common/Domain/OAuth2/UserInfo/YandexUserInfo.cs create mode 100644 Security/Common/Domain/OAuthProvider.cs create mode 100644 Security/Common/Domain/OAuthUser.cs create mode 100644 Security/Common/Interfaces/IUserInfo.cs create mode 100644 Security/Services/OAuthService.cs diff --git a/Endpoint/Configuration/Model/Admin.cs b/Endpoint/Configuration/Model/Admin.cs index d31d7d0..26492ec 100644 --- a/Endpoint/Configuration/Model/Admin.cs +++ b/Endpoint/Configuration/Model/Admin.cs @@ -1,5 +1,6 @@ using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Security.Common.Domain; +using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; @@ -20,6 +21,8 @@ public class Admin : ISaveSettings public TwoFactorAuthenticator TwoFactorAuthenticator { get; set; } = TwoFactorAuthenticator.None; public string? Secret { get; set; } + public Dictionary? OAuthProviders { get; set; } + public void SaveSetting() { File.WriteAllText(FilePath, JsonSerializer.Serialize(this)); diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 1b969db..69f0ed0 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -13,6 +13,7 @@ using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Security.Common.Domain; using Mirea.Api.Security.Services; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1; @@ -47,7 +48,8 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass PasswordHash = userEntity.PasswordHash, Salt = userEntity.Salt, TwoFactorAuthenticator = userEntity.TwoFactorAuthenticator, - SecondFactorToken = userEntity.Secret + SecondFactorToken = userEntity.Secret, + OAuthProviders = userEntity.OAuthProviders }, HttpContext, request.Password); diff --git a/Security/Common/Domain/OAuth2/OAuthProviderUrisData.cs b/Security/Common/Domain/OAuth2/OAuthProviderUrisData.cs new file mode 100644 index 0000000..cca96f2 --- /dev/null +++ b/Security/Common/Domain/OAuth2/OAuthProviderUrisData.cs @@ -0,0 +1,13 @@ +using System; + +namespace Mirea.Api.Security.Common.Domain.OAuth2; + +internal struct OAuthProviderUrisData +{ + public string RedirectUrl { get; init; } + public string TokenUrl { get; init; } + public string UserInfoUrl { get; init; } + public string AuthHeader { get; init; } + public string Scope { get; init; } + public Type UserInfoType { get; init; } +} \ No newline at end of file diff --git a/Security/Common/Domain/OAuth2/OAuthTokenResponse.cs b/Security/Common/Domain/OAuth2/OAuthTokenResponse.cs new file mode 100644 index 0000000..920fa03 --- /dev/null +++ b/Security/Common/Domain/OAuth2/OAuthTokenResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Mirea.Api.Security.Common.Domain.OAuth2; + +public class OAuthTokenResponse +{ + [JsonPropertyName("access_token")] + public required string AccessToken { get; set; } + + [JsonPropertyName("expires_in")] + public int ExpiresIn { get; set; } +} \ No newline at end of file diff --git a/Security/Common/Domain/OAuth2/UserInfo/GoogleUserInfo.cs b/Security/Common/Domain/OAuth2/UserInfo/GoogleUserInfo.cs new file mode 100644 index 0000000..1579f9c --- /dev/null +++ b/Security/Common/Domain/OAuth2/UserInfo/GoogleUserInfo.cs @@ -0,0 +1,38 @@ +using Mirea.Api.Security.Common.Interfaces; +using System.Text.Json.Serialization; + +namespace Mirea.Api.Security.Common.Domain.OAuth2.UserInfo; + +internal class GoogleUserInfo : IUserInfo +{ + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("email")] + public required string Email { get; set; } + + [JsonPropertyName("given_name")] + public required string GivenName { get; set; } + + [JsonPropertyName("family_name")] + public required string FamilyName { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("picture")] + public required string Picture { get; set; } + + [JsonPropertyName("verified_email")] + public bool? VerifiedEmail { get; set; } + + public OAuthUser MapToInternalUser() => + new() + { + Id = Id, + Email = VerifiedEmail.HasValue && VerifiedEmail.Value ? Email : null, + FirstName = GivenName, + LastName = FamilyName, + IconUri = Picture + }; +} \ No newline at end of file diff --git a/Security/Common/Domain/OAuth2/UserInfo/MailRuUserInfo.cs b/Security/Common/Domain/OAuth2/UserInfo/MailRuUserInfo.cs new file mode 100644 index 0000000..4920cdb --- /dev/null +++ b/Security/Common/Domain/OAuth2/UserInfo/MailRuUserInfo.cs @@ -0,0 +1,36 @@ +using Mirea.Api.Security.Common.Interfaces; +using System.Text.Json.Serialization; + +namespace Mirea.Api.Security.Common.Domain.OAuth2.UserInfo; + +internal class MailRuUserInfo : IUserInfo +{ + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("email")] + public required string Email { get; set; } + + [JsonPropertyName("first_name")] + public required string FirstName { get; set; } + + [JsonPropertyName("last_name")] + public required string LastName { get; set; } + + [JsonPropertyName("nickname")] + public required string Username { get; set; } + + [JsonPropertyName("image")] + public string? Image { get; set; } + + public OAuthUser MapToInternalUser() => + new() + { + Id = Id, + Email = Email, + FirstName = FirstName, + LastName = LastName, + Username = Username, + IconUri = Image + }; +} \ No newline at end of file diff --git a/Security/Common/Domain/OAuth2/UserInfo/YandexUserInfo.cs b/Security/Common/Domain/OAuth2/UserInfo/YandexUserInfo.cs new file mode 100644 index 0000000..f1a9bab --- /dev/null +++ b/Security/Common/Domain/OAuth2/UserInfo/YandexUserInfo.cs @@ -0,0 +1,41 @@ +using Mirea.Api.Security.Common.Interfaces; +using System.Text.Json.Serialization; + +namespace Mirea.Api.Security.Common.Domain.OAuth2.UserInfo; + +internal class YandexUserInfo : IUserInfo +{ + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("login")] + public required string Login { get; set; } + + [JsonPropertyName("default_email")] + public required string DefaultEmail { get; set; } + + [JsonPropertyName("first_name")] + public string? FirstName { get; set; } + + [JsonPropertyName("last_name")] + public string? LastName { get; set; } + + + [JsonPropertyName("is_avatar_empty")] + public bool IsAvatarEmpty { get; set; } + + [JsonPropertyName("default_avatar_id")] + public string? DefaultAvatarId { get; set; } + + public OAuthUser MapToInternalUser() => + new() + { + Id = Id, + Email = DefaultEmail, + FirstName = FirstName, + LastName = LastName, + IconUri = + IsAvatarEmpty ? null : $"https://avatars.yandex.net/get-yapic/{DefaultAvatarId}/islands-retina-50", + Username = Login + }; +} \ No newline at end of file diff --git a/Security/Common/Domain/OAuthProvider.cs b/Security/Common/Domain/OAuthProvider.cs new file mode 100644 index 0000000..cec561b --- /dev/null +++ b/Security/Common/Domain/OAuthProvider.cs @@ -0,0 +1,8 @@ +namespace Mirea.Api.Security.Common.Domain; + +public enum OAuthProvider +{ + Google, + Yandex, + MailRu +} \ No newline at end of file diff --git a/Security/Common/Domain/OAuthUser.cs b/Security/Common/Domain/OAuthUser.cs new file mode 100644 index 0000000..539a244 --- /dev/null +++ b/Security/Common/Domain/OAuthUser.cs @@ -0,0 +1,11 @@ +namespace Mirea.Api.Security.Common.Domain; + +public class OAuthUser +{ + public required string Id { get; set; } + public string? Username { get; set; } + public string? Email { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? IconUri { get; set; } +} \ No newline at end of file diff --git a/Security/Common/Domain/User.cs b/Security/Common/Domain/User.cs index 27ea522..61dad9f 100644 --- a/Security/Common/Domain/User.cs +++ b/Security/Common/Domain/User.cs @@ -1,4 +1,6 @@ -namespace Mirea.Api.Security.Common.Domain; +using System.Collections.Generic; + +namespace Mirea.Api.Security.Common.Domain; public class User { @@ -9,4 +11,5 @@ public class User public required string Salt { get; set; } public required TwoFactorAuthenticator TwoFactorAuthenticator { get; set; } public string? SecondFactorToken { get; set; } + public Dictionary? OAuthProviders { get; set; } } \ No newline at end of file diff --git a/Security/Common/Interfaces/IUserInfo.cs b/Security/Common/Interfaces/IUserInfo.cs new file mode 100644 index 0000000..9e96223 --- /dev/null +++ b/Security/Common/Interfaces/IUserInfo.cs @@ -0,0 +1,8 @@ +using Mirea.Api.Security.Common.Domain; + +namespace Mirea.Api.Security.Common.Interfaces; + +internal interface IUserInfo +{ + OAuthUser MapToInternalUser(); +} \ No newline at end of file diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs index 0549ff4..aa36553 100644 --- a/Security/DependencyInjection.cs +++ b/Security/DependencyInjection.cs @@ -1,9 +1,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Mirea.Api.Security.Common.Domain; using Mirea.Api.Security.Common.Interfaces; using Mirea.Api.Security.Services; using System; +using System.Collections.Generic; namespace Mirea.Api.Security; @@ -45,6 +47,22 @@ public static class DependencyInjection }; }); + var providers = new Dictionary(); + + foreach (var provider in Enum.GetValues()) + { + var providerName = Enum.GetName(provider)!.ToUpper(); + var clientId = configuration[$"{providerName}_CLIENT_ID"]; + var secret = configuration[$"{providerName}_CLIENT_SECRET"]; + + if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(secret)) + continue; + + providers.Add(provider, (clientId, secret)); + } + + services.AddSingleton(provider => new OAuthService(provider.GetRequiredService>(), providers)); + return services; } } \ No newline at end of file diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 7da44ab..30cd2e8 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -23,7 +23,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I accessTokenService.GenerateToken(userId); private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token"; - internal static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; + private static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; private Task SetAuthTokenDataToCache(AuthToken data, CancellationToken cancellation) => cache.SetAsync( @@ -32,6 +32,18 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I slidingExpiration: Lifetime, cancellationToken: cancellation); + private Task CreateFirstAuthTokenToCache(User data, RequestContextInfo requestContext, CancellationToken cancellation) => + cache.SetAsync( + GetFirstAuthCacheKey(requestContext.Fingerprint), + JsonSerializer.SerializeToUtf8Bytes(new FirstAuthToken(requestContext) + { + UserId = data.Id, + Secret = data.SecondFactorToken, + TwoFactorAuthenticator = data.TwoFactorAuthenticator + }), + slidingExpiration: LifetimeFirstAuth, + cancellationToken: cancellation); + private Task RevokeAccessToken(string token) => revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); @@ -94,6 +106,21 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.Fingerprint); } + public async Task LoginOAuthAsync(CookieOptionsParameters cookieOptions, HttpContext context, User user, OAuthProvider provider, CancellationToken cancellation = default) + { + var requestContext = new RequestContextInfo(context, cookieOptions); + + if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) + { + await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id, cancellation); + return TwoFactorAuthenticator.None; + } + + await CreateFirstAuthTokenToCache(user, requestContext, cancellation); + + return user.TwoFactorAuthenticator; + } + public async Task LoginAsync(CookieOptionsParameters cookieOptions, HttpContext context, TwoFactorAuthenticator authenticator, string code, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); @@ -116,8 +143,6 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I throw new SecurityException("Invalid verification code. Please try again."); } break; - case TwoFactorAuthenticator.None: - break; default: throw new InvalidOperationException("Unsupported authorization method."); } @@ -138,14 +163,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return TwoFactorAuthenticator.None; } - var firstAuthToken = new FirstAuthToken(requestContext) - { - UserId = user.Id, - Secret = user.SecondFactorToken, - TwoFactorAuthenticator = user.TwoFactorAuthenticator - }; - - await cache.SetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), firstAuthToken, absoluteExpirationRelativeToNow: LifetimeFirstAuth, cancellationToken: cancellation); + await CreateFirstAuthTokenToCache(user, requestContext, cancellation); return user.TwoFactorAuthenticator; } diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs new file mode 100644 index 0000000..7721b86 --- /dev/null +++ b/Security/Services/OAuthService.cs @@ -0,0 +1,166 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Domain.OAuth2; +using Mirea.Api.Security.Common.Domain.OAuth2.UserInfo; +using Mirea.Api.Security.Common.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.Security.Services; + +public class OAuthService(ILogger logger, Dictionary providers) +{ + private static readonly Dictionary ProviderData = new() + { + [OAuthProvider.Google] = new OAuthProviderUrisData + { + RedirectUrl = "https://accounts.google.com/o/oauth2/v2/auth", + TokenUrl = "https://oauth2.googleapis.com/token", + UserInfoUrl = "https://www.googleapis.com/oauth2/v2/userinfo", + Scope = "openid email profile", + AuthHeader = "Bearer", + UserInfoType = typeof(GoogleUserInfo) + }, + [OAuthProvider.Yandex] = new OAuthProviderUrisData + { + RedirectUrl = "https://oauth.yandex.ru/authorize", + TokenUrl = "https://oauth.yandex.ru/token", + UserInfoUrl = "https://login.yandex.ru/info?format=json", + Scope = "login:email login:info login:avatar", + AuthHeader = "OAuth", + UserInfoType = typeof(YandexUserInfo) + }, + [OAuthProvider.MailRu] = new OAuthProviderUrisData + { + RedirectUrl = "https://oauth.mail.ru/login", + TokenUrl = "https://oauth.mail.ru/token", + UserInfoUrl = "https://oauth.mail.ru/userinfo", + AuthHeader = "", + Scope = "", + UserInfoType = typeof(MailRuUserInfo) + } + }; + + private static async Task ExchangeCodeForTokensAsync(string requestUri, string redirectUrl, string code, string clientId, string secret, CancellationToken cancellation) + { + var tokenRequest = new HttpRequestMessage(HttpMethod.Post, requestUri) + { + Content = new FormUrlEncodedContent(new Dictionary + { + { "code", code }, + { "client_id", clientId }, + { "client_secret", secret }, + { "redirect_uri", redirectUrl}, + { "grant_type", "authorization_code" } + }) + }; + + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("MireaSchedule/1.0 (Winsomnia)"); + + var response = await httpClient.SendAsync(tokenRequest, cancellation); + var data = await response.Content.ReadAsStringAsync(cancellation); + + if (!response.IsSuccessStatusCode) + throw new HttpRequestException(data); + + return JsonSerializer.Deserialize(data); + } + + private static async Task GetUserProfileAsync(string requestUri, string authHeader, string accessToken, OAuthProvider provider, CancellationToken cancellation) + { + var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + + if (string.IsNullOrEmpty(authHeader)) + request.RequestUri = new Uri(request.RequestUri?.AbsoluteUri + "?access_token=" + accessToken); + else + request.Headers.Authorization = new AuthenticationHeaderValue(authHeader, accessToken); + + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("MireaSchedule/1.0 (Winsomnia)"); + + var response = await httpClient.SendAsync(request, cancellation); + var data = await response.Content.ReadAsStringAsync(cancellation); + + if (!response.IsSuccessStatusCode) + throw new HttpRequestException(data); + + var userInfo = JsonSerializer.Deserialize(data, ProviderData[provider].UserInfoType) as IUserInfo; + return userInfo?.MapToInternalUser(); + } + + public (OAuthProvider Provider, Uri Redirect)[] GetAvailableProviders(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl) + { + var redirectUri = "?client_id={0}" + + "&response_type=code" + + $"&redirect_uri={redirectUrl}" + + "&scope={1}" + + $"&state={new RequestContextInfo(context, cookieOptions).Fingerprint}_{{2}}"; + + return providers.Select(x => (x.Key, new Uri(ProviderData[x.Key].RedirectUrl.TrimEnd('/') + + string.Format(redirectUri, + x.Value.ClientId, + ProviderData[x.Key].Scope, + Enum.GetName(x.Key)))) + ).ToArray(); + } + + public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) + { + var requestContext = new RequestContextInfo(context, cookieOptions); + + var partsState = state.Split('_'); + + if (!Enum.TryParse(partsState.Last(), true, out var provider) || + !providers.TryGetValue(provider, out var providerInfo) || + !ProviderData.TryGetValue(provider, out var currentProviderStruct)) + { + logger.LogWarning("Failed to parse OAuth provider from state: {State}", state); + throw new InvalidOperationException("Invalid authorization request."); + } + + var fingerprint = string.Join("_", partsState.SkipLast(1)); + + if (requestContext.Fingerprint != fingerprint) + { + logger.LogWarning("Fingerprint mismatch. Possible CSRF attack detected."); + throw new SecurityException("Suspicious activity detected. Please try again."); + } + + OAuthTokenResponse? accessToken = null; + try + { + accessToken = await ExchangeCodeForTokensAsync(currentProviderStruct.TokenUrl, redirectUrl, code, providerInfo.ClientId, providerInfo.Secret, cancellation); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to exchange authorization code for tokens with provider {Provider}", provider); + } + + if (accessToken == null) + throw new SecurityException("Unable to complete authorization with the provider. Please try again later."); + + OAuthUser? result = null; + try + { + result = await GetUserProfileAsync(currentProviderStruct.UserInfoUrl, currentProviderStruct.AuthHeader, accessToken.AccessToken, provider, cancellation); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to retrieve user information from provider {Provider}", provider); + } + + if (result == null) + throw new SecurityException("Unable to retrieve user information. Please check the details and try again."); + + return (provider, result); + } +} \ No newline at end of file -- 2.43.0 From 5f36e0f75bcefa7a6b66572b9d51090ddc3cfa19 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 02:39:45 +0300 Subject: [PATCH 362/474] docs: update --- .env | 48 +++++++++++++++++- README.md | 148 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 164 insertions(+), 32 deletions(-) diff --git a/.env b/.env index 1e663c4..955584e 100644 --- a/.env +++ b/.env @@ -21,6 +21,8 @@ PATH_TO_SAVE= # The actual sub path to the api # string # (optional) +# If the specified path ends with "/api", the system will avoid duplicating "api" in the final URL. +# This allows flexible API structuring, especially when running behind a reverse proxy or in containerized environments. ACTUAL_SUB_PATH= # The sub path to the swagger @@ -114,4 +116,48 @@ SECURITY_HASH_TOKEN= # The size of the salt used to hash passwords # integer # The salt is a random value added to the password before hashing to prevent the use of rainbow hash tables and other attacks. -SECURITY_SALT_SIZE=16 \ No newline at end of file +SECURITY_SALT_SIZE=16 + +### OAuth2 + +#### GOOGLE + +# The client ID for Google OAuth +# string +# This is the client ID provided by Google when you register your application for OAuth. +# It's necessary for enabling Google login functionality. +GOOGLE_CLIENT_ID= + +# The client secret for Google OAuth +# string +# This is the client secret provided by Google, used alongside the client ID to authenticate your application. +# Make sure to keep it confidential. +GOOGLE_CLIENT_SECRET= + +#### Yandex + +# The client ID for Yandex OAuth +# string +# This is the client ID provided by Yandex when you register your application for OAuth. +# It's required for enabling Yandex login functionality. +YANDEX_CLIENT_ID= + +# The client secret for Yandex OAuth +# string +# This is the client secret provided by Yandex, used alongside the client ID to authenticate your application. +# Keep it confidential to ensure the security of your app. +YANDEX_CLIENT_SECRET= + +#### MailRu + +# The client ID for MailRu OAuth +# string +# This is the client ID provided by MailRu (Mail.ru Group) when you register your application for OAuth. +# It's necessary for enabling MailRu login functionality. +MAILRU_CLIENT_ID= + +# The client secret for MailRu OAuth +# string +# This is the client secret provided by MailRu, used alongside the client ID to authenticate your application. +# Keep it confidential to ensure the security of your app. +MAILRU_CLIENT_SECRET= diff --git a/README.md b/README.md index ab2c7d0..02d2276 100644 --- a/README.md +++ b/README.md @@ -11,48 +11,126 @@ The main task is to provide convenient and flexible tools for accessing the sche The purpose of this project is to provide convenient and flexible tools for obtaining schedule data. -In a situation where existing resources provide limited functionality or an inconvenient interface, this project aims to provide users with a simple and effective tool for accessing information about class schedules. +In a situation where existing resources provide limited functionality or an inconvenient interface, this project aims to +provide users with a simple and effective tool for accessing information about class schedules. -Developing your own API and using your own tools for downloading and processing data allows you to ensure the reliability, flexibility and extensibility of the application functionality. +Developing your own API and using your own tools for downloading and processing data allows you to ensure the +reliability, flexibility and extensibility of the application functionality. ## Features -1. **Flexible API**: The API provides a variety of methods for accessing schedule data. Unlike competitors that provide a limited set of endpoints, this application provides a wider range of functionality, allowing you to get data about groups, campuses, faculties, classrooms and teachers. You can get all the data at once or select specific IDs with the details that are needed. +1. **Flexible API**: The API provides a variety of methods for accessing schedule data. Unlike competitors that provide + a limited set of endpoints, this application provides a wider range of functionality, allowing you to get data about + groups, campuses, faculties, classrooms and teachers. You can get all the data at once or select specific IDs with + the details that are needed. 2. **Database Providers**: The application provides the capability of various database providers. -3. **Using self-written packages**: The project uses two proprietary NuGet packages. One of them is designed for parsing schedules, and the other is for downloading Excel spreadsheets from external sites. +3. **Using self-written packages**: The project uses two proprietary NuGet packages. One of them is designed for parsing + schedules, and the other is for downloading Excel spreadsheets from external sites. ## Project status -The project is under development. Further development will be aimed at expanding the functionality and improving the user experience. +The project is under development. Further development will be aimed at expanding the functionality and improving the +user experience. # Environment Variables -This table provides information about the environment variables that are used in the application. These variables are stored in the [.env](.env) file. +This table provides information about the environment variables that are used in the application. These variables are +stored in the [.env](.env) file. -In addition to these variables, you also need to fill in a file with settings in json format. The web application provided by this project already has everything necessary to configure the file in the Client-Server communication format via the controller. If you need to get the configuration file otherwise, then you need to refer to the classes that provide configuration-related variables. +In addition to these variables, you also need to fill in a file with settings in json format. The web application +provided by this project already has everything necessary to configure the file in the Client-Server communication +format via the controller. If you need to get the configuration file otherwise, then you need to refer to the classes +that provide configuration-related variables. Please note that the application will not work correctly if you do not fill in the required variables. -| Variable | Default | Description | Required | -|---------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| -| PATH_TO_SAVE | Current folder | The path to save the data. Saving logs (if the full path is not specified), databases (if Sqlite), and other data that should be saved in a place other than where the program is launched. | ✔ | -| SECURITY_SIGNING_TOKEN | ❌ | JWT signature token. This token will be used to create and verify the signature of JWT tokens. The token must be equal to 64 characters. | ✔ | -| SECURITY_ENCRYPTION_TOKEN | ❌ | Token for JWT encryption. This token will be used to encrypt and decrypt JWT tokens. The token must be equal to 32 characters. | ✔ | -| SECURITY_LIFE_TIME_RT | 1440 | Time in minutes after which the Refresh Token will become invalid. | ❌ | -| SECURITY_LIFE_TIME_JWT | 15 | Time in minutes after which the JWT token will become invalid. | ❌ | -| SECURITY_LIFE_TIME_1_FA | 15 | Time in minutes after which the token of the first factor will become invalid. | ❌ | -| SECURITY_JWT_ISSUER | ❌ | An identifier that points to the server that created the token. | ✔ | -| SECURITY_JWT_AUDIENCE | ❌ | ID of the audience for which the token is intended. | ✔ | -| SECURITY_HASH_ITERATION | ❌ | The number of iterations used to hash passwords in the Argon2 algorithm. | ✔ | -| SECURITY_HASH_MEMORY | ❌ | The amount of memory used to hash passwords in the Argon2 algorithm. | ✔ | -| SECURITY_HASH_PARALLELISM | ❌ | Parallelism determines how many of the memory fragments divided into strips will be used to generate a hash. | ✔ | -| SECURITY_HASH_SIZE | 32 | The size of the output hash generated by the password hashing algorithm. | ❌ | -| SECURITY_HASH_TOKEN | ❌ | Additional protection for Argon2. We recommend installing a token so that even if the data is compromised, an attacker cannot brute force a password without a token. | ❌ | -| SECURITY_SALT_SIZE | 16 | The size of the salt used to hash passwords. The salt is a random value added to the password before hashing to prevent the use of rainbow hash tables and other attacks. | ❌ | +### General Configuration + +| Variable | Default | Description | Required | +|------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| PATH_TO_SAVE | ./ | The path to save the data. Saving logs, databases (if Sqlite), and other data that should be saved in a different place. REQUIRED if the application is inside the container. | ✔ | +| ACTUAL_SUB_PATH | | The actual sub path to the API. If the specified path ends with "/api", the system will avoid duplicating "api" in the final URL. | ❌ | +| SWAGGER_SUB_PATH | swagger | The sub path to the Swagger documentation. | ❌ | +| INTERNAL_PORT | 8080 | Specify the internal port on which the server will listen. | ❌ | + +### Security Configuration + +| Variable | Default | Description | Required | +|---------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------|----------| +| SECURITY_SIGNING_TOKEN | | JWT signature token. This token will be used to create and verify the signature of JWT tokens. Must be equal to 64 characters. | ✔ | +| SECURITY_ENCRYPTION_TOKEN | | Token for JWT encryption. This token will be used to encrypt and decrypt JWT tokens. Must be equal to 32 characters. | ✔ | +| SECURITY_LIFE_TIME_RT | 1440 | Time in minutes, which indicates after which time the Refresh Token will become invalid. | ❌ | +| SECURITY_LIFE_TIME_JWT | 15 | The time in minutes for the JWT to be valid. | ❌ | +| SECURITY_LIFE_TIME_1_FA | 15 | Time in minutes after which the token of the first factor will become invalid. | ❌ | +| SECURITY_JWT_ISSUER | | An identifier that points to the server that created the token. | ✔ | +| SECURITY_JWT_AUDIENCE | | ID of the audience for which the token is intended. | ✔ | + +### Hashing Configuration + +| Variable | Default | Description | Required | +|---------------------------|---------|-------------------------------------------------------------------------------------------------------------------|----------| +| SECURITY_HASH_ITERATION | | The number of iterations used to hash passwords in the Argon2 algorithm. At least 10 is recommended for security. | ✔ | +| SECURITY_HASH_MEMORY | 65536 | The amount of memory used to hash passwords in the Argon2 algorithm (in KB). | ✔ | +| SECURITY_HASH_PARALLELISM | | Parallelism determines how many of the memory fragments divided into strips will be used to generate a hash. | ✔ | +| SECURITY_HASH_SIZE | 32 | The size of the output hash generated by the password hashing algorithm. | ✔ | +| SECURITY_HASH_TOKEN | | Additional protection for Argon2. Recommended to install a token for added security. | ❌ | +| SECURITY_SALT_SIZE | 16 | The size of the salt used to hash passwords. | ✔ | + +### OAuth2 Configuration + +To set up the `redirect URL` when registering and logging in using OAuth 2, use the following format: + +``` +"{schema}://{domain}{portString}{ACTUAL_SUB_PATH}/api/v1/Auth/OAuth2" +``` + +** Where:** + +- `{schema}` is the protocol you are using (`http` or `https'). +- `{domain}` is your domain (for example, `mydomain.com ` or IP address). +- `{portString}` is a port string that is only needed if your application is running on a non—standard port (for + example, `:8080`). If you use standard ports (`80` for `http` and `443` for `https`), this parameter can be omitted. +- `{ACTUAL_SUB_PATH}` is the path to your API that you specify in the settings. If it ends with `/api', then don't add ` + /api` at the end of the URL. + +**Examples:** + +- If you have `ACTUAL_SUB_PATH = my_subpath/api`, then the `redirect URL` will be: + - `https://mydomain.com/my_subpath/api/v1/Auth/OAuth2` + +- If your application is on a local server and uses a non-standard port: + - `http://192.168.1.5:8080/my_subpath/api/v1/Auth/OAuth2 ` + +**Important:** + +- If your application is not located behind a proxy server, then the parameter `{ACTUAL_SUB_PATH}` does not need to be + specified. + +#### Google + +| Variable | Default | Description | Required | +|----------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|----------| +| GOOGLE_CLIENT_ID | | The client ID provided by Google when you register your application for OAuth. It is necessary for enabling Google login functionality. | ✔ | +| GOOGLE_CLIENT_SECRET | | The client secret provided by Google, used alongside the client ID to authenticate your application. | ✔ | + +#### Yandex + +| Variable | Default | Description | Required | +|----------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------|----------| +| YANDEX_CLIENT_ID | | The client ID provided by Yandex when you register your application for OAuth. It is required for enabling Yandex login functionality. | ✔ | +| YANDEX_CLIENT_SECRET | | The client secret provided by Yandex, used alongside the client ID to authenticate your application. | ✔ | + +#### MailRu + +| Variable | Default | Description | Required | +|----------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| MAILRU_CLIENT_ID | | The client ID provided by MailRu (Mail.ru Group) when you register your application for OAuth. It is necessary for enabling MailRu login functionality. | ✔ | +| MAILRU_CLIENT_SECRET | | The client secret provided by MailRu, used alongside the client ID to authenticate your application. | ✔ | # Installation -If you want to make a fork of this project or place the Backend application on your hosting yourself, then follow the instructions below. +If you want to make a fork of this project or place the Backend application on your hosting yourself, then follow the +instructions below. 1. [Docker Installation](#docker-installation) 2. [Docker Self Build](#docker-self-build) @@ -86,11 +164,14 @@ Using the `--name` option, you can specify your container name, for example: `-- With the `-p` option, you can specify the port you need: `-p 80:8080`. -It is necessary to tell the application exactly where to save the data so that it does not disappear when the container is deleted. +It is necessary to tell the application exactly where to save the data so that it does not disappear when the container +is deleted. -To do this, replace the `-v` option, where you need to specify the path to the data on the host first, and then using `:` specify the path inside the container. `-v /nas/mirea/backend:/myfolder`. +To do this, replace the `-v` option, where you need to specify the path to the data on the host first, and then using +`:` specify the path inside the container. `-v /nas/mirea/backend:/myfolder`. -At the same time, do not forget to replace inside [.env](.env) `PATH_TO_SAVE` with what you specify in the `-v` option. In our case, it will be `PATH_TO_SAVE=/myfolder`. +At the same time, do not forget to replace inside [.env](.env) `PATH_TO_SAVE` with what you specify in the `-v` option. +In our case, it will be `PATH_TO_SAVE=/myfolder`. That's it, the container is running! @@ -106,7 +187,8 @@ docker build -t my-name/mirea-backend:latest . Where `-t` indicates the name and version of the image. You can specify their `your-name/image-name:version`. -Now the image is ready. To launch the container, refer to [Docker Installation](#docker-installation), do not forget to specify the name of the image that you have built. +Now the image is ready. To launch the container, refer to [Docker Installation](#docker-installation), do not forget to +specify the name of the image that you have built. ## Manual Installation @@ -123,11 +205,14 @@ To install using a pre-built application, follow these steps: ### Install ASP.NET Installation ASP.NET it depends on the specific platform. -Go to [Microsoft website](https://dotnet.microsoft.com/download/dotnet/8.0 ) and find your platform. Follow the installation instructions. +Go to [Microsoft website](https://dotnet.microsoft.com/download/dotnet/8.0 ) and find your platform. Follow the +installation instructions. ### Download Package -The latest versions of the packages can be found in [releases](https://git.winsomnia.net/Winsomnia/MireaBackend/releases ). If there is no build for your platform, go [to the Self Build section](#self-build). +The latest versions of the packages can be found +in [releases](https://git.winsomnia.net/Winsomnia/MireaBackend/releases ). If there is no build for your platform, +go [to the Self Build section](#self-build). ### Run @@ -160,7 +245,8 @@ To build your own version of the program, follow these steps: ### Install NET SDK Installation.The NET SDK depends on the specific platform. -Go to [Microsoft website](https://dotnet.microsoft.com/download/dotnet/8.0 ) and find your platform. Follow the installation instructions. +Go to [Microsoft website](https://dotnet.microsoft.com/download/dotnet/8.0 ) and find your platform. Follow the +installation instructions. ### Clone The Repository -- 2.43.0 From a96073d44db4ebaeab6b0aa4ca99355fd03e9eb2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 02:59:51 +0300 Subject: [PATCH 363/474] feat: add available providers list --- ApiDto/Common/OAuthProvider.cs | 22 +++++++++++++++ .../Responses/AvailableProvidersResponse.cs | 25 +++++++++++++++++ .../MapperDto/AvailableProvidersConverter.cs | 27 +++++++++++++++++++ Endpoint/Controllers/V1/AuthController.cs | 7 +++++ 4 files changed, 81 insertions(+) create mode 100644 ApiDto/Common/OAuthProvider.cs create mode 100644 ApiDto/Responses/AvailableProvidersResponse.cs create mode 100644 Endpoint/Common/MapperDto/AvailableProvidersConverter.cs diff --git a/ApiDto/Common/OAuthProvider.cs b/ApiDto/Common/OAuthProvider.cs new file mode 100644 index 0000000..ccd0622 --- /dev/null +++ b/ApiDto/Common/OAuthProvider.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.Dto.Common; + +/// +/// Represents different OAuth providers for authentication. +/// +public enum OAuthProvider +{ + /// + /// OAuth provider for Google. + /// + Google, + + /// + /// OAuth provider for Yandex. + /// + Yandex, + + /// + /// OAuth provider for Mail.ru. + /// + MailRu +} diff --git a/ApiDto/Responses/AvailableProvidersResponse.cs b/ApiDto/Responses/AvailableProvidersResponse.cs new file mode 100644 index 0000000..c4e9dfc --- /dev/null +++ b/ApiDto/Responses/AvailableProvidersResponse.cs @@ -0,0 +1,25 @@ +using Mirea.Api.Dto.Common; +using System; + +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents the response containing information about available OAuth providers. +/// +public class AvailableProvidersResponse +{ + /// + /// Gets or sets the name of the OAuth provider. + /// + public required string ProviderName { get; set; } + + /// + /// Gets or sets the enum value representing the OAuth provider. + /// + public OAuthProvider Provider { get; set; } + + /// + /// Gets or sets the redirect URI for the OAuth provider. + /// + public required Uri Redirect { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs b/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs new file mode 100644 index 0000000..02021bd --- /dev/null +++ b/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs @@ -0,0 +1,27 @@ +using Mirea.Api.Dto.Responses; +using Mirea.Api.Security.Common.Domain; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mirea.Api.Endpoint.Common.MapperDto; + +public static class AvailableProvidersConverter +{ + public static Dto.Common.OAuthProvider ConvertToDto(this OAuthProvider provider) => + provider switch + { + OAuthProvider.Google => Dto.Common.OAuthProvider.Google, + OAuthProvider.Yandex => Dto.Common.OAuthProvider.Yandex, + OAuthProvider.MailRu => Dto.Common.OAuthProvider.MailRu, + _ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null) + }; + + public static List ConvertToDto(this (OAuthProvider Provider, Uri Redirect)[] data) => + data.Select(x => new AvailableProvidersResponse() + { + ProviderName = Enum.GetName(x.Provider)!, + Provider = x.Provider.ConvertToDto(), + Redirect = x.Redirect + }).ToList(); +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 69f0ed0..075f298 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; 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.MapperDto; @@ -28,6 +29,12 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass Path = UrlHelper.GetSubPathWithoutFirstApiName + "api" }; + [HttpGet("GetAvailableProviders")] + public ActionResult> GetUrls() => + Ok(oAuthService + .GetAvailableProviders(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!)) + .ConvertToDto()); + [HttpPost("Login")] [BadRequestResponse] public async Task> Login([FromBody] LoginRequest request) -- 2.43.0 From 95627003e559dd94f1d0a3213f46fc3ff7a44aa4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 03:14:17 +0300 Subject: [PATCH 364/474] refactor: change the error --- Endpoint/Controllers/V1/AuthController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 075f298..2fb1e7a 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -43,7 +43,7 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass if (!userEntity.Username.Equals(request.Username, StringComparison.OrdinalIgnoreCase) && !userEntity.Email.Equals(request.Username, StringComparison.OrdinalIgnoreCase)) - return BadRequest("Invalid username/email or password"); + return Unauthorized("Authentication failed. Please check your credentials."); var tokenResult = await auth.LoginAsync( GetCookieParams(), -- 2.43.0 From 503f5792fb8c52b064e9bd1b0ffa1b395416b77a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 03:14:42 +0300 Subject: [PATCH 365/474] docs: add comment --- Endpoint/Controllers/V1/AuthController.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 2fb1e7a..3946c56 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -29,12 +29,21 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass Path = UrlHelper.GetSubPathWithoutFirstApiName + "api" }; + /// + /// Gets the list of available OAuth providers with their respective redirect URIs. + /// + /// A list of available providers. [HttpGet("GetAvailableProviders")] public ActionResult> GetUrls() => Ok(oAuthService .GetAvailableProviders(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!)) .ConvertToDto()); + /// + /// Logs in a user using their username or email and password. + /// + /// The login request containing username/email and password. + /// A TwoFactorAuthentication token if the login is successful; otherwise, a BadRequest response. [HttpPost("Login")] [BadRequestResponse] public async Task> Login([FromBody] LoginRequest request) @@ -63,6 +72,11 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass return Ok(tokenResult.ConvertToDto()); } + /// + /// Performs two-factor authentication for the user. + /// + /// The request containing the method and code for two-factor authentication. + /// A boolean indicating whether the two-factor authentication was successful. [HttpPost("2FA")] [BadRequestResponse] public async Task> TwoFactorAuth([FromBody] TwoFactorAuthRequest request) -- 2.43.0 From 2c0912297101a2733946ba9ae23dc86db56fab8b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 4 Nov 2024 03:15:13 +0300 Subject: [PATCH 366/474] fix: add cookie expire time --- Security/Common/Domain/RequestContextInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/Common/Domain/RequestContextInfo.cs b/Security/Common/Domain/RequestContextInfo.cs index 7eee08d..28b0cef 100644 --- a/Security/Common/Domain/RequestContextInfo.cs +++ b/Security/Common/Domain/RequestContextInfo.cs @@ -22,7 +22,7 @@ internal class RequestContextInfo if (string.IsNullOrEmpty(fingerprint)) { fingerprint = Guid.NewGuid().ToString().Replace("-", "") + GeneratorKey.GenerateString(32); - cookieOptions.SetCookie(context, CookieNames.FingerprintToken, fingerprint); + cookieOptions.SetCookie(context, CookieNames.FingerprintToken, fingerprint, DateTimeOffset.UtcNow.AddYears(10)); } UserAgent = userAgent; -- 2.43.0 From 5437623a20f9403da23ccb2c3251690dc0b52400 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:13:27 +0300 Subject: [PATCH 367/474] build: update ref --- Endpoint/Endpoint.csproj | 47 ++++++++++++++------------ Security/Security.csproj | 9 ++--- SqlData/Application/Application.csproj | 12 +++---- SqlData/Persistence/Persistence.csproj | 17 ++++++---- 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index da40f60..7271f7a 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0-rc4 - 1.0.2.4 - 1.0.2.4 + 1.0-rc5 + 1.0.2.5 + 1.0.2.5 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -26,37 +26,40 @@ - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + - - - - - - - - - - + + + + + + + + + + + diff --git a/Security/Security.csproj b/Security/Security.csproj index 8e35bb3..bb2469b 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.1.0 - 1.1.3.0 - 1.1.3.0 + 1.1.1 + 1.1.3.1 + 1.1.3.1 Mirea.Api.Security $(AssemblyName) Library @@ -15,9 +15,10 @@ - + + diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 1062253..b7f766d 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -5,18 +5,18 @@ disable enable Winsomnia - 1.0.2 - 1.0.3.2 - 1.0.3.2 + 1.0.3 + 1.0.3.3 + 1.0.3.3 Mirea.Api.DataAccess.Application $(AssemblyName) - - + + - + diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 433daa1..df77b4e 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.2 - 1.0.3.2 - 1.0.3.2 + 1.0.3 + 1.0.3.3 + 1.0.3.3 Mirea.Api.DataAccess.Persistence $(AssemblyName) @@ -16,11 +16,14 @@ - - - - + + + + + + + -- 2.43.0 From 182235c4cd1a5e73807cfba097efe6ddb2256690 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:14:04 +0300 Subject: [PATCH 368/474] feat: add generator base32 for totp --- Security/Services/GeneratorKey.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Security/Services/GeneratorKey.cs b/Security/Services/GeneratorKey.cs index b71bfab..80a5b8e 100644 --- a/Security/Services/GeneratorKey.cs +++ b/Security/Services/GeneratorKey.cs @@ -25,6 +25,10 @@ public static class GeneratorKey .ToArray()); } + public static string GenerateAlphaNumericBase32Compatible(int size, string? excludes = null, + string? includes = null) => + GenerateAlphaNumeric(size, excludes + "0189", includes); + public static ReadOnlySpan GenerateBytes(int size) { var key = new byte[size]; -- 2.43.0 From 08aeb7ea3c58f70346d0c56735bf626f5c06dfa2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:23:23 +0300 Subject: [PATCH 369/474] sec: get links to the backend to initiate the receipt of provider data --- Endpoint/Controllers/V1/AuthController.cs | 35 +++++++++++++++++++---- Security/Services/OAuthService.cs | 32 ++++++++++++--------- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 3946c56..9b199fc 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -15,7 +15,9 @@ using Mirea.Api.Security.Common.Domain; using Mirea.Api.Security.Services; using System; using System.Collections.Generic; +using System.Security.Claims; using System.Threading.Tasks; +using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; namespace Mirea.Api.Endpoint.Controllers.V1; @@ -30,13 +32,36 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass }; /// - /// Gets the list of available OAuth providers with their respective redirect URIs. + /// Initiates the OAuth2 authorization process for the selected provider. /// - /// A list of available providers. - [HttpGet("GetAvailableProviders")] - public ActionResult> GetUrls() => + /// + /// This method generates a redirect URL for the selected provider and redirects the user to it. + /// + /// The identifier of the OAuth provider to authorize with. + /// A redirect to the OAuth provider's authorization URL. + /// Thrown if the specified provider is not valid. + [HttpGet("AuthorizeOAuth2")] + [MaintenanceModeIgnore] + public ActionResult AuthorizeOAuth2([FromQuery] int provider) + { + if (!Enum.IsDefined(typeof(OAuthProvider), provider)) + throw new ControllerArgumentException("There is no selected provider"); + + return Redirect(oAuthService.GetProviderRedirect(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!), (OAuthProvider)provider).AbsoluteUri); + } + + /// + /// Retrieves a list of available OAuth providers with their corresponding authorization URLs. + /// + /// + /// This allows the client to fetch all possible OAuth options and the URLs required to initiate authorization. + /// + /// A list of available providers and their redirect URLs. + [HttpGet("AvailableProviders")] + [MaintenanceModeIgnore] + public ActionResult> AvailableProviders() => Ok(oAuthService - .GetAvailableProviders(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!)) + .GetAvailableProviders(HttpContext, HttpContext.GetApiUrl(Url.Action("AuthorizeOAuth2")!)) .ConvertToDto()); /// diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 7721b86..b23d9da 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -97,20 +97,26 @@ public class OAuthService(ILogger logger, Dictionary (x.Key, new Uri(ProviderData[x.Key].RedirectUrl.TrimEnd('/') + - string.Format(redirectUri, - x.Value.ClientId, - ProviderData[x.Key].Scope, - Enum.GetName(x.Key)))) - ).ToArray(); + public Uri GetProviderRedirect(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUri, OAuthProvider provider) + { + var providerData = providers[provider]; + + var redirectUrl = $"?client_id={providerData.ClientId}" + + "&response_type=code" + + $"&redirect_uri={redirectUri}" + + $"&scope={ProviderData[provider].Scope}" + + $"&state={new RequestContextInfo(context, cookieOptions).Fingerprint}_{Enum.GetName(provider)}"; + + + + return new Uri(ProviderData[provider].RedirectUrl + redirectUrl); + } + + public (OAuthProvider Provider, Uri Redirect)[] GetAvailableProviders(HttpContext context, string redirectUri) + { + return providers.Select(x => (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key))) + .ToArray(); } public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) -- 2.43.0 From 598ebabc5c102c36207dc54f6938bb783d14e55f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:24:33 +0300 Subject: [PATCH 370/474] sec: use HMAC to encrypt state --- Security/DependencyInjection.cs | 2 +- Security/Services/OAuthService.cs | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs index aa36553..dc87c7d 100644 --- a/Security/DependencyInjection.cs +++ b/Security/DependencyInjection.cs @@ -61,7 +61,7 @@ public static class DependencyInjection providers.Add(provider, (clientId, secret)); } - services.AddSingleton(provider => new OAuthService(provider.GetRequiredService>(), providers)); + services.AddSingleton(provider => new OAuthService(provider.GetRequiredService>(), providers, configuration["SECURITY_ENCRYPTION_TOKEN"]!)); return services; } diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index b23d9da..b2ef0ee 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -10,13 +10,15 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Security; +using System.Security.Cryptography; +using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Mirea.Api.Security.Services; -public class OAuthService(ILogger logger, Dictionary providers) +public class OAuthService(ILogger logger, Dictionary providers, string secretKey) { private static readonly Dictionary ProviderData = new() { @@ -97,6 +99,12 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary logger, Dictionary LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) { - var requestContext = new RequestContextInfo(context, cookieOptions); - var partsState = state.Split('_'); if (!Enum.TryParse(partsState.Last(), true, out var provider) || @@ -133,9 +139,10 @@ public class OAuthService(ILogger logger, Dictionary Date: Wed, 18 Dec 2024 07:27:57 +0300 Subject: [PATCH 371/474] feat: give the user the ability to make a password policy --- ApiDto/Common/PasswordPolicy.cs | 32 +++++++++++++++ .../MapperDto/PasswordPolicyConverter.cs | 23 +++++++++++ .../Configuration/SetupController.cs | 15 +++++++ Endpoint/Controllers/V1/AuthController.cs | 11 +++-- Endpoint/Controllers/V1/SecurityController.cs | 26 ++++++++++++ Security/Common/Domain/PasswordPolicy.cs | 15 +++++++ Security/Services/PasswordHashService.cs | 14 +------ Security/Services/PasswordPolicyService.cs | 40 +++++++++++++++++++ 8 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 ApiDto/Common/PasswordPolicy.cs create mode 100644 Endpoint/Common/MapperDto/PasswordPolicyConverter.cs create mode 100644 Endpoint/Controllers/V1/SecurityController.cs create mode 100644 Security/Common/Domain/PasswordPolicy.cs create mode 100644 Security/Services/PasswordPolicyService.cs diff --git a/ApiDto/Common/PasswordPolicy.cs b/ApiDto/Common/PasswordPolicy.cs new file mode 100644 index 0000000..bd308e6 --- /dev/null +++ b/ApiDto/Common/PasswordPolicy.cs @@ -0,0 +1,32 @@ +namespace Mirea.Api.Dto.Common; + +/// +/// Represents the password policy settings for user authentication. +/// +public class PasswordPolicy +{ + /// + /// Gets or sets the minimum length required for a password. + /// + public int MinimumLength { get; set; } + + /// + /// Gets or sets a value indicating whether at least one letter is required in the password. + /// + public bool RequireLetter { get; set; } + + /// + /// Gets or sets a value indicating whether the password must contain both lowercase and uppercase letters. + /// + public bool RequireLettersDifferentCase { get; set; } + + /// + /// Gets or sets a value indicating whether at least one digit is required in the password. + /// + public bool RequireDigit { get; set; } + + /// + /// Gets or sets a value indicating whether at least one special character is required in the password. + /// + public bool RequireSpecialCharacter { get; set; } +} diff --git a/Endpoint/Common/MapperDto/PasswordPolicyConverter.cs b/Endpoint/Common/MapperDto/PasswordPolicyConverter.cs new file mode 100644 index 0000000..85d27d4 --- /dev/null +++ b/Endpoint/Common/MapperDto/PasswordPolicyConverter.cs @@ -0,0 +1,23 @@ +using Mirea.Api.Dto.Common; + +namespace Mirea.Api.Endpoint.Common.MapperDto; + +public static class PasswordPolicyConverter +{ + public static Security.Common.Domain.PasswordPolicy ConvertFromDto(this PasswordPolicy policy) => + new(policy.MinimumLength, + policy.RequireLetter, + policy.RequireLettersDifferentCase, + policy.RequireDigit, + policy.RequireSpecialCharacter); + + public static PasswordPolicy ConvertToDto(this Security.Common.Domain.PasswordPolicy policy) => + new() + { + MinimumLength = policy.MinimumLength, + RequireLetter = policy.RequireLetter, + RequireDigit = policy.RequireDigit, + RequireSpecialCharacter = policy.RequireSpecialCharacter, + RequireLettersDifferentCase = policy.RequireLettersDifferentCase + }; +} \ No newline at end of file diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index d0033ea..cbb449f 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -25,6 +25,7 @@ using System.Linq; using System.Net.Mail; using System.Runtime.InteropServices; using System.Security.Cryptography; +using PasswordPolicy = Mirea.Api.Dto.Common.PasswordPolicy; namespace Mirea.Api.Endpoint.Controllers.Configuration; @@ -340,6 +341,20 @@ public class SetupController( return true; } + [HttpPost("SetPasswordPolicy")] + [TokenAuthentication] + public ActionResult SetPasswordPolicy([FromBody] PasswordPolicy? policy = null) + { + GeneralConfig.PasswordPolicy = policy?.ConvertFromDto() ?? new Security.Common.Domain.PasswordPolicy(); + cache.Set("password", true); + return true; + } + + [HttpGet("PasswordPolicyConfiguration")] + [TokenAuthentication] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult PasswordPolicyConfiguration() => + cache.TryGetValue("password", out _) ? Ok(GeneralConfig.PasswordPolicy) : NoContent(); [HttpPost("Submit")] [TokenAuthentication] diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 9b199fc..b5ef2b8 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -149,13 +149,16 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass [BadRequestResponse] public ActionResult RenewPassword([FromBody] string? password = null) { + var passwordPolicy = generalConfig.Value.PasswordPolicy; + var passwordPolicyService = new PasswordPolicyService(passwordPolicy); + if (string.IsNullOrEmpty(password)) password = string.Empty; - else if (!PasswordHashService.HasPasswordInPolicySecurity(password)) - throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character."); + else + passwordPolicyService.ValidatePasswordOrThrow(password); - while (!PasswordHashService.HasPasswordInPolicySecurity(password)) - password = GeneratorKey.GenerateAlphaNumeric(16, includes: "!@#%^"); + while (!passwordPolicyService.TryValidatePassword(password)) + password = GeneratorKey.GenerateAlphaNumeric(passwordPolicy.MinimumLength + 2, includes: "!@#%^"); var (salt, hash) = passwordService.HashPassword(password); diff --git a/Endpoint/Controllers/V1/SecurityController.cs b/Endpoint/Controllers/V1/SecurityController.cs new file mode 100644 index 0000000..165bd85 --- /dev/null +++ b/Endpoint/Controllers/V1/SecurityController.cs @@ -0,0 +1,26 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Mirea.Api.Dto.Common; +using Mirea.Api.Endpoint.Common.Attributes; +using Mirea.Api.Endpoint.Common.MapperDto; +using Mirea.Api.Endpoint.Configuration.Model; +using QRCoder; +using System; +using System.Drawing; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +public class SecurityController(IOptionsSnapshot generalConfig) : BaseController +{ + /// + /// Retrieves the current password policy for user authentication. + /// + /// + /// The current password policy + /// + [HttpGet("PasswordPolicy")] + public ActionResult PasswordPolicy() => + Ok(generalConfig.Value.PasswordPolicy.ConvertToDto()); +} \ No newline at end of file diff --git a/Security/Common/Domain/PasswordPolicy.cs b/Security/Common/Domain/PasswordPolicy.cs new file mode 100644 index 0000000..fd1ee57 --- /dev/null +++ b/Security/Common/Domain/PasswordPolicy.cs @@ -0,0 +1,15 @@ +namespace Mirea.Api.Security.Common.Domain; + +public class PasswordPolicy( + int minimumLength = 8, + bool requireLetter = true, + bool requireLettersDifferentCase = true, + bool requireDigit = true, + bool requireSpecialCharacter = true) +{ + public int MinimumLength { get; set; } = minimumLength; + public bool RequireLetter { get; set; } = requireLetter; + public bool RequireLettersDifferentCase { get; set; } = requireLettersDifferentCase; + public bool RequireDigit { get; set; } = requireDigit; + public bool RequireSpecialCharacter { get; set; } = requireSpecialCharacter; +} \ No newline at end of file diff --git a/Security/Services/PasswordHashService.cs b/Security/Services/PasswordHashService.cs index df16f75..8673222 100644 --- a/Security/Services/PasswordHashService.cs +++ b/Security/Services/PasswordHashService.cs @@ -1,11 +1,10 @@ using Konscious.Security.Cryptography; using System; using System.Text; -using System.Text.RegularExpressions; namespace Mirea.Api.Security.Services; -public partial class PasswordHashService +public class PasswordHashService { public int SaltSize { private get; init; } public int HashSize { private get; init; } @@ -54,15 +53,4 @@ public partial class PasswordHashService public bool VerifyPassword(string password, string saltBase64, string hashBase64) => VerifyPassword(password, Convert.FromBase64String(saltBase64), Convert.FromBase64String(hashBase64)); - - public static bool HasPasswordInPolicySecurity(string password) => - password.Length >= 8 && - PasswordExistSpecialSymbol().IsMatch(password) && - PasswordExistUpperLetter().IsMatch(password); - - [GeneratedRegex("[A-Z]+")] - private static partial Regex PasswordExistUpperLetter(); - - [GeneratedRegex("[!@#$%^&*]+")] - private static partial Regex PasswordExistSpecialSymbol(); } \ No newline at end of file diff --git a/Security/Services/PasswordPolicyService.cs b/Security/Services/PasswordPolicyService.cs new file mode 100644 index 0000000..63f421e --- /dev/null +++ b/Security/Services/PasswordPolicyService.cs @@ -0,0 +1,40 @@ +using Mirea.Api.Security.Common.Domain; +using System.Linq; +using System.Security; + +namespace Mirea.Api.Security.Services; + +public class PasswordPolicyService(PasswordPolicy policy) +{ + public void ValidatePasswordOrThrow(string password) + { + if (password.Length < policy.MinimumLength) + throw new SecurityException($"Password must be at least {policy.MinimumLength} characters long."); + + if (policy.RequireLetter && !password.Any(char.IsLetter)) + throw new SecurityException("Password must contain at least one letter."); + + if (policy.RequireLettersDifferentCase && !password.Any(char.IsLower) && !password.Any(char.IsUpper)) + throw new SecurityException("Password must contain at least one lowercase and uppercase letter."); + + if (policy.RequireDigit && !password.Any(char.IsDigit)) + throw new SecurityException("Password must contain at least one digit."); + + if (policy.RequireSpecialCharacter && password.All(char.IsLetterOrDigit)) + throw new SecurityException("Password must contain at least one special character."); + } + + public bool TryValidatePassword(string password) + { + try + { + ValidatePasswordOrThrow(password); + } + catch + { + return false; + } + + return true; + } +} \ No newline at end of file -- 2.43.0 From 74ba4e901a3613d5e628af1c30def7ca2edd60ba Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:29:05 +0300 Subject: [PATCH 372/474] fix: replace localhost to 127.0.0.1 --- Endpoint/Common/Services/UrlHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Common/Services/UrlHelper.cs b/Endpoint/Common/Services/UrlHelper.cs index 3de08b9..0c2a4f5 100644 --- a/Endpoint/Common/Services/UrlHelper.cs +++ b/Endpoint/Common/Services/UrlHelper.cs @@ -45,7 +45,7 @@ public static class UrlHelper public static string GetApiUrl(this HttpContext context, string apiPath = "") { var scheme = GetCurrentScheme(context); - var domain = GetCurrentDomain(context).TrimEnd('/'); + var domain = GetCurrentDomain(context).TrimEnd('/').Replace("localhost", "127.0.0.1"); var port = context.Request.Host.Port; var portString = port.HasValue && port != 80 && port != 443 ? $":{port}" : string.Empty; -- 2.43.0 From 25eddbe7767f2c923166492d90fda9eeb47a189d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:29:31 +0300 Subject: [PATCH 373/474] feat: add generator totp qr-code --- Endpoint/Controllers/V1/SecurityController.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Endpoint/Controllers/V1/SecurityController.cs b/Endpoint/Controllers/V1/SecurityController.cs index 165bd85..5889acb 100644 --- a/Endpoint/Controllers/V1/SecurityController.cs +++ b/Endpoint/Controllers/V1/SecurityController.cs @@ -14,6 +14,64 @@ namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] public class SecurityController(IOptionsSnapshot generalConfig) : BaseController { + /// + /// Generates an SVG QR code for TOTP setup with customizable colors and size. + /// + /// The TOTP secret key to embed in the QR code. + /// The label to display with the QR code (usually username or email). + /// Background color for the QR code in hex format (e.g., "#FFFFFF" for white). Defaults to transparent. + /// Foreground color for the QR code in hex format (e.g., "#000000" for black), defaults to black. + /// The pixel size of the QR code image (width and height). + /// Error correction level (low, medium, high, or very high). Valid values: L, M, Q, H. + /// An SVG string of the generated QR code. + [HttpGet("GenerateTotpQrCode")] + [Produces("image/svg+xml")] + [MaintenanceModeIgnore] + public IActionResult GenerateTotpQrCode( + [FromQuery] string totpKey, + [FromQuery] string label, + [FromQuery] string? backgroundColor = null, + [FromQuery] string foregroundColor = "#000000", + [FromQuery] int size = 250, + [FromQuery] string? errorCorrectionLevel = "M") + { + try + { + var bgColor = string.IsNullOrEmpty(backgroundColor) ? Color.Transparent : ColorTranslator.FromHtml(backgroundColor); + var fgColor = ColorTranslator.FromHtml(foregroundColor); + + var eccLevel = errorCorrectionLevel?.ToUpper() switch + { + "L" => QRCodeGenerator.ECCLevel.L, + "Q" => QRCodeGenerator.ECCLevel.Q, + "H" => QRCodeGenerator.ECCLevel.H, + _ => QRCodeGenerator.ECCLevel.M + }; + + var issuer = Uri.EscapeDataString("Mirea Schedule"); + + // Generate TOTP URI (otpauth://totp/issuer:username?secret=KEY&issuer=issuer) + var totpUri = $"otpauth://totp/{issuer}:{label}?secret={totpKey}&issuer={issuer}"; + + using var qrGenerator = new QRCodeGenerator(); + var qrCodeData = qrGenerator.CreateQrCode(totpUri, eccLevel); + + using var qrCode = new SvgQRCode(qrCodeData); + + var svgImage = qrCode.GetGraphic( + pixelsPerModule: size / 25, + darkColorHex: $"#{fgColor.R:X2}{fgColor.G:X2}{fgColor.B:X2}", + lightColorHex: $"#{bgColor.R:X2}{bgColor.G:X2}{bgColor.B:X2}" + ); + + return Content(svgImage, "image/svg+xml"); + } + catch (Exception ex) + { + return BadRequest($"Failed to generate QR code: {ex.Message}"); + } + } + /// /// Retrieves the current password policy for user authentication. /// -- 2.43.0 From 5e072d88c206b4c9b79032afb1ab90368065dc22 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:29:58 +0300 Subject: [PATCH 374/474] fix: converter dto --- Endpoint/Common/MapperDto/AvailableProvidersConverter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs b/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs index 02021bd..9a01f17 100644 --- a/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs +++ b/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs @@ -17,11 +17,11 @@ public static class AvailableProvidersConverter _ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null) }; - public static List ConvertToDto(this (OAuthProvider Provider, Uri Redirect)[] data) => - data.Select(x => new AvailableProvidersResponse() + public static List ConvertToDto(this (OAuthProvider Provider, Uri Redirect)[] data) => + data.Select(x => new AvailableOAuthProvidersResponse() { ProviderName = Enum.GetName(x.Provider)!, Provider = x.Provider.ConvertToDto(), - Redirect = x.Redirect + Redirect = x.Redirect.ToString() }).ToList(); } \ No newline at end of file -- 2.43.0 From 39208037f000f74e6761ba6642413771c693e060 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:30:23 +0300 Subject: [PATCH 375/474] fix: add method for ignoring mode attribute --- Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs b/Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs index 6f69b7a..3d804ca 100644 --- a/Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs +++ b/Endpoint/Common/Attributes/MaintenanceModeIgnoreAttribute.cs @@ -2,5 +2,5 @@ namespace Mirea.Api.Endpoint.Common.Attributes; -[AttributeUsage(AttributeTargets.Class, Inherited = false)] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)] public class MaintenanceModeIgnoreAttribute : Attribute; \ No newline at end of file -- 2.43.0 From 8250957b8599621edba3d4b4ba6fcc9bfdac476d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:30:47 +0300 Subject: [PATCH 376/474] refactor: use another origin --- Endpoint/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 4ce387a..b643ee8 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -76,9 +76,7 @@ public class Program policy.AllowAnyMethod(); policy.AllowAnyHeader(); policy.AllowCredentials(); -#if DEBUG - policy.WithOrigins("http://localhost:4200"); -#endif + policy.SetIsOriginAllowed(_ => true); }); }); -- 2.43.0 From cff42d0a31792e9ad35b6e3af3ca3c361fa990ca Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:31:16 +0300 Subject: [PATCH 377/474] feat: add password policy to general config --- Endpoint/Configuration/Model/GeneralConfig.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Endpoint/Configuration/Model/GeneralConfig.cs b/Endpoint/Configuration/Model/GeneralConfig.cs index 454ee90..f363517 100644 --- a/Endpoint/Configuration/Model/GeneralConfig.cs +++ b/Endpoint/Configuration/Model/GeneralConfig.cs @@ -1,5 +1,6 @@ using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; +using Mirea.Api.Security.Common.Domain; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; @@ -18,6 +19,8 @@ public class GeneralConfig : ISaveSettings public ScheduleSettings? ScheduleSettings { get; set; } public EmailSettings? EmailSettings { get; set; } public LogSettings? LogSettings { get; set; } + public PasswordPolicy PasswordPolicy { get; set; } = new(); + public string? SecretForwardToken { get; set; } public void SaveSetting() -- 2.43.0 From fd578aa61e361a0b74a7751e7398a36dc22508da Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:32:00 +0300 Subject: [PATCH 378/474] fix: add condition for token --- .../Controllers/Configuration/SetupController.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index cbb449f..ecc21c2 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -70,7 +70,17 @@ public class SetupController( [HttpGet("CheckToken")] public ActionResult CheckToken([FromQuery] string token) { - if (!setupToken.MatchToken(Convert.FromBase64String(token))) + byte[] tokenBase64; + try + { + tokenBase64 = Convert.FromBase64String(token); + } + catch (FormatException) + { + throw new ControllerArgumentException("A token of the wrong format."); + } + + if (!setupToken.MatchToken(tokenBase64)) return Unauthorized("The token is not valid"); Response.Cookies.Append(TokenAuthenticationAttribute.AuthToken, token, new CookieOptions -- 2.43.0 From e9ff1cabe8e430d8841bef08659fd67b25687f3c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:39:17 +0300 Subject: [PATCH 379/474] feat: add a return of the data that has been configured --- ApiDto/Common/CacheType.cs | 17 ++ ApiDto/Common/DatabaseType.cs | 22 +++ ApiDto/Requests/CreateUserRequest.cs | 9 - ....cs => AvailableOAuthProvidersResponse.cs} | 10 +- .../Responses/Configuration/CacheResponse.cs | 29 +++ .../Configuration/DatabaseResponse.cs | 49 +++++ ApiDto/Responses/TotpKeyResponse.cs | 17 ++ ApiDto/Responses/UserResponse.cs | 35 ++++ .../Core/Startup/CacheConfiguration.cs | 4 +- .../Core/Startup/SecureConfiguration.cs | 4 +- .../Model/GeneralSettings/CacheSettings.cs | 13 +- .../Model/GeneralSettings/DbSettings.cs | 15 +- .../Configuration/SetupController.cs | 186 ++++++++++++++++-- 13 files changed, 362 insertions(+), 48 deletions(-) create mode 100644 ApiDto/Common/CacheType.cs create mode 100644 ApiDto/Common/DatabaseType.cs rename ApiDto/Responses/{AvailableProvidersResponse.cs => AvailableOAuthProvidersResponse.cs} (66%) create mode 100644 ApiDto/Responses/Configuration/CacheResponse.cs create mode 100644 ApiDto/Responses/Configuration/DatabaseResponse.cs create mode 100644 ApiDto/Responses/TotpKeyResponse.cs create mode 100644 ApiDto/Responses/UserResponse.cs diff --git a/ApiDto/Common/CacheType.cs b/ApiDto/Common/CacheType.cs new file mode 100644 index 0000000..d8a719d --- /dev/null +++ b/ApiDto/Common/CacheType.cs @@ -0,0 +1,17 @@ +namespace Mirea.Api.Dto.Common; + +/// +/// Specifies the types of caching mechanisms available. +/// +public enum CacheType +{ + /// + /// Memcached caching type. + /// + Memcached, + + /// + /// Redis caching type. + /// + Redis +} \ No newline at end of file diff --git a/ApiDto/Common/DatabaseType.cs b/ApiDto/Common/DatabaseType.cs new file mode 100644 index 0000000..6e413b2 --- /dev/null +++ b/ApiDto/Common/DatabaseType.cs @@ -0,0 +1,22 @@ +namespace Mirea.Api.Dto.Common; + +/// +/// Specifies the types of databases supported. +/// +public enum DatabaseType +{ + /// + /// MySQL database type. + /// + Mysql, + + /// + /// SQLite database type. + /// + Sqlite, + + /// + /// PostgreSQL database type. + /// + PostgresSql +} \ No newline at end of file diff --git a/ApiDto/Requests/CreateUserRequest.cs b/ApiDto/Requests/CreateUserRequest.cs index 3bcf70c..e1b3a4c 100644 --- a/ApiDto/Requests/CreateUserRequest.cs +++ b/ApiDto/Requests/CreateUserRequest.cs @@ -10,27 +10,18 @@ public class CreateUserRequest /// /// Gets or sets the email address of the user. /// - /// - /// The email address is a required field. - /// [Required] public required string Email { get; set; } /// /// Gets or sets the username of the user. /// - /// - /// The username is a required field. - /// [Required] public required string Username { get; set; } /// /// Gets or sets the password of the user. /// - /// - /// The password is a required field. - /// [Required] public required string Password { get; set; } } diff --git a/ApiDto/Responses/AvailableProvidersResponse.cs b/ApiDto/Responses/AvailableOAuthProvidersResponse.cs similarity index 66% rename from ApiDto/Responses/AvailableProvidersResponse.cs rename to ApiDto/Responses/AvailableOAuthProvidersResponse.cs index c4e9dfc..bb8e13c 100644 --- a/ApiDto/Responses/AvailableProvidersResponse.cs +++ b/ApiDto/Responses/AvailableOAuthProvidersResponse.cs @@ -1,16 +1,17 @@ using Mirea.Api.Dto.Common; -using System; +using System.ComponentModel.DataAnnotations; namespace Mirea.Api.Dto.Responses; /// /// Represents the response containing information about available OAuth providers. /// -public class AvailableProvidersResponse +public class AvailableOAuthProvidersResponse { /// /// Gets or sets the name of the OAuth provider. /// + [Required] public required string ProviderName { get; set; } /// @@ -19,7 +20,8 @@ public class AvailableProvidersResponse public OAuthProvider Provider { get; set; } /// - /// Gets or sets the redirect URI for the OAuth provider. + /// Gets or sets the redirect URL for the OAuth provider's authorization process. /// - public required Uri Redirect { get; set; } + [Required] + public required string Redirect { get; set; } } \ No newline at end of file diff --git a/ApiDto/Responses/Configuration/CacheResponse.cs b/ApiDto/Responses/Configuration/CacheResponse.cs new file mode 100644 index 0000000..70671de --- /dev/null +++ b/ApiDto/Responses/Configuration/CacheResponse.cs @@ -0,0 +1,29 @@ +using Mirea.Api.Dto.Common; + +namespace Mirea.Api.Dto.Responses.Configuration; + +/// +/// Represents a response containing cache configuration details. +/// +public class CacheResponse +{ + /// + /// Gets or sets the type of cache database. + /// + public CacheType Type { get; set; } + + /// + /// Gets or sets the server address. + /// + public string? Server { get; set; } + + /// + /// Gets or sets the port number. + /// + public int Port { get; set; } + + /// + /// Gets or sets the password. + /// + public string? Password { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/Configuration/DatabaseResponse.cs b/ApiDto/Responses/Configuration/DatabaseResponse.cs new file mode 100644 index 0000000..76baad7 --- /dev/null +++ b/ApiDto/Responses/Configuration/DatabaseResponse.cs @@ -0,0 +1,49 @@ +using Mirea.Api.Dto.Common; + +namespace Mirea.Api.Dto.Responses.Configuration; + +/// +/// Represents a response containing database configuration details. +/// +public class DatabaseResponse +{ + /// + /// Gets or sets the type of database. + /// + public DatabaseType Type { get; set; } + + /// + /// Gets or sets the server address. + /// + public string? Server { get; set; } + + /// + /// Gets or sets the port number. + /// + public int Port { get; set; } + + /// + /// Gets or sets the database name. + /// + public string? Database { get; set; } + + /// + /// Gets or sets the username. + /// + public string? User { get; set; } + + /// + /// Gets or sets a value indicating whether SSL is enabled. + /// + public bool Ssl { get; set; } + + /// + /// Gets or sets the password. + /// + public string? Password { get; set; } + + /// + /// Gets or sets the path to database. Only for Sqlite + /// + public string? PathToDatabase { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/TotpKeyResponse.cs b/ApiDto/Responses/TotpKeyResponse.cs new file mode 100644 index 0000000..70a5865 --- /dev/null +++ b/ApiDto/Responses/TotpKeyResponse.cs @@ -0,0 +1,17 @@ +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents the response containing the TOTP (Time-Based One-Time Password) key details. +/// +public class TotpKeyResponse +{ + /// + /// Gets or sets the secret key used for TOTP generation. + /// + public required string Secret { get; set; } + + /// + /// Gets or sets the image (QR code) representing the TOTP key. + /// + public required string Image { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/UserResponse.cs b/ApiDto/Responses/UserResponse.cs new file mode 100644 index 0000000..16f8a9d --- /dev/null +++ b/ApiDto/Responses/UserResponse.cs @@ -0,0 +1,35 @@ +using Mirea.Api.Dto.Common; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents a response containing user information. +/// +public class UserResponse +{ + /// + /// Gets or sets the email address of the user. + /// + [Required] + public required string Email { get; set; } + + /// + /// Gets or sets the username of the user. + /// + [Required] + public required string Username { get; set; } + + /// + /// Gets or sets a value indicating whether the user has two-factor authentication enabled. + /// + [Required] + public bool TwoFactorAuthenticatorEnabled { get; set; } + + /// + /// Gets or sets a collection of OAuth providers used by the user. + /// + [Required] + public required IEnumerable UsedOAuthProviders { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs b/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs index ca3ddb8..7cac5c9 100644 --- a/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/CacheConfiguration.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.Dto.Common; using Mirea.Api.Endpoint.Configuration.Model; -using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; namespace Mirea.Api.Endpoint.Configuration.Core.Startup; @@ -10,7 +10,7 @@ public static class CacheConfiguration public static IServiceCollection AddCustomRedis(this IServiceCollection services, IConfiguration configuration, IHealthChecksBuilder? healthChecksBuilder = null) { var cache = configuration.Get()?.CacheSettings; - if (cache?.TypeDatabase != CacheSettings.CacheEnum.Redis) + if (cache?.TypeDatabase != CacheType.Redis) return services; services.AddStackExchangeRedisCache(options => diff --git a/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs b/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs index f9f7a81..c2671d7 100644 --- a/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SecureConfiguration.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.Dto.Common; using Mirea.Api.Endpoint.Common.Services.Security; using Mirea.Api.Endpoint.Configuration.Model; -using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; using Mirea.Api.Security; using Mirea.Api.Security.Common.Interfaces; @@ -16,7 +16,7 @@ public static class SecureConfiguration services.AddSingleton(); - if (configuration.Get()?.CacheSettings?.TypeDatabase == CacheSettings.CacheEnum.Redis) + if (configuration.Get()?.CacheSettings?.TypeDatabase == CacheType.Redis) services.AddSingleton(); else services.AddSingleton(); diff --git a/Endpoint/Configuration/Model/GeneralSettings/CacheSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/CacheSettings.cs index c5e5dc9..af0acb3 100644 --- a/Endpoint/Configuration/Model/GeneralSettings/CacheSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/CacheSettings.cs @@ -1,4 +1,5 @@ -using Mirea.Api.Endpoint.Configuration.Validation.Attributes; +using Mirea.Api.Dto.Common; +using Mirea.Api.Endpoint.Configuration.Validation.Attributes; using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; @@ -6,18 +7,12 @@ namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; [RequiredSettings] public class CacheSettings : IIsConfigured { - public enum CacheEnum - { - Memcached, - Redis - } - - public CacheEnum TypeDatabase { get; set; } + public CacheType TypeDatabase { get; set; } public string? ConnectionString { get; set; } public bool IsConfigured() { - return TypeDatabase == CacheEnum.Memcached || + return TypeDatabase == CacheType.Memcached || !string.IsNullOrEmpty(ConnectionString); } } \ No newline at end of file diff --git a/Endpoint/Configuration/Model/GeneralSettings/DbSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/DbSettings.cs index 28921e9..c839fd4 100644 --- a/Endpoint/Configuration/Model/GeneralSettings/DbSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/DbSettings.cs @@ -1,4 +1,5 @@ using Mirea.Api.DataAccess.Persistence.Common; +using Mirea.Api.Dto.Common; using Mirea.Api.Endpoint.Configuration.Validation.Attributes; using Mirea.Api.Endpoint.Configuration.Validation.Interfaces; using System; @@ -9,22 +10,16 @@ namespace Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; [RequiredSettings] public class DbSettings : IIsConfigured { - public enum DatabaseEnum - { - Mysql, - Sqlite, - PostgresSql - } - public DatabaseEnum TypeDatabase { get; set; } + public DatabaseType TypeDatabase { get; set; } public required string ConnectionStringSql { get; set; } [JsonIgnore] public DatabaseProvider DatabaseProvider => TypeDatabase switch { - DatabaseEnum.PostgresSql => DatabaseProvider.Postgresql, - DatabaseEnum.Mysql => DatabaseProvider.Mysql, - DatabaseEnum.Sqlite => DatabaseProvider.Sqlite, + DatabaseType.PostgresSql => DatabaseProvider.Postgresql, + DatabaseType.Mysql => DatabaseProvider.Mysql, + DatabaseType.Sqlite => DatabaseProvider.Sqlite, _ => throw new ArgumentOutOfRangeException() }; diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index ecc21c2..e1780f2 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -4,15 +4,21 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Requests.Configuration; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Dto.Responses.Configuration; using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.Interfaces; +using Mirea.Api.Endpoint.Common.MapperDto; 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.Security.Common.Domain; using Mirea.Api.Security.Services; using MySqlConnector; using Npgsql; @@ -36,7 +42,8 @@ public class SetupController( ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache, - PasswordHashService passwordHashService) : BaseController + PasswordHashService passwordHashService, + IOptionsSnapshot user) : BaseController { private const string CacheGeneralKey = "config_general"; private const string CacheAdminKey = "config_admin"; @@ -95,7 +102,12 @@ public class SetupController( return Ok(true); } - private ActionResult SetDatabase(string connectionString, DbSettings.DatabaseEnum databaseType) + [HttpGet("IsConfiguredToken")] + [TokenAuthentication] + public ActionResult IsConfiguredToken() => + Ok(true); + + private void SetDatabase(string connectionString, DatabaseType databaseType) where TConnection : class, IDbConnection, new() where TException : Exception { @@ -118,8 +130,6 @@ public class SetupController( TypeDatabase = databaseType }; GeneralConfig = general; - - return Ok(true); } catch (TException ex) { @@ -138,7 +148,20 @@ public class SetupController( if (request.Ssl) connectionString += ";SSL Mode=Require;"; - return SetDatabase(connectionString, DbSettings.DatabaseEnum.PostgresSql); + + SetDatabase(connectionString, DatabaseType.PostgresSql); + cache.Set("database", new DatabaseResponse + { + Type = DatabaseType.PostgresSql, + Database = request.Database, + Password = request.Password, + Ssl = request.Ssl, + Port = request.Port, + Server = request.Server, + User = request.User + }); + + return Ok(true); } [HttpPost("SetMysql")] @@ -152,7 +175,19 @@ public class SetupController( if (request.Ssl) connectionString += "SslMode=Require;"; - return SetDatabase(connectionString, DbSettings.DatabaseEnum.Mysql); + SetDatabase(connectionString, DatabaseType.Mysql); + cache.Set("database", new DatabaseResponse + { + Type = DatabaseType.Mysql, + Database = request.Database, + Password = request.Password, + Ssl = request.Ssl, + Port = request.Port, + Server = request.Server, + User = request.User + }); + + return Ok(true); } [HttpPost("SetSqlite")] @@ -179,14 +214,26 @@ public class SetupController( var filePath = Path.Combine(path, "database.db3"); var connectionString = $"Data Source={filePath}"; - var result = SetDatabase(connectionString, DbSettings.DatabaseEnum.Sqlite); + SetDatabase(connectionString, DatabaseType.Sqlite); foreach (var file in Directory.GetFiles(path)) System.IO.File.Delete(file); - return result; + cache.Set("database", new DatabaseResponse + { + Type = DatabaseType.Sqlite, + PathToDatabase = path + }); + + return Ok(true); } + [HttpGet("DatabaseConfiguration")] + [TokenAuthentication] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult DatabaseConfiguration() => + cache.TryGetValue("database", out var response) ? Ok(response) : NoContent(); + [HttpPost("SetRedis")] [TokenAuthentication] [BadRequestResponse] @@ -205,10 +252,17 @@ public class SetupController( general.CacheSettings = new CacheSettings { ConnectionString = connectionString, - TypeDatabase = CacheSettings.CacheEnum.Redis + TypeDatabase = CacheType.Redis }; GeneralConfig = general; + cache.Set("cache", new CacheResponse + { + Type = CacheType.Redis, + Server = request.Server, + Password = request.Password, + Port = request.Port + }); return Ok(true); } catch (Exception ex) @@ -226,20 +280,29 @@ public class SetupController( general.CacheSettings = new CacheSettings { ConnectionString = null, - TypeDatabase = CacheSettings.CacheEnum.Memcached + TypeDatabase = CacheType.Memcached }; GeneralConfig = general; + cache.Set("cache", new CacheResponse + { + Type = CacheType.Memcached + }); return Ok(true); } + [HttpGet("CacheConfiguration")] + [TokenAuthentication] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult CacheConfiguration() => + cache.TryGetValue("cache", out var response) ? Ok(response) : NoContent(); + [HttpPost("CreateAdmin")] [TokenAuthentication] [BadRequestResponse] public ActionResult CreateAdmin([FromBody] CreateUserRequest user) { - if (!PasswordHashService.HasPasswordInPolicySecurity(user.Password)) - throw new ControllerArgumentException("The password must be at least 8 characters long and contain at least one uppercase letter and one special character."); + new PasswordPolicyService(GeneralConfig.PasswordPolicy).ValidatePasswordOrThrow(user.Password); if (!MailAddress.TryCreate(user.Email, out _)) throw new ControllerArgumentException("The email address is incorrect."); @@ -258,6 +321,73 @@ public class SetupController( return Ok(true); } + [HttpGet("UpdateAdminConfiguration")] + [TokenAuthentication] + public ActionResult UpdateAdminConfiguration() + { + if (string.IsNullOrEmpty(user.Value.Email)) + return Ok(); + + if (!cache.TryGetValue(CacheAdminKey, out var admin)) + { + admin = user.Value; + cache.Set(CacheAdminKey, admin); + return Ok(); + } + + admin!.OAuthProviders = user.Value.OAuthProviders; + + if (string.IsNullOrEmpty(admin.Email)) + admin.Email = user.Value.Email; + + if (string.IsNullOrEmpty(admin.Username)) + admin.Username = user.Value.Username; + + cache.Set(CacheAdminKey, admin); + return Ok(); + } + + [HttpGet("AdminConfiguration")] + [TokenAuthentication] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult AdminConfiguration() => + cache.TryGetValue(CacheAdminKey, out var admin) ? Ok(new UserResponse() + { + Email = admin!.Email, + Username = admin.Username, + TwoFactorAuthenticatorEnabled = admin.TwoFactorAuthenticator != TwoFactorAuthenticator.None, + UsedOAuthProviders = admin.OAuthProviders == null ? [] : admin.OAuthProviders.Keys.Select(x => x.ConvertToDto()) + }) : NoContent(); + + [HttpGet("GenerateTotpKey")] + [TokenAuthentication] + public ActionResult GenerateTotpKey() + { + if (cache.TryGetValue("totpSecret", out var secret)) + return secret!; + + secret = GeneratorKey.GenerateAlphaNumericBase32Compatible(16); + cache.Set("totpSecret", secret); + return secret; + } + + [HttpGet("VerifyTotp")] + [TokenAuthentication] + public ActionResult VerifyTotp([FromQuery] string code) + { + var isCorrect = cache.TryGetValue("totpSecret", out var secret) && + new TotpService(secret!).VerifyToken(code); + + if (!isCorrect || !cache.TryGetValue(CacheAdminKey, out var admin)) + return false; + + admin!.Secret = secret; + admin.TwoFactorAuthenticator = TwoFactorAuthenticator.Totp; + cache.Set(CacheAdminKey, admin); + + return true; + } + [HttpPost("SetLogging")] [TokenAuthentication] [BadRequestResponse] @@ -292,9 +422,22 @@ public class SetupController( general.LogSettings = settings; GeneralConfig = general; + cache.Set("logging", new LoggingRequest + { + EnableLogToFile = settings.EnableLogToFile, + LogFileName = settings.LogFileName, + LogFilePath = settings.LogFilePath + }); + return true; } + [HttpGet("LoggingConfiguration")] + [TokenAuthentication] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult LoggingConfiguration() => + cache.TryGetValue("logging", out var data) ? Ok(data) : NoContent(); + [HttpPost("SetEmail")] [TokenAuthentication] [BadRequestResponse] @@ -318,9 +461,16 @@ public class SetupController( general.EmailSettings = settings; GeneralConfig = general; + cache.Set("email", settings); return true; } + [HttpGet("EmailConfiguration")] + [TokenAuthentication] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult EmailConfiguration() => + cache.TryGetValue("email", out var data) ? Ok(data) : NoContent(); + [HttpPost("SetSchedule")] [TokenAuthentication] [BadRequestResponse] @@ -349,8 +499,20 @@ public class SetupController( GeneralConfig = general; + cache.Set("schedule", new ScheduleConfigurationRequest() + { + StartTerm = general.ScheduleSettings.StartTerm, + CronUpdateSchedule = general.ScheduleSettings.CronUpdateSchedule + }); return true; } + + [HttpGet("ScheduleConfiguration")] + [TokenAuthentication] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult ScheduleConfiguration() => + cache.TryGetValue("schedule", out var data) ? Ok(data) : NoContent(); + [HttpPost("SetPasswordPolicy")] [TokenAuthentication] public ActionResult SetPasswordPolicy([FromBody] PasswordPolicy? policy = null) -- 2.43.0 From 5cc54eac44610d8ee66572c06cc2109f56e088fa Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 18 Dec 2024 07:40:07 +0300 Subject: [PATCH 380/474] feat: add a method for getting data from OAuth --- Endpoint/Controllers/V1/AuthController.cs | 91 ++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index b5ef2b8..4453f65 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -22,7 +22,7 @@ using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] -public class AuthController(IOptionsSnapshot user, AuthService auth, PasswordHashService passwordService) : BaseController +public class AuthController(IOptionsSnapshot user, IOptionsSnapshot generalConfig, AuthService auth, PasswordHashService passwordService, OAuthService oAuthService) : BaseController { private CookieOptionsParameters GetCookieParams() => new() @@ -31,6 +31,95 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass Path = UrlHelper.GetSubPathWithoutFirstApiName + "api" }; + private static string GenerateHtmlResponse(string title, string message, OAuthProvider? provider, bool isError = false) + { + string messageColor = isError ? "red" : "white"; + string script = ""; + + return $"{title}

{title}

{message}

Это информационная страница. Вы можете закрыть её.

{script}"; + } + + [HttpGet("OAuth2")] + [BadRequestResponse] + [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] + [Produces("text/html")] + [MaintenanceModeIgnore] + public async Task OAuth2([FromQuery] string code, [FromQuery] string state) + { + var userId = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + string title; + string message; + OAuthProvider provider; + OAuthUser oAuthUser; + + try + { + (provider, oAuthUser) = await oAuthService.LoginOAuth(HttpContext, GetCookieParams(), + HttpContext.GetApiUrl(Url.Action("OAuth2")!), code, state); + } + catch (Exception e) + { + title = "Произошла ошибка при общении с провайдером OAuth!"; + message = e.Message; + return Content(GenerateHtmlResponse(title, message, null, true), "text/html"); + } + + var userEntity = user.Value; + + if (userId != null) + { + userEntity.OAuthProviders ??= new Dictionary(); + + if (!userEntity.OAuthProviders.TryAdd(provider, oAuthUser)) + { + title = "Ошибка связи аккаунта!"; + message = "Этот OAuth провайдер уже связан с вашей учетной записью. Пожалуйста, используйте другого провайдера или удалите связь с аккаунтом."; + return Content(GenerateHtmlResponse(title, message, provider, true), "text/html"); + } + + userEntity.SaveSetting(); + + title = "Учетная запись успешно связана."; + message = "Вы успешно связали свою учетную запись с провайдером OAuth. Вы можете продолжить использовать приложение."; + return Content(GenerateHtmlResponse(title, message, provider), "text/html"); + } + + if (userEntity.OAuthProviders != null && + userEntity.OAuthProviders.TryGetValue(provider, out var userOAuth) && + userOAuth.Id == oAuthUser.Id) + { + await auth.LoginOAuthAsync(GetCookieParams(), HttpContext, new User + { + Id = 1.ToString(), + Username = userEntity.Username, + Email = userEntity.Email, + PasswordHash = userEntity.PasswordHash, + Salt = userEntity.Salt, + TwoFactorAuthenticator = userEntity.TwoFactorAuthenticator, + SecondFactorToken = userEntity.Secret, + OAuthProviders = userEntity.OAuthProviders + }, provider); + + title = "Успешный вход в аккаунт."; + message = "Вы успешно вошли в свою учетную запись. Добро пожаловать!"; + return Content(GenerateHtmlResponse(title, message, provider), "text/html"); + } + + title = "Вы успешно зарегистрированы."; + message = "Процесс завершен. Вы можете закрыть эту страницу."; + userEntity.Email = string.IsNullOrEmpty(oAuthUser.Email) ? string.Empty : oAuthUser.Email; + userEntity.Username = string.IsNullOrEmpty(oAuthUser.Username) ? string.Empty : oAuthUser.Username; + userEntity.OAuthProviders ??= []; + userEntity.OAuthProviders.Add(provider, oAuthUser); + userEntity.SaveSetting(); + return Content(GenerateHtmlResponse(title, message, provider), "text/html"); + } + /// /// Initiates the OAuth2 authorization process for the selected provider. /// -- 2.43.0 From f2e79e51f217de53d785fac250405a017055f10d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 22 Dec 2024 05:13:46 +0300 Subject: [PATCH 381/474] sec: transfer user verification to the appropriate service --- Endpoint/Controllers/V1/AuthController.cs | 6 +----- Security/Services/AuthService.cs | 9 +++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 4453f65..529b29a 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -164,10 +164,6 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot user, IOptionsSnapshot revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); - private async Task VerifyUserOrThrowError(RequestContextInfo requestContext, User user, string password, + private async Task VerifyUserOrThrowError(RequestContextInfo requestContext, User user, string password, string username, CancellationToken cancellation = default) { - if (passwordService.VerifyPassword(password, user.Salt, user.PasswordHash)) + if ((user.Email.Equals(username, StringComparison.OrdinalIgnoreCase) || user.Username.Equals(username, StringComparison.OrdinalIgnoreCase)) && + passwordService.VerifyPassword(password, user.Salt, user.PasswordHash)) return; var failedLoginCacheName = $"{requestContext.Fingerprint}_login_failed"; @@ -151,11 +152,11 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return true; } - public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, CancellationToken cancellation = default) + public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, string username, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); - await VerifyUserOrThrowError(requestContext, user, password, cancellation); + await VerifyUserOrThrowError(requestContext, user, password, username, cancellation); if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) { -- 2.43.0 From e4b942d06223dd8566624939114cc7dbfa8a01ef Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 22 Dec 2024 05:25:19 +0300 Subject: [PATCH 382/474] refactor: to return the result according to the RFC 7807 standard and add a traceId --- ApiDto/Responses/ErrorResponse.cs | 22 ------ .../Attributes/BadRequestResponseAttribute.cs | 3 +- .../Attributes/NotFoundResponseAttribute.cs | 3 +- .../CustomExceptionHandlerMiddleware.cs | 68 +++++++++++-------- .../Core/Startup/LoggerConfiguration.cs | 11 ++- .../Configuration/SetupController.cs | 3 +- Endpoint/Controllers/V1/AuthController.cs | 14 ++-- .../Controllers/V1/ProfessorController.cs | 2 +- Endpoint/Controllers/V1/ScheduleController.cs | 13 ++-- Endpoint/Controllers/V1/SecurityController.cs | 3 +- 10 files changed, 68 insertions(+), 74 deletions(-) delete mode 100644 ApiDto/Responses/ErrorResponse.cs 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}

Это информационная страница. Вы можете закрыть её.

{(!string.IsNullOrEmpty(traceId) ? $"TraceId={traceId}" : string.Empty)}
{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}"); } } -- 2.43.0 From 544ad6e79125f6f501798f074a62d1a6e70c4bbf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 22 Dec 2024 05:26:10 +0300 Subject: [PATCH 383/474] feat: add an annotation to the data --- Endpoint/Controllers/V1/DisciplineController.cs | 3 ++- Endpoint/Controllers/V1/FacultyController.cs | 3 ++- Endpoint/Controllers/V1/GroupController.cs | 3 ++- Endpoint/Controllers/V1/ProfessorController.cs | 6 ++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Endpoint/Controllers/V1/DisciplineController.cs b/Endpoint/Controllers/V1/DisciplineController.cs index e02b94d..cde5ebc 100644 --- a/Endpoint/Controllers/V1/DisciplineController.cs +++ b/Endpoint/Controllers/V1/DisciplineController.cs @@ -6,6 +6,7 @@ using Mirea.Api.DataAccess.Application.Cqrs.Discipline.Queries.GetDisciplineList using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -23,7 +24,7 @@ public class DisciplineController(IMediator mediator) : BaseController /// Paginated list of disciplines. [HttpGet] [BadRequestResponse] - public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? pageSize) { var result = await mediator.Send(new GetDisciplineListQuery() { diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index 3b5af11..f21352e 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -5,6 +5,7 @@ using Mirea.Api.DataAccess.Application.Cqrs.Faculty.Queries.GetFacultyList; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -22,7 +23,7 @@ public class FacultyController(IMediator mediator) : BaseController /// Paginated list of faculties. [HttpGet] [BadRequestResponse] - public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? pageSize) { var result = await mediator.Send(new GetFacultyListQuery() { diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index ebac4cb..4041b84 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -7,6 +7,7 @@ using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -37,7 +38,7 @@ public class GroupController(IMediator mediator) : BaseController /// A list of groups. [HttpGet] [BadRequestResponse] - public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? pageSize) { var result = await mediator.Send(new GetGroupListQuery() { diff --git a/Endpoint/Controllers/V1/ProfessorController.cs b/Endpoint/Controllers/V1/ProfessorController.cs index 989105a..0c1a171 100644 --- a/Endpoint/Controllers/V1/ProfessorController.cs +++ b/Endpoint/Controllers/V1/ProfessorController.cs @@ -6,7 +6,9 @@ using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorDetail using Mirea.Api.DataAccess.Application.Cqrs.Professor.Queries.GetProfessorList; using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; +using Mirea.Api.Endpoint.Common.Exceptions; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -24,7 +26,7 @@ public class ProfessorController(IMediator mediator) : BaseController /// A list of professors. [HttpGet] [BadRequestResponse] - public async Task>> Get([FromQuery] int? page, [FromQuery] int? pageSize) + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? pageSize) { var result = await mediator.Send(new GetProfessorListQuery() { @@ -78,7 +80,7 @@ public class ProfessorController(IMediator mediator) : BaseController [HttpGet("{name:required}")] [BadRequestResponse] [NotFoundResponse] - public async Task>> GetDetails(string name) + public async Task>> GetDetails([MinLength(4)] string name) { if (string.IsNullOrEmpty(name) || name.Length < 4) throw new ControllerArgumentException($"The minimum number of characters is 4 (current: {name.Length})."); -- 2.43.0 From 7bafbb95c40f9ddf4c6d39193e60882641a3c8d0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 22 Dec 2024 05:27:07 +0300 Subject: [PATCH 384/474] build: update ref --- Endpoint/Endpoint.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 7271f7a..2175de9 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0-rc5 - 1.0.2.5 - 1.0.2.5 + 1.0-rc6 + 1.0.2.6 + 1.0.2.6 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -41,7 +41,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -50,7 +50,7 @@ - + -- 2.43.0 From 7b94f9cc1ffcd6cb03adfd0bfb36c9b751bd5ccf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 22 Dec 2024 05:51:11 +0300 Subject: [PATCH 385/474] refactor: for maintenance mode, return the standard error --- .../Common/Exceptions/ServerUnavailableException.cs | 8 ++++++++ .../Middleware/CustomExceptionHandlerMiddleware.cs | 8 ++++++++ .../Core/Middleware/MaintenanceModeMiddleware.cs | 12 +++++------- 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 Endpoint/Common/Exceptions/ServerUnavailableException.cs diff --git a/Endpoint/Common/Exceptions/ServerUnavailableException.cs b/Endpoint/Common/Exceptions/ServerUnavailableException.cs new file mode 100644 index 0000000..7e58d61 --- /dev/null +++ b/Endpoint/Common/Exceptions/ServerUnavailableException.cs @@ -0,0 +1,8 @@ +using System; + +namespace Mirea.Api.Endpoint.Common.Exceptions; + +public class ServerUnavailableException(string message, bool addRetryAfter) : Exception(message) +{ + public bool AddRetryAfter { get; } = addRetryAfter; +} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index 3c65dd8..23c848b 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -71,6 +71,14 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger Date: Sun, 22 Dec 2024 06:39:12 +0300 Subject: [PATCH 386/474] refactor: remove unused code --- .../Configuration/Core/Middleware/MaintenanceModeMiddleware.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs b/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs index 68825dc..adfee69 100644 --- a/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs @@ -30,8 +30,6 @@ public class MaintenanceModeMiddleware(RequestDelegate next, IMaintenanceModeSer throw new ServerUnavailableException( "The service is currently not configured. Go to the setup page if you are an administrator or try again later.", false); - - await context.Response.WriteAsync(error); } } } \ No newline at end of file -- 2.43.0 From 85722f85524319daf5b2d028c42505bf507e161c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 22 Dec 2024 07:13:59 +0300 Subject: [PATCH 387/474] feat: add integration with seq --- .../Requests/Configuration/LoggingRequest.cs | 13 +++++++++ .../Middleware/MaintenanceModeMiddleware.cs | 1 - .../Core/Startup/LoggerConfiguration.cs | 6 ++++ .../Model/GeneralSettings/LogSettings.cs | 2 ++ .../Configuration/SetupController.cs | 28 ++++++++++++++++++- Endpoint/Endpoint.csproj | 1 + 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/ApiDto/Requests/Configuration/LoggingRequest.cs b/ApiDto/Requests/Configuration/LoggingRequest.cs index eba6428..3108f8a 100644 --- a/ApiDto/Requests/Configuration/LoggingRequest.cs +++ b/ApiDto/Requests/Configuration/LoggingRequest.cs @@ -22,4 +22,17 @@ public class LoggingRequest /// Gets or sets the log file path. ///
public string? LogFilePath { get; set; } + + /// + /// Gets or sets the API key for integrating with Seq, a log aggregation service. + /// If provided, logs will be sent to a Seq server using this API key. + /// + public string? ApiKeySeq { get; set; } + + /// + /// Gets or sets the server URL for the Seq logging service. + /// This property specifies the Seq server endpoint to which logs will be sent. + /// If is provided, logs will be sent to this server. + /// + public string? ApiServerSeq { get; set; } } \ No newline at end of file diff --git a/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs b/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs index adfee69..8377a93 100644 --- a/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/MaintenanceModeMiddleware.cs @@ -24,7 +24,6 @@ public class MaintenanceModeMiddleware(RequestDelegate next, IMaintenanceModeSer context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; context.Response.ContentType = "plain/text"; - string error; if (maintenanceModeService.IsMaintenanceMode) throw new ServerUnavailableException("The service is currently undergoing maintenance. Please try again later.", true); diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index 2e10259..238ad22 100644 --- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -45,6 +45,12 @@ public static class LoggerConfiguration rollingInterval: RollingInterval.Day); } +#if !DEBUG + if (generalConfig != null && !string.IsNullOrEmpty(generalConfig.ApiServerSeq) && + Uri.TryCreate(generalConfig.ApiServerSeq, UriKind.Absolute, out var _)) + configuration.WriteTo.Seq(generalConfig.ApiServerSeq, apiKey: generalConfig.ApiKeySeq); +#endif + configuration .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning) diff --git a/Endpoint/Configuration/Model/GeneralSettings/LogSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/LogSettings.cs index 3f8deb1..c5fafe0 100644 --- a/Endpoint/Configuration/Model/GeneralSettings/LogSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/LogSettings.cs @@ -9,6 +9,8 @@ public class LogSettings : IIsConfigured public bool EnableLogToFile { get; set; } public string? LogFilePath { get; set; } public string? LogFileName { get; set; } + public string? ApiKeySeq { get; set; } + public string? ApiServerSeq { get; set; } public bool IsConfigured() { diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 5ab4671..ab17c64 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -22,6 +22,7 @@ using Mirea.Api.Security.Common.Domain; using Mirea.Api.Security.Services; using MySqlConnector; using Npgsql; +using Serilog; using StackExchange.Redis; using System; using System.Collections.Generic; @@ -408,6 +409,29 @@ public class SetupController( } }; + if (!string.IsNullOrEmpty(request?.ApiServerSeq)) + { + settings.ApiServerSeq = request.ApiServerSeq; + settings.ApiKeySeq = request.ApiKeySeq; + + try + { + Log.Logger = new LoggerConfiguration() + .WriteTo.Seq(settings.ApiServerSeq, apiKey: settings.ApiKeySeq) + .CreateLogger(); + + Log.Warning("Testing configuration Seq."); + } + catch + { + // ignoring + } + finally + { + Log.CloseAndFlush(); + } + } + if (settings.EnableLogToFile) { if (string.IsNullOrEmpty(settings.LogFileName)) @@ -427,7 +451,9 @@ public class SetupController( { EnableLogToFile = settings.EnableLogToFile, LogFileName = settings.LogFileName, - LogFilePath = settings.LogFilePath + LogFilePath = settings.LogFilePath, + ApiKeySeq = settings.ApiKeySeq, + ApiServerSeq = settings.ApiServerSeq }); return true; diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 2175de9..2692750 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -50,6 +50,7 @@ + -- 2.43.0 From b82fbc491fc6e12434bcb91c2dfb815b09687dec Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 22 Dec 2024 07:21:51 +0300 Subject: [PATCH 388/474] fix: add missing using --- Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index 238ad22..ad013e7 100644 --- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -8,6 +8,7 @@ using Serilog.Context; using Serilog.Events; using Serilog.Filters; using Serilog.Formatting.Compact; +using System; using System.Diagnostics; using System.IO; @@ -45,11 +46,9 @@ public static class LoggerConfiguration rollingInterval: RollingInterval.Day); } -#if !DEBUG if (generalConfig != null && !string.IsNullOrEmpty(generalConfig.ApiServerSeq) && Uri.TryCreate(generalConfig.ApiServerSeq, UriKind.Absolute, out var _)) configuration.WriteTo.Seq(generalConfig.ApiServerSeq, apiKey: generalConfig.ApiKeySeq); -#endif configuration .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) -- 2.43.0 From 3e05863aea1337275b1276f08b91b307c378996b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 22 Dec 2024 07:25:41 +0300 Subject: [PATCH 389/474] refactor: clean code --- Endpoint/Controllers/V1/AuthController.cs | 6 +++--- Security/Services/AuthService.cs | 2 +- Security/Services/OAuthService.cs | 9 ++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 3c26de0..8e52435 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -75,7 +75,7 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot(); + userEntity.OAuthProviders ??= []; if (!userEntity.OAuthProviders.TryAdd(provider, oAuthUser)) { @@ -105,7 +105,7 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot user, IOptionsSnapshot> AvailableProviders() => Ok(oAuthService - .GetAvailableProviders(HttpContext, HttpContext.GetApiUrl(Url.Action("AuthorizeOAuth2")!)) + .GetAvailableProviders(HttpContext.GetApiUrl(Url.Action("AuthorizeOAuth2")!)) .ConvertToDto()); /// diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 7eb71a7..33c5d27 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -107,7 +107,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.Fingerprint); } - public async Task LoginOAuthAsync(CookieOptionsParameters cookieOptions, HttpContext context, User user, OAuthProvider provider, CancellationToken cancellation = default) + public async Task LoginOAuthAsync(CookieOptionsParameters cookieOptions, HttpContext context, User user, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index b2ef0ee..0f99548 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -108,9 +108,9 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key))) - .ToArray(); + return [.. providers.Select(x => (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; } public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) -- 2.43.0 From 202d20bb25a723331e2627157301455dd5c0a991 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 06:28:06 +0300 Subject: [PATCH 390/474] build: add providers --- .gitea/workflows/deploy-stage.yaml | 14 +++++++++++++- README.md | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/deploy-stage.yaml b/.gitea/workflows/deploy-stage.yaml index 85abe79..ef51849 100644 --- a/.gitea/workflows/deploy-stage.yaml +++ b/.gitea/workflows/deploy-stage.yaml @@ -1,4 +1,4 @@ -name: Build and Deploy Docker Container +name: Build and Deploy Docker Container on: push: @@ -52,6 +52,12 @@ jobs: SECURITY_HASH_SIZE: ${{ secrets.SECURITY_HASH_SIZE }} SECURITY_HASH_TOKEN: ${{ secrets.SECURITY_HASH_TOKEN }} SECURITY_SALT_SIZE: ${{ secrets.SECURITY_SALT_SIZE }} + GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + YANDEX_CLIENT_ID: ${{ secrets.YANDEX_CLIENT_ID }} + YANDEX_CLIENT_SECRET: ${{ secrets.YANDEX_CLIENT_SECRET }} + MAILRU_CLIENT_ID: ${{ secrets.MAILRU_CLIENT_ID }} + MAILRU_CLIENT_SECRET: ${{ secrets.MAILRU_CLIENT_SECRET }} run: | ssh-keyscan $SSH_HOST >> ~/.ssh/known_hosts ssh $SSH_USER@$SSH_HOST " @@ -78,6 +84,12 @@ jobs: -e ACTUAL_SUB_PATH=api \ -e SWAGGER_SUB_PATH=swagger \ -e TZ=Europe/Moscow \ + -e GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID \ + -e GOOGLE_CLIENT_SECRET=GOOGLE_CLIENT_SECRET \ + -e YANDEX_CLIENT_ID=YANDEX_CLIENT_ID \ + -e YANDEX_CLIENT_SECRET=YANDEX_CLIENT_SECRET \ + -e MAILRU_CLIENT_ID=MAILRU_CLIENT_ID \ + -e MAILRU_CLIENT_SECRET=MAILRU_CLIENT_SECRET \ $DOCKER_IMAGE " diff --git a/README.md b/README.md index 02d2276..0450250 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ To set up the `redirect URL` when registering and logging in using OAuth 2, use "{schema}://{domain}{portString}{ACTUAL_SUB_PATH}/api/v1/Auth/OAuth2" ``` -** Where:** +**Where:** - `{schema}` is the protocol you are using (`http` or `https'). - `{domain}` is your domain (for example, `mydomain.com ` or IP address). -- 2.43.0 From 78254ed23d8b47b8c40142527f5173184a679f0d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 06:28:28 +0300 Subject: [PATCH 391/474] fix: change same name --- .../Controllers/Configuration/SetupController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index ab17c64..2ea17e2 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -302,19 +302,19 @@ public class SetupController( [HttpPost("CreateAdmin")] [TokenAuthentication] [BadRequestResponse] - public ActionResult CreateAdmin([FromBody] CreateUserRequest user) + public ActionResult CreateAdmin([FromBody] CreateUserRequest userRequest) { - new PasswordPolicyService(GeneralConfig.PasswordPolicy).ValidatePasswordOrThrow(user.Password); + new PasswordPolicyService(GeneralConfig.PasswordPolicy).ValidatePasswordOrThrow(userRequest.Password); - if (!MailAddress.TryCreate(user.Email, out _)) + if (!MailAddress.TryCreate(userRequest.Email, out _)) throw new ControllerArgumentException("The email address is incorrect."); - var (salt, hash) = passwordHashService.HashPassword(user.Password); + var (salt, hash) = passwordHashService.HashPassword(userRequest.Password); var admin = new Admin { - Username = user.Username, - Email = user.Email, + Username = userRequest.Username, + Email = userRequest.Email, PasswordHash = hash, Salt = salt }; -- 2.43.0 From 57b9819d1387713387d7ac1653985775b3da2a1f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 06:29:00 +0300 Subject: [PATCH 392/474] feat: add maintenance ignore --- Endpoint/Controllers/V1/SecurityController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Controllers/V1/SecurityController.cs b/Endpoint/Controllers/V1/SecurityController.cs index 4024d71..b789e0d 100644 --- a/Endpoint/Controllers/V1/SecurityController.cs +++ b/Endpoint/Controllers/V1/SecurityController.cs @@ -80,6 +80,7 @@ public class SecurityController(IOptionsSnapshot generalConfig) : /// The current password policy /// [HttpGet("PasswordPolicy")] + [MaintenanceModeIgnore] public ActionResult PasswordPolicy() => Ok(generalConfig.Value.PasswordPolicy.ConvertToDto()); } \ No newline at end of file -- 2.43.0 From 55562a9f004892f953c5b8e66775cc1de1306651 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 06:29:29 +0300 Subject: [PATCH 393/474] feat: add default response type --- Endpoint/Program.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index b643ee8..cd07ac5 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -58,7 +59,10 @@ public class Program builder.Host.AddCustomSerilog(); AddDatabase(builder.Services, builder.Configuration, healthCheckBuilder); - builder.Services.AddControllers(); + builder.Services.AddControllers(options => + { + options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status500InternalServerError)); + }); builder.Services.AddSingleton(); builder.Services.AddSingleton(); -- 2.43.0 From e8e94e45a5285addab4e4535c9bb02e96e3c00d3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 06:32:08 +0300 Subject: [PATCH 394/474] fix: add missing using --- Endpoint/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index cd07ac5..b80fa73 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -1,6 +1,7 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -- 2.43.0 From 053f01eec1678531f4e17a92cf3d39cc51a9a103 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 06:56:01 +0300 Subject: [PATCH 395/474] fix: hotfix getting current port --- Endpoint/Common/Services/UrlHelper.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Endpoint/Common/Services/UrlHelper.cs b/Endpoint/Common/Services/UrlHelper.cs index 0c2a4f5..a539c41 100644 --- a/Endpoint/Common/Services/UrlHelper.cs +++ b/Endpoint/Common/Services/UrlHelper.cs @@ -11,6 +11,9 @@ public static class UrlHelper public static string GetCurrentDomain(this HttpContext context) => context.Request.Headers["X-Forwarded-Host"].FirstOrDefault() ?? context.Request.Host.Host; + public static int? GetCurrentPort(this HttpContext context) => + string.IsNullOrEmpty(context.Request.Headers["X-Forwarded-Port"].FirstOrDefault()) ? context.Request.Host.Port : + int.Parse(context.Request.Headers["X-Forwarded-Port"].First()!); private static string CreateSubPath(string? path) { @@ -47,7 +50,7 @@ public static class UrlHelper var scheme = GetCurrentScheme(context); var domain = GetCurrentDomain(context).TrimEnd('/').Replace("localhost", "127.0.0.1"); - var port = context.Request.Host.Port; + var port = GetCurrentPort(context); var portString = port.HasValue && port != 80 && port != 443 ? $":{port}" : string.Empty; return $"{scheme}://{domain}{portString}{GetSubPathWithoutFirstApiName}{apiPath.Trim('/')}"; -- 2.43.0 From 5ff8744a552731c26227db96aad093cd5957010a Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 07:48:28 +0300 Subject: [PATCH 396/474] feat: improve logging --- .../Core/Startup/LoggerConfiguration.cs | 3 ++ Security/Services/AuthService.cs | 15 +++------- Security/Services/OAuthService.cs | 28 +++++++++++++------ 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index ad013e7..0df8831 100644 --- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -65,7 +65,10 @@ public static class LoggerConfiguration return app.Use(async (context, next) => { var traceId = Activity.Current?.Id ?? context.TraceIdentifier; + using (LogContext.PushProperty("TraceId", traceId)) + using (LogContext.PushProperty("UserAgent", context.Request.Headers.UserAgent.ToString())) + using (LogContext.PushProperty("RemoteIPAddress", context.Connection.RemoteIpAddress?.ToString())) { await next(); } diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 33c5d27..a505c09 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -63,19 +63,16 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I if (countFailedLogin > 5) { logger.LogWarning( - "Multiple unsuccessful login attempts for user ID {UserId} from IP {UserIp}. Attempt count: {AttemptNumber}.", + "Multiple unsuccessful login attempts for user ID {UserId}. Attempt count: {AttemptNumber}.", user.Id, - requestContext.Ip, countFailedLogin); throw new SecurityException("Too many unsuccessful login attempts. Please try again later."); } logger.LogInformation( - "Login attempt failed for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.", + "Login attempt failed for user ID {UserId}. Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.", user.Id, - requestContext.Ip, - requestContext.UserAgent, requestContext.Fingerprint, countFailedLogin); @@ -100,10 +97,8 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); logger.LogInformation( - "Login successful for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}.", + "Login successful for user ID {UserId}. Fingerprint: {Fingerprint}.", authToken.UserId, - authToken.Ip, - authToken.UserAgent, authToken.Fingerprint); } @@ -184,10 +179,8 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I cookieOptions.DropCookie(context, CookieNames.AccessToken); cookieOptions.DropCookie(context, CookieNames.RefreshToken); - logger.LogWarning("Token validation failed for user ID {UserId}. IP: {UserIp}, User-Agent: {UserAgent}, Fingerprint: {Fingerprint}. Reason: {Reason}.", + logger.LogWarning("Token validation failed for user ID {UserId}. Fingerprint: {Fingerprint}. Reason: {Reason}.", authToken.UserId, - authToken.Ip, - authToken.UserAgent, authToken.Fingerprint, authToken.RefreshToken != requestContext.RefreshToken ? $"Cached refresh token '{authToken.RefreshToken}' does not match the provided refresh token '{requestContext.RefreshToken}'" : diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 0f99548..d10f568 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -110,21 +110,25 @@ public class OAuthService(ILogger logger, Dictionary (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; - } + public (OAuthProvider Provider, Uri Redirect)[] GetAvailableProviders(string redirectUri) => + [.. providers.Select(x => (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) { @@ -139,11 +143,17 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary Date: Mon, 23 Dec 2024 08:10:19 +0300 Subject: [PATCH 397/474] fix: escape data for state --- Security/Security.csproj | 6 +++--- Security/Services/OAuthService.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Security/Security.csproj b/Security/Security.csproj index bb2469b..6854234 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.1.1 - 1.1.3.1 - 1.1.3.1 + 1.1.3 + 1.1.3.3 + 1.1.3.3 Mirea.Api.Security $(AssemblyName) Library diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index d10f568..562b35b 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -117,7 +117,7 @@ public class OAuthService(ILogger logger, Dictionary Date: Mon, 23 Dec 2024 10:53:32 +0300 Subject: [PATCH 398/474] fix: set minimum level for authorization and authentication to warning Signed-off-by: Polianin Nikita --- Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index 0df8831..a9ed8c5 100644 --- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -53,7 +53,9 @@ public static class LoggerConfiguration configuration .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning); + .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authorization", LogEventLevel.Warning); configuration.Filter.ByExcluding(Matching.WithProperty("SourceContext", sc => sc.Contains("Microsoft.EntityFrameworkCore.Database.Command"))); -- 2.43.0 From 2a33ecbf07890b1546e31658fe8909f113f710e2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 23 Dec 2024 16:54:05 +0300 Subject: [PATCH 399/474] build: fix deploy script Signed-off-by: Polianin Nikita --- .gitea/workflows/deploy-stage.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/deploy-stage.yaml b/.gitea/workflows/deploy-stage.yaml index ef51849..8e22f9b 100644 --- a/.gitea/workflows/deploy-stage.yaml +++ b/.gitea/workflows/deploy-stage.yaml @@ -85,11 +85,11 @@ jobs: -e SWAGGER_SUB_PATH=swagger \ -e TZ=Europe/Moscow \ -e GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID \ - -e GOOGLE_CLIENT_SECRET=GOOGLE_CLIENT_SECRET \ - -e YANDEX_CLIENT_ID=YANDEX_CLIENT_ID \ - -e YANDEX_CLIENT_SECRET=YANDEX_CLIENT_SECRET \ - -e MAILRU_CLIENT_ID=MAILRU_CLIENT_ID \ - -e MAILRU_CLIENT_SECRET=MAILRU_CLIENT_SECRET \ + -e GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET \ + -e YANDEX_CLIENT_ID=$YANDEX_CLIENT_ID \ + -e YANDEX_CLIENT_SECRET=$YANDEX_CLIENT_SECRET \ + -e MAILRU_CLIENT_ID=$MAILRU_CLIENT_ID \ + -e MAILRU_CLIENT_SECRET=$MAILRU_CLIENT_SECRET \ $DOCKER_IMAGE " -- 2.43.0 From 408a95e4b3c55a77c6d5e96bf41b3be5fbe0058e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:43:30 +0300 Subject: [PATCH 400/474] refactor: add .editorconfig and refactor code --- .editorconfig | 278 ++++++++++++++++++ Backend.sln | 1 + .../Common/Attributes/CacheMaxAgeAttribute.cs | 2 +- .../TokenAuthenticationAttribute.cs | 2 +- .../IMaintenanceModeNotConfigureService.cs | 1 - .../Interfaces/IMaintenanceModeService.cs | 2 - .../MapperDto/PairPeriodTimeConverter.cs | 3 +- .../Security/DistributedCacheService.cs | 3 +- .../Services/Security/MemoryCacheService.cs | 3 +- .../BackgroundTasks/ScheduleSyncService.cs | 8 +- .../Core/Startup/EnvironmentConfiguration.cs | 2 +- .../Core/Startup/JwtConfiguration.cs | 6 +- .../SwaggerOptions/ConfigureSwaggerOptions.cs | 3 +- .../Validation/SetupTokenService.cs | 4 +- .../Configuration/SetupController.cs | 6 +- Endpoint/Controllers/V1/AuthController.cs | 13 +- .../Controllers/V1/DisciplineController.cs | 3 +- Endpoint/Controllers/V1/FacultyController.cs | 3 +- Endpoint/Controllers/V1/GroupController.cs | 3 +- Endpoint/Controllers/V1/ImportController.cs | 6 +- .../Controllers/V1/ProfessorController.cs | 3 +- Endpoint/Controllers/V1/ScheduleController.cs | 2 +- Endpoint/Sync/ScheduleSynchronizer.cs | 15 +- .../Domain/OAuth2/OAuthProviderUrisData.cs | 2 +- Security/DependencyInjection.cs | 3 +- Security/Services/GeneratorKey.cs | 2 +- Security/Services/OAuthService.cs | 19 +- Security/Services/PasswordHashService.cs | 4 +- .../CampusBasicInfoVm.cs | 2 +- .../GetCampusDetailsQueryHandler.cs | 3 +- .../GetDisciplineInfoQueryHandler.cs | 3 +- .../GetDisciplineList/DisciplineListVm.cs | 2 +- .../Queries/GetFacultyList/FacultyListVm.cs | 2 +- .../GetFacultyList/FacultyLookupDto.cs | 5 - .../Group/Queries/GetGroupList/GroupListVm.cs | 2 +- .../GetLectureHallList/LectureHallListVm.cs | 2 +- .../GetProfessorInfoQueryHandler.cs | 3 +- .../GetProfessorInfoSearchQueryHandler.cs | 2 +- .../ProfessorInfoListVm.cs | 2 +- .../GetProfessorList/ProfessorListVm.cs | 2 +- .../GetScheduleListQueryHandler.cs | 2 +- .../Queries/GetScheduleList/ScheduleListVm.cs | 2 +- SqlData/Application/DependencyInjection.cs | 4 +- .../Common/ModelBuilderExtensions.cs | 2 +- SqlData/Persistence/DbInitializer.cs | 4 +- 45 files changed, 371 insertions(+), 75 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8016c51 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,278 @@ +# Удалите строку ниже, если вы хотите наследовать параметры .editorconfig из каталогов, расположенных выше в иерархии +root = true + +# Файлы C# +[*.cs] + +#### Основные параметры EditorConfig #### + +# Отступы и интервалы +indent_size = 4 +indent_style = space +tab_width = 4 + +# Предпочтения для новых строк +end_of_line = crlf +insert_final_newline = false + +#### Действия кода .NET #### + +# Члены типа +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Поиск символов +dotnet_search_reference_assemblies = true + +#### Рекомендации по написанию кода .NET #### + +# Упорядочение Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# Предпочтения для this. и Me. +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Параметры использования ключевых слов языка и типов BCL +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Предпочтения для скобок +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Предпочтения модификатора +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Выражения уровень предпочтения +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Предпочтения для полей +dotnet_style_readonly_field = true + +# Настройки параметров +dotnet_code_quality_unused_parameters = non_public + +# Параметры подавления +dotnet_remove_unnecessary_suppression_exclusions = none + +# Предпочтения для новых строк +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = false + +#### Рекомендации по написанию кода C# #### + +# Предпочтения var +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent + +# Члены, заданные выражениями +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent + +# Настройки соответствия шаблонов +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_switch_expression = true:suggestion + +# Настройки проверки на null +csharp_style_conditional_delegate_call = true:suggestion + +# Предпочтения модификатора +csharp_prefer_static_anonymous_function = true:suggestion +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion + +# Предпочтения для блоков кода +csharp_prefer_braces = when_multiline:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = false:silent + +# Выражения уровень предпочтения +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# предпочтения для директивы using +csharp_using_directive_placement = outside_namespace:silent + +# Предпочтения для новых строк +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:silent +csharp_style_allow_embedded_statements_on_same_line_experimental = false:silent + +#### Правила форматирования C# #### + +# Предпочтения для новых строк +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Предпочтения для отступов +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Предпочтения для интервалов +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Предпочтения переноса +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Стили именования #### + +# Правила именования + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = error +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = error +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Спецификации символов + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Стили именования + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_code_quality_unused_parameters = non_public:suggestion +dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = false:silent +dotnet_style_readonly_field = true:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent \ No newline at end of file diff --git a/Backend.sln b/Backend.sln index 5fe3b84..6cb0a6d 100644 --- a/Backend.sln +++ b/Backend.sln @@ -8,6 +8,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", "Elements of the solution", "{3E087889-A4A0-4A55-A07D-7D149A5BC928}" ProjectSection(SolutionItems) = preProject .dockerignore = .dockerignore + .editorconfig = .editorconfig .env = .env .gitattributes = .gitattributes .gitignore = .gitignore diff --git a/Endpoint/Common/Attributes/CacheMaxAgeAttribute.cs b/Endpoint/Common/Attributes/CacheMaxAgeAttribute.cs index f61778f..bce617e 100644 --- a/Endpoint/Common/Attributes/CacheMaxAgeAttribute.cs +++ b/Endpoint/Common/Attributes/CacheMaxAgeAttribute.cs @@ -2,7 +2,7 @@ namespace Mirea.Api.Endpoint.Common.Attributes; -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)] public class CacheMaxAgeAttribute : Attribute { public int MaxAge { get; } diff --git a/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs b/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs index 88db4c1..747bef3 100644 --- a/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs +++ b/Endpoint/Common/Attributes/TokenAuthenticationAttribute.cs @@ -13,7 +13,7 @@ public class TokenAuthenticationAttribute : Attribute, IActionFilter public void OnActionExecuting(ActionExecutingContext context) { var setupToken = context.HttpContext.RequestServices.GetRequiredService(); - if (!context.HttpContext.Request.Cookies.TryGetValue(AuthToken, out string? tokenFromCookie)) + if (!context.HttpContext.Request.Cookies.TryGetValue(AuthToken, out var tokenFromCookie)) { context.Result = new UnauthorizedResult(); return; diff --git a/Endpoint/Common/Interfaces/IMaintenanceModeNotConfigureService.cs b/Endpoint/Common/Interfaces/IMaintenanceModeNotConfigureService.cs index e2e6a5d..fe22e41 100644 --- a/Endpoint/Common/Interfaces/IMaintenanceModeNotConfigureService.cs +++ b/Endpoint/Common/Interfaces/IMaintenanceModeNotConfigureService.cs @@ -3,6 +3,5 @@ public interface IMaintenanceModeNotConfigureService { bool IsMaintenanceMode { get; } - void DisableMaintenanceMode(); } \ No newline at end of file diff --git a/Endpoint/Common/Interfaces/IMaintenanceModeService.cs b/Endpoint/Common/Interfaces/IMaintenanceModeService.cs index 7f2d7cb..57020a2 100644 --- a/Endpoint/Common/Interfaces/IMaintenanceModeService.cs +++ b/Endpoint/Common/Interfaces/IMaintenanceModeService.cs @@ -3,8 +3,6 @@ public interface IMaintenanceModeService { bool IsMaintenanceMode { get; } - void EnableMaintenanceMode(); - void DisableMaintenanceMode(); } \ No newline at end of file diff --git a/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs b/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs index 0f50b43..a9ffffc 100644 --- a/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs +++ b/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs @@ -9,5 +9,6 @@ public static class PairPeriodTimeConverter public static Dictionary ConvertToDto(this IDictionary pairPeriod) => pairPeriod.ToDictionary(kvp => kvp.Key, kvp => new Dto.Common.PairPeriodTime { Start = kvp.Value.Start, End = kvp.Value.End }); - public static Dictionary ConvertFromDto(this IDictionary pairPeriod) => pairPeriod.ToDictionary(kvp => kvp.Key, kvp => new ScheduleSettings.PairPeriodTime(kvp.Value.Start, kvp.Value.End)); + public static Dictionary ConvertFromDto(this IDictionary pairPeriod) => + pairPeriod.ToDictionary(kvp => kvp.Key, kvp => new ScheduleSettings.PairPeriodTime(kvp.Value.Start, kvp.Value.End)); } diff --git a/Endpoint/Common/Services/Security/DistributedCacheService.cs b/Endpoint/Common/Services/Security/DistributedCacheService.cs index 3630ae0..cf5f141 100644 --- a/Endpoint/Common/Services/Security/DistributedCacheService.cs +++ b/Endpoint/Common/Services/Security/DistributedCacheService.cs @@ -9,7 +9,8 @@ namespace Mirea.Api.Endpoint.Common.Services.Security; public class DistributedCacheService(IDistributedCache cache) : ICacheService { - public async Task SetAsync(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default) + public async Task SetAsync(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, TimeSpan? slidingExpiration = null, + CancellationToken cancellationToken = default) { var options = new DistributedCacheEntryOptions { diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs index 4c7f7fd..74c7579 100644 --- a/Endpoint/Common/Services/Security/MemoryCacheService.cs +++ b/Endpoint/Common/Services/Security/MemoryCacheService.cs @@ -9,7 +9,8 @@ namespace Mirea.Api.Endpoint.Common.Services.Security; public class MemoryCacheService(IMemoryCache cache) : ICacheService { - public Task SetAsync(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default) + public Task SetAsync(string key, T value, TimeSpan? absoluteExpirationRelativeToNow = null, TimeSpan? slidingExpiration = null, + CancellationToken cancellationToken = default) { var options = new MemoryCacheEntryOptions { diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs index 8d897ff..726ba8a 100644 --- a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -32,7 +32,7 @@ public class ScheduleSyncService : IHostedService, IDisposable private void OnForceSyncRequested() { - StopAsync(default).ContinueWith(_ => + StopAsync(CancellationToken.None).ContinueWith(_ => { _cancellationTokenSource = new CancellationTokenSource(); ExecuteTask(null); @@ -41,9 +41,9 @@ public class ScheduleSyncService : IHostedService, IDisposable private void OnUpdateIntervalRequested() { - StopAsync(default).ContinueWith(_ => + StopAsync(CancellationToken.None).ContinueWith(_ => { - StartAsync(default); + StartAsync(CancellationToken.None); }); } @@ -109,7 +109,7 @@ public class ScheduleSyncService : IHostedService, IDisposable public void Dispose() { - StopAsync(default).GetAwaiter().GetResult(); + StopAsync(CancellationToken.None).GetAwaiter().GetResult(); _timer?.Dispose(); ScheduleSyncManager.OnForceSyncRequested -= OnForceSyncRequested; ScheduleSyncManager.OnUpdateIntervalRequested -= OnUpdateIntervalRequested; diff --git a/Endpoint/Configuration/Core/Startup/EnvironmentConfiguration.cs b/Endpoint/Configuration/Core/Startup/EnvironmentConfiguration.cs index 2c10eb3..49e8062 100644 --- a/Endpoint/Configuration/Core/Startup/EnvironmentConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/EnvironmentConfiguration.cs @@ -21,7 +21,7 @@ public static class EnvironmentConfiguration var commentIndex = line.IndexOf('#', StringComparison.Ordinal); - string arg = line; + var arg = line; if (commentIndex != -1) arg = arg.Remove(commentIndex, arg.Length - commentIndex); diff --git a/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs b/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs index c6009d0..4188479 100644 --- a/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs @@ -19,12 +19,14 @@ public static class JwtConfiguration var jwtDecrypt = Encoding.UTF8.GetBytes(configuration["SECURITY_ENCRYPTION_TOKEN"] ?? string.Empty); if (jwtDecrypt.Length != 32) - throw new InvalidOperationException("The secret token \"SECURITY_ENCRYPTION_TOKEN\" cannot be less than 32 characters long. Now the size is equal is " + jwtDecrypt.Length); + throw new InvalidOperationException("The secret token \"SECURITY_ENCRYPTION_TOKEN\" cannot be less than 32 characters long. " + + "Now the size is equal is " + jwtDecrypt.Length); var jwtKey = Encoding.UTF8.GetBytes(configuration["SECURITY_SIGNING_TOKEN"] ?? string.Empty); if (jwtKey.Length != 64) - throw new InvalidOperationException("The signature token \"SECURITY_SIGNING_TOKEN\" cannot be less than 64 characters. Now the size is " + jwtKey.Length); + throw new InvalidOperationException("The signature token \"SECURITY_SIGNING_TOKEN\" cannot be less than 64 characters. " + + "Now the size is " + jwtKey.Length); var jwtIssuer = configuration["SECURITY_JWT_ISSUER"]; var jwtAudience = configuration["SECURITY_JWT_AUDIENCE"]; diff --git a/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs index 09520fa..eae4d2e 100644 --- a/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs +++ b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs @@ -23,7 +23,8 @@ public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : { Title = "MIREA Schedule Web API", Version = description.ApiVersion.ToString(), - Description = "This API provides a convenient interface for retrieving data stored in the database. Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.", + Description = "This API provides a convenient interface for retrieving data stored in the database. " + + "Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.", Contact = new OpenApiContact { Name = "Author name", Email = "support@winsomnia.net" }, License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") } }; diff --git a/Endpoint/Configuration/Validation/SetupTokenService.cs b/Endpoint/Configuration/Validation/SetupTokenService.cs index 5ef3559..476eea7 100644 --- a/Endpoint/Configuration/Validation/SetupTokenService.cs +++ b/Endpoint/Configuration/Validation/SetupTokenService.cs @@ -14,8 +14,8 @@ public class SetupTokenService : ISetupToken var token2 = Token.Value.Span; - int result = 0; - for (int i = 0; i < Token.Value.Length; i++) + var result = 0; + for (var i = 0; i < Token.Value.Length; i++) result |= token2[i] ^ token[i]; return result == 0; diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 2ea17e2..e92ca2b 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -144,7 +144,7 @@ public class SetupController( [BadRequestResponse] public ActionResult SetPsql([FromBody] DatabaseRequest request) { - string connectionString = $"Host={request.Server}:{request.Port};Username={request.User};Database={request.Database}"; + var connectionString = $"Host={request.Server}:{request.Port};Username={request.User};Database={request.Database}"; if (request.Password != null) connectionString += $";Password={request.Password}"; if (request.Ssl) @@ -171,7 +171,7 @@ public class SetupController( [BadRequestResponse] public ActionResult SetMysql([FromBody] DatabaseRequest request) { - string connectionString = $"Server={request.Server}:{request.Port};Uid={request.User};Database={request.Database};"; + var connectionString = $"Server={request.Server}:{request.Port};Uid={request.User};Database={request.Database};"; if (request.Password != null) connectionString += $"Pwd={request.Password};"; if (request.Ssl) @@ -241,7 +241,7 @@ public class SetupController( [BadRequestResponse] public ActionResult SetRedis([FromBody] CacheRequest request) { - string connectionString = $"{request.Server}:{request.Port},ssl=false"; + var connectionString = $"{request.Server}:{request.Port},ssl=false"; if (request.Password != null) connectionString += $",password={request.Password}"; diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 8e52435..a9ea5a3 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -23,7 +23,8 @@ using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; namespace Mirea.Api.Endpoint.Controllers.V1; [ApiVersion("1.0")] -public class AuthController(IOptionsSnapshot user, IOptionsSnapshot generalConfig, AuthService auth, PasswordHashService passwordService, OAuthService oAuthService) : BaseController +public class AuthController(IOptionsSnapshot user, IOptionsSnapshot generalConfig, AuthService auth, + PasswordHashService passwordService, OAuthService oAuthService) : BaseController { private CookieOptionsParameters GetCookieParams() => new() @@ -34,8 +35,8 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot user, IOptionsSnapshot user, IOptionsSnapshot diff --git a/Endpoint/Controllers/V1/DisciplineController.cs b/Endpoint/Controllers/V1/DisciplineController.cs index cde5ebc..10e18e5 100644 --- a/Endpoint/Controllers/V1/DisciplineController.cs +++ b/Endpoint/Controllers/V1/DisciplineController.cs @@ -24,7 +24,8 @@ public class DisciplineController(IMediator mediator) : BaseController /// Paginated list of disciplines. [HttpGet] [BadRequestResponse] - public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? pageSize) + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, + [FromQuery][Range(1, int.MaxValue)] int? pageSize) { var result = await mediator.Send(new GetDisciplineListQuery() { diff --git a/Endpoint/Controllers/V1/FacultyController.cs b/Endpoint/Controllers/V1/FacultyController.cs index f21352e..cd69689 100644 --- a/Endpoint/Controllers/V1/FacultyController.cs +++ b/Endpoint/Controllers/V1/FacultyController.cs @@ -23,7 +23,8 @@ public class FacultyController(IMediator mediator) : BaseController /// Paginated list of faculties. [HttpGet] [BadRequestResponse] - public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? pageSize) + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, + [FromQuery][Range(1, int.MaxValue)] int? pageSize) { var result = await mediator.Send(new GetFacultyListQuery() { diff --git a/Endpoint/Controllers/V1/GroupController.cs b/Endpoint/Controllers/V1/GroupController.cs index 4041b84..24bbdd5 100644 --- a/Endpoint/Controllers/V1/GroupController.cs +++ b/Endpoint/Controllers/V1/GroupController.cs @@ -38,7 +38,8 @@ public class GroupController(IMediator mediator) : BaseController /// A list of groups. [HttpGet] [BadRequestResponse] - public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? pageSize) + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, + [FromQuery][Range(1, int.MaxValue)] int? pageSize) { var result = await mediator.Send(new GetGroupListQuery() { diff --git a/Endpoint/Controllers/V1/ImportController.cs b/Endpoint/Controllers/V1/ImportController.cs index 4800c1a..a7882e9 100644 --- a/Endpoint/Controllers/V1/ImportController.cs +++ b/Endpoint/Controllers/V1/ImportController.cs @@ -49,7 +49,7 @@ public class ImportController(IMediator mediator, IOptionsSnapshotA list of professors. [HttpGet] [BadRequestResponse] - public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? pageSize) + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, + [FromQuery][Range(1, int.MaxValue)] int? pageSize) { var result = await mediator.Send(new GetProfessorListQuery() { diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 00baf6e..5e4d48c 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -66,7 +66,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot config, ILogger logger, IMaintenanceModeService maintenanceMode) +internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSnapshot config, ILogger logger, + IMaintenanceModeService maintenanceMode) { private readonly DataRepository _campuses = new([.. dbContext.Campuses]); private readonly DataRepository _disciplines = new([.. dbContext.Disciplines]); @@ -120,7 +121,7 @@ internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSna { hall = []; campuses = []; - for (int i = 0; i < groupInfo.Campuses.Length; i++) + for (var i = 0; i < groupInfo.Campuses.Length; i++) { var campus = groupInfo.Campuses[i]; campuses.Add(_campuses.GetOrCreate( @@ -151,7 +152,7 @@ internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSna Name = groupInfo.Discipline }); - var lesson = _lessons.GetOrCreate(l => + Lesson lesson = _lessons.GetOrCreate(l => l.IsEven == groupInfo.IsEven && l.DayOfWeek == groupInfo.Day && l.PairNumber == groupInfo.Pair && @@ -182,9 +183,9 @@ internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSna return lesson; }); - int maxValue = int.Max(int.Max(professor?.Count ?? -1, hall?.Count ?? -1), 1); + var maxValue = int.Max(int.Max(professor?.Count ?? -1, hall?.Count ?? -1), 1); - for (int i = 0; i < maxValue; i++) + for (var i = 0; i < maxValue; i++) { var prof = professor?.ElementAtOrDefault(i); var lectureHall = hall?.ElementAtOrDefault(i); @@ -226,7 +227,9 @@ internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSna if (pairPeriods == null || startTerm == null) { - logger.LogWarning("It is not possible to synchronize the schedule due to the fact that the {Arg1} or {Arg2} variable is not initialized.", nameof(pairPeriods), nameof(startTerm)); + logger.LogWarning("It is not possible to synchronize the schedule due to the fact that the {Arg1} or {Arg2} variable is not initialized.", + nameof(pairPeriods), + nameof(startTerm)); return; } diff --git a/Security/Common/Domain/OAuth2/OAuthProviderUrisData.cs b/Security/Common/Domain/OAuth2/OAuthProviderUrisData.cs index cca96f2..85a8e0b 100644 --- a/Security/Common/Domain/OAuth2/OAuthProviderUrisData.cs +++ b/Security/Common/Domain/OAuth2/OAuthProviderUrisData.cs @@ -2,7 +2,7 @@ namespace Mirea.Api.Security.Common.Domain.OAuth2; -internal struct OAuthProviderUrisData +internal readonly struct OAuthProviderUrisData { public string RedirectUrl { get; init; } public string TokenUrl { get; init; } diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs index dc87c7d..d3d4b38 100644 --- a/Security/DependencyInjection.cs +++ b/Security/DependencyInjection.cs @@ -61,7 +61,8 @@ public static class DependencyInjection providers.Add(provider, (clientId, secret)); } - services.AddSingleton(provider => new OAuthService(provider.GetRequiredService>(), providers, configuration["SECURITY_ENCRYPTION_TOKEN"]!)); + services.AddSingleton(provider => new OAuthService(provider.GetRequiredService>(), providers, + configuration["SECURITY_ENCRYPTION_TOKEN"]!)); return services; } diff --git a/Security/Services/GeneratorKey.cs b/Security/Services/GeneratorKey.cs index 80a5b8e..5093809 100644 --- a/Security/Services/GeneratorKey.cs +++ b/Security/Services/GeneratorKey.cs @@ -12,7 +12,7 @@ public static class GeneratorKey var random = new Random(); const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - string charsForGenerate = excludes? + var charsForGenerate = excludes? .Aggregate(chars, (current, ex) => current.Replace(ex.ToString(), string.Empty)) ?? chars; if (!string.IsNullOrEmpty(includes)) diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 562b35b..929f1b7 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -51,7 +51,8 @@ public class OAuthService(ILogger logger, Dictionary ExchangeCodeForTokensAsync(string requestUri, string redirectUrl, string code, string clientId, string secret, CancellationToken cancellation) + private static async Task ExchangeCodeForTokensAsync(string requestUri, string redirectUrl, string code, + string clientId, string secret, CancellationToken cancellation) { var tokenRequest = new HttpRequestMessage(HttpMethod.Post, requestUri) { @@ -77,7 +78,8 @@ public class OAuthService(ILogger logger, Dictionary(data); } - private static async Task GetUserProfileAsync(string requestUri, string authHeader, string accessToken, OAuthProvider provider, CancellationToken cancellation) + private static async Task GetUserProfileAsync(string requestUri, string authHeader, string accessToken, OAuthProvider provider, + CancellationToken cancellation) { var request = new HttpRequestMessage(HttpMethod.Get, requestUri); @@ -130,7 +132,8 @@ public class OAuthService(ILogger logger, Dictionary [.. providers.Select(x => (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; - public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) + public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, + string redirectUrl, string code, string state, CancellationToken cancellation = default) { var partsState = state.Split('_'); @@ -160,11 +163,14 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary /// The list of campus basic information. /// - public IList Campuses { get; set; } = new List(); + public IEnumerable Campuses { get; set; } = []; } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs b/SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs index 75af510..ef8848f 100644 --- a/SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs +++ b/SqlData/Application/Cqrs/Campus/Queries/GetCampusDetails/GetCampusDetailsQueryHandler.cs @@ -11,7 +11,8 @@ public class GetCampusDetailsQueryHandler(ICampusDbContext dbContext) : IRequest { public async Task Handle(GetCampusDetailsQuery request, CancellationToken cancellationToken) { - var campus = await dbContext.Campuses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Campus), request.Id); + var campus = await dbContext.Campuses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken) ?? + throw new NotFoundException(typeof(Domain.Schedule.Campus), request.Id); return new CampusDetailsVm() { diff --git a/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs index 007e74f..c3dda8b 100644 --- a/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs +++ b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineDetails/GetDisciplineInfoQueryHandler.cs @@ -11,7 +11,8 @@ public class GetDisciplineInfoQueryHandler(IDisciplineDbContext dbContext) : IRe { public async Task Handle(GetDisciplineInfoQuery request, CancellationToken cancellationToken) { - var discipline = await dbContext.Disciplines.FirstOrDefaultAsync(d => d.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Discipline), request.Id); + var discipline = await dbContext.Disciplines.FirstOrDefaultAsync(d => d.Id == request.Id, cancellationToken) ?? + throw new NotFoundException(typeof(Domain.Schedule.Discipline), request.Id); return new DisciplineInfoVm() { diff --git a/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs index 3f5c835..a968de0 100644 --- a/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs +++ b/SqlData/Application/Cqrs/Discipline/Queries/GetDisciplineList/DisciplineListVm.cs @@ -10,5 +10,5 @@ public class DisciplineListVm /// /// The list of disciplines. /// - public IList Disciplines { get; set; } = new List(); + public IEnumerable Disciplines { get; set; } = []; } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs index 8ad7d4b..c1e0d87 100644 --- a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs +++ b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyListVm.cs @@ -10,5 +10,5 @@ public class FacultyListVm /// /// The list of faculties. /// - public IList Faculties { get; set; } = new List(); + public IEnumerable Faculties { get; set; } = []; } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs index 7a8c615..b88c8bf 100644 --- a/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs +++ b/SqlData/Application/Cqrs/Faculty/Queries/GetFacultyList/FacultyLookupDto.cs @@ -14,9 +14,4 @@ public class FacultyLookupDto /// The name of the faculty. ///
public required string Name { get; set; } - - /// - /// ID indicating the faculty's affiliation to the campus. - /// - public int? CampusId { get; set; } } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs b/SqlData/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs index f4a777c..2edc37a 100644 --- a/SqlData/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs +++ b/SqlData/Application/Cqrs/Group/Queries/GetGroupList/GroupListVm.cs @@ -10,5 +10,5 @@ public class GroupListVm /// /// The list of groups. /// - public IList Groups { get; set; } = new List(); + public IEnumerable Groups { get; set; } = []; } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs index 8338f99..a737e9f 100644 --- a/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs +++ b/SqlData/Application/Cqrs/LectureHall/Queries/GetLectureHallList/LectureHallListVm.cs @@ -10,5 +10,5 @@ public class LectureHallListVm /// /// The list of lecture hall. /// - public IList LectureHalls { get; set; } = new List(); + public IEnumerable LectureHalls { get; set; } = []; } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs index 2f833bf..970af8f 100644 --- a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetails/GetProfessorInfoQueryHandler.cs @@ -11,7 +11,8 @@ public class GetProfessorInfoQueryHandler(IProfessorDbContext dbContext) : IRequ { public async Task Handle(GetProfessorInfoQuery request, CancellationToken cancellationToken) { - var professor = await dbContext.Professors.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Professor), request.Id); + var professor = await dbContext.Professors.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken) ?? + throw new NotFoundException(typeof(Domain.Schedule.Professor), request.Id); return new ProfessorInfoVm() { diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQueryHandler.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQueryHandler.cs index c466adc..c03a1b6 100644 --- a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQueryHandler.cs +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/GetProfessorInfoSearchQueryHandler.cs @@ -27,7 +27,7 @@ public class GetProfessorInfoSearchQueryHandler(IProfessorDbContext dbContext) : Id = x.Id, Name = x.Name, AltName = x.AltName - }).ToList() + }) }; } } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/ProfessorInfoListVm.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/ProfessorInfoListVm.cs index 9b2637f..a6952ad 100644 --- a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/ProfessorInfoListVm.cs +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorDetailsBySearch/ProfessorInfoListVm.cs @@ -11,5 +11,5 @@ public class ProfessorInfoListVm /// /// List of /// - public required IList Details { get; set; } + public IEnumerable Details { get; set; } = []; } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs index f3bf0f2..f7ecb20 100644 --- a/SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs +++ b/SqlData/Application/Cqrs/Professor/Queries/GetProfessorList/ProfessorListVm.cs @@ -10,5 +10,5 @@ public class ProfessorListVm /// /// The list of professors. /// - public IList Professors { get; set; } = new List(); + public IEnumerable Professors { get; set; } = []; } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs index 63a8a4d..626bee5 100644 --- a/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs +++ b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs @@ -83,7 +83,7 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH .Select(la => la.ProfessorId), LinkToMeet = l.LessonAssociations!.Select(la => la.LinkToMeet) - }).ToList(); + }); return new ScheduleListVm { diff --git a/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs index 565e5b9..1c17352 100644 --- a/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs +++ b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/ScheduleListVm.cs @@ -10,5 +10,5 @@ public class ScheduleListVm /// /// Gets or sets the list of schedules. /// - public IList Schedules { get; set; } = new List(); + public IEnumerable Schedules { get; set; } = []; } \ No newline at end of file diff --git a/SqlData/Application/DependencyInjection.cs b/SqlData/Application/DependencyInjection.cs index 61c3eab..2fe8c4b 100644 --- a/SqlData/Application/DependencyInjection.cs +++ b/SqlData/Application/DependencyInjection.cs @@ -1,4 +1,4 @@ -using FluentValidation; +using FluentValidation; using MediatR; using Microsoft.Extensions.DependencyInjection; using Mirea.Api.DataAccess.Application.Common.Behaviors; @@ -11,7 +11,7 @@ public static class DependencyInjection public static IServiceCollection AddApplication(this IServiceCollection services) { services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); - services.AddValidatorsFromAssemblies(new[] { Assembly.GetExecutingAssembly() }); + services.AddValidatorsFromAssemblies([Assembly.GetExecutingAssembly()]); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); return services; diff --git a/SqlData/Persistence/Common/ModelBuilderExtensions.cs b/SqlData/Persistence/Common/ModelBuilderExtensions.cs index 2198808..f2c8af4 100644 --- a/SqlData/Persistence/Common/ModelBuilderExtensions.cs +++ b/SqlData/Persistence/Common/ModelBuilderExtensions.cs @@ -9,7 +9,7 @@ public static class ModelBuilderExtensions { var applyGenericMethod = typeof(ModelBuilder) .GetMethods() - .First(m => m.Name == "ApplyConfiguration" && + .First(m => m.Name == nameof(ApplyConfiguration) && m.GetParameters().Any(p => p.ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>))); var entityType = configuration.GetType().GetInterfaces() diff --git a/SqlData/Persistence/DbInitializer.cs b/SqlData/Persistence/DbInitializer.cs index 7bedd91..1c23215 100644 --- a/SqlData/Persistence/DbInitializer.cs +++ b/SqlData/Persistence/DbInitializer.cs @@ -4,8 +4,6 @@ namespace Mirea.Api.DataAccess.Persistence; public static class DbInitializer { - public static void Initialize(DbContext dbContext) - { + public static void Initialize(DbContext dbContext) => dbContext.Database.Migrate(); - } } \ No newline at end of file -- 2.43.0 From 9ff0f51e19762f8b4a088357d3cd3c482b9ded13 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:44:15 +0300 Subject: [PATCH 401/474] refactor: add data annotations --- ApiDto/Requests/CreateUserRequest.cs | 3 +++ Endpoint/Controllers/Configuration/SetupController.cs | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ApiDto/Requests/CreateUserRequest.cs b/ApiDto/Requests/CreateUserRequest.cs index e1b3a4c..2d6fecf 100644 --- a/ApiDto/Requests/CreateUserRequest.cs +++ b/ApiDto/Requests/CreateUserRequest.cs @@ -11,17 +11,20 @@ public class CreateUserRequest /// Gets or sets the email address of the user. ///
[Required] + [EmailAddress] public required string Email { get; set; } /// /// Gets or sets the username of the user. /// [Required] + [MinLength(2)] public required string Username { get; set; } /// /// Gets or sets the password of the user. /// [Required] + [MinLength(2)] public required string Password { get; set; } } diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index e92ca2b..2d64f7d 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -29,7 +29,6 @@ using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; -using System.Net.Mail; using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography; @@ -305,10 +304,6 @@ public class SetupController( public ActionResult CreateAdmin([FromBody] CreateUserRequest userRequest) { new PasswordPolicyService(GeneralConfig.PasswordPolicy).ValidatePasswordOrThrow(userRequest.Password); - - if (!MailAddress.TryCreate(userRequest.Email, out _)) - throw new ControllerArgumentException("The email address is incorrect."); - var (salt, hash) = passwordHashService.HashPassword(userRequest.Password); var admin = new Admin -- 2.43.0 From 8c51ba83a46bc89c9f35ed414889b7e348e9e8e1 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:44:37 +0300 Subject: [PATCH 402/474] fix: add trim for email and username --- Endpoint/Configuration/Model/Admin.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Endpoint/Configuration/Model/Admin.cs b/Endpoint/Configuration/Model/Admin.cs index 26492ec..381981b 100644 --- a/Endpoint/Configuration/Model/Admin.cs +++ b/Endpoint/Configuration/Model/Admin.cs @@ -10,12 +10,22 @@ namespace Mirea.Api.Endpoint.Configuration.Model; public class Admin : ISaveSettings { [JsonIgnore] private const string FileName = "admin.json"; + private string _username = string.Empty; + private string _email = string.Empty; [JsonIgnore] public static string FilePath => PathBuilder.Combine(FileName); - public required string Username { get; set; } - public required string Email { get; set; } + public required string Username + { + get => _username; + set => _username = value.Trim(); + } + public required string Email + { + get => _email; + set => _email = value.Trim(); + } public required string PasswordHash { get; set; } public required string Salt { get; set; } public TwoFactorAuthenticator TwoFactorAuthenticator { get; set; } = TwoFactorAuthenticator.None; -- 2.43.0 From 71c31c0bbb05d5085f76e4a7bb6cbaf584004475 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:46:27 +0300 Subject: [PATCH 403/474] refactor: separate the method of counting failed attempts --- Security/Services/AuthService.cs | 50 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index a505c09..17103e8 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -24,6 +24,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I private static string GetAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token"; private static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; + private static string GetAttemptFailedCountKey(string fingerprint) => $"{fingerprint}_login_failed"; private Task SetAuthTokenDataToCache(AuthToken data, CancellationToken cancellation) => cache.SetAsync( @@ -47,34 +48,47 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I private Task RevokeAccessToken(string token) => revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); - private async Task VerifyUserOrThrowError(RequestContextInfo requestContext, User user, string password, string username, - CancellationToken cancellation = default) + private async Task RecordFailedLoginAttempt(string fingerprint, string userId, CancellationToken cancellation) { - if ((user.Email.Equals(username, StringComparison.OrdinalIgnoreCase) || user.Username.Equals(username, StringComparison.OrdinalIgnoreCase)) && - passwordService.VerifyPassword(password, user.Salt, user.PasswordHash)) - return; + var failedLoginAttemptsCount = await cache.GetAsync(GetAttemptFailedCountKey(fingerprint), cancellation) ?? 1; + var failedLoginCacheExpiration = TimeSpan.FromHours(1); - var failedLoginCacheName = $"{requestContext.Fingerprint}_login_failed"; - var countFailedLogin = await cache.GetAsync(failedLoginCacheName, cancellation) ?? 1; - var cacheSaveTime = TimeSpan.FromHours(1); - - await cache.SetAsync(failedLoginCacheName, countFailedLogin + 1, slidingExpiration: cacheSaveTime, cancellationToken: cancellation); - - if (countFailedLogin > 5) + if (failedLoginAttemptsCount > 5) { logger.LogWarning( - "Multiple unsuccessful login attempts for user ID {UserId}. Attempt count: {AttemptNumber}.", - user.Id, - countFailedLogin); + "Multiple unsuccessful login attempts for user ID {UserId}. Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.", + userId, + fingerprint, + failedLoginAttemptsCount); throw new SecurityException("Too many unsuccessful login attempts. Please try again later."); } logger.LogInformation( "Login attempt failed for user ID {UserId}. Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.", - user.Id, - requestContext.Fingerprint, - countFailedLogin); + userId, + fingerprint, + failedLoginAttemptsCount); + + await cache.SetAsync(GetAttemptFailedCountKey(fingerprint), failedLoginAttemptsCount + 1, + slidingExpiration: failedLoginCacheExpiration, cancellationToken: cancellation); + } + + private Task ResetFailedLoginAttempts(string fingerprint, CancellationToken cancellation) => + cache.RemoveAsync(GetAttemptFailedCountKey(fingerprint), cancellation); + + private async Task VerifyUserOrThrowError(RequestContextInfo requestContext, User user, string password, string username, + CancellationToken cancellation = default) + { + if ((user.Email.Equals(username, StringComparison.OrdinalIgnoreCase) || + user.Username.Equals(username, StringComparison.OrdinalIgnoreCase)) && + passwordService.VerifyPassword(password, user.Salt, user.PasswordHash)) + { + await ResetFailedLoginAttempts(requestContext.Fingerprint, cancellation); + return; + } + + await RecordFailedLoginAttempt(requestContext.Fingerprint, user.Id, cancellation); throw new SecurityException("Authentication failed. Please check your credentials."); } -- 2.43.0 From c12323dc29b3692e04319e51409f7ecf8a78058b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:47:51 +0300 Subject: [PATCH 404/474] refactor: rename methods to match the context --- Security/Services/AuthService.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 17103e8..f36ad6d 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -26,14 +26,14 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I private static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; private static string GetAttemptFailedCountKey(string fingerprint) => $"{fingerprint}_login_failed"; - private Task SetAuthTokenDataToCache(AuthToken data, CancellationToken cancellation) => + private Task StoreAuthTokenInCache(AuthToken data, CancellationToken cancellation) => cache.SetAsync( GetAuthCacheKey(data.Fingerprint), JsonSerializer.SerializeToUtf8Bytes(data), slidingExpiration: Lifetime, cancellationToken: cancellation); - private Task CreateFirstAuthTokenToCache(User data, RequestContextInfo requestContext, CancellationToken cancellation) => + private Task StoreFirstAuthTokenInCache(User data, RequestContextInfo requestContext, CancellationToken cancellation) => cache.SetAsync( GetFirstAuthCacheKey(requestContext.Fingerprint), JsonSerializer.SerializeToUtf8Bytes(new FirstAuthToken(requestContext) @@ -106,7 +106,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I AccessToken = token }; - await SetAuthTokenDataToCache(authToken, cancellation); + await StoreAuthTokenInCache(authToken, cancellation); cookieOptions.SetCookie(context, CookieNames.AccessToken, authToken.AccessToken, expireIn); cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); @@ -126,7 +126,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return TwoFactorAuthenticator.None; } - await CreateFirstAuthTokenToCache(user, requestContext, cancellation); + await StoreFirstAuthTokenInCache(user, requestContext, cancellation); return user.TwoFactorAuthenticator; } @@ -173,7 +173,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return TwoFactorAuthenticator.None; } - await CreateFirstAuthTokenToCache(user, requestContext, cancellation); + await StoreFirstAuthTokenInCache(user, requestContext, cancellation); return user.TwoFactorAuthenticator; } @@ -211,7 +211,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.AccessToken = token; authToken.RefreshToken = newRefreshToken; - await SetAuthTokenDataToCache(authToken, cancellation); + await StoreAuthTokenInCache(authToken, cancellation); cookieOptions.SetCookie(context, CookieNames.AccessToken, authToken.AccessToken, expireIn); cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); } -- 2.43.0 From c66f3355ec363e5bc5bfed70c9621cdb55de63be Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:48:22 +0300 Subject: [PATCH 405/474] feat: add logging for empty secret --- Security/Services/AuthService.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index f36ad6d..4951608 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -145,7 +145,13 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I case TwoFactorAuthenticator.Totp: { if (string.IsNullOrEmpty(firstTokenAuth.Secret)) + { + logger.LogWarning("The user {Fingerprint} for {UserId} tried to pass the 2FA even though the secret is empty", + requestContext.Fingerprint, + firstTokenAuth.UserId); + throw new InvalidOperationException("Required authentication data is missing."); + } var totp = new TotpService(firstTokenAuth.Secret); -- 2.43.0 From dfac9ddca8f5ed31de836eb00b65ac0abcb0afad Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:49:13 +0300 Subject: [PATCH 406/474] sec: add failed attempts for 2FA --- Security/Services/AuthService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 4951608..732c031 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -156,8 +156,13 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I var totp = new TotpService(firstTokenAuth.Secret); if (!totp.VerifyToken(code)) + { + await RecordFailedLoginAttempt(requestContext.Fingerprint, firstTokenAuth.UserId, cancellation); throw new SecurityException("Invalid verification code. Please try again."); } + + await ResetFailedLoginAttempts(requestContext.Fingerprint, cancellation); + } break; default: throw new InvalidOperationException("Unsupported authorization method."); -- 2.43.0 From 5e65aded796a7f725034ab8e759c6153efdd0946 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:51:54 +0300 Subject: [PATCH 407/474] refactor: instead of Reason, add explicit arguments --- Security/Services/AuthService.cs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 732c031..1c7b80e 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -191,9 +191,10 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I public async Task RefreshTokenAsync(CookieOptionsParameters cookieOptions, HttpContext context, CancellationToken cancellation = default) { + const string defaultMessageError = "The session time has expired"; var requestContext = new RequestContextInfo(context, cookieOptions); - var authToken = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation) - ?? throw new SecurityException("The session time has expired"); + var authToken = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation) ?? + throw new SecurityException(defaultMessageError); if (authToken.RefreshToken != requestContext.RefreshToken || authToken.UserAgent != requestContext.UserAgent && @@ -204,14 +205,29 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I cookieOptions.DropCookie(context, CookieNames.AccessToken); cookieOptions.DropCookie(context, CookieNames.RefreshToken); - logger.LogWarning("Token validation failed for user ID {UserId}. Fingerprint: {Fingerprint}. Reason: {Reason}.", + const string error = "Token validation failed for user ID {UserId}. Fingerprint: {Fingerprint}. "; + if (authToken.RefreshToken != requestContext.RefreshToken) + logger.LogWarning( + error + + "Cached refresh token {ExpectedRefreshToken} does not match the provided refresh token {RefreshToken}", authToken.UserId, authToken.Fingerprint, - authToken.RefreshToken != requestContext.RefreshToken ? - $"Cached refresh token '{authToken.RefreshToken}' does not match the provided refresh token '{requestContext.RefreshToken}'" : - $"User-Agent '{authToken.UserAgent}' and IP '{authToken.Ip}' in cache do not match the provided User-Agent '{requestContext.UserAgent}' and IP '{requestContext.Ip}'"); + authToken.RefreshToken, + requestContext.RefreshToken); + else + logger.LogWarning( + error + + "User-Agent {ExpectedUserAgent} and IP {ExpectedUserIp} in cache do not match the provided " + + "User-Agent {ProvidedUserAgent} and IP {ProvidedIp}", + authToken.UserId, + authToken.Fingerprint, + authToken.UserAgent, + authToken.Ip, + requestContext.UserAgent, + requestContext.Ip); + + throw new SecurityException(defaultMessageError); - throw new SecurityException("The session time has expired"); } var (token, expireIn) = GenerateAccessToken(authToken.UserId); -- 2.43.0 From 2ab5dea8ba13696b342d91f55a58cfaa2d921ea5 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:52:39 +0300 Subject: [PATCH 408/474] feat: add a change to the User Agent and Ip address in case of a mismatch --- Security/Services/AuthService.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 1c7b80e..e78ce75 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -227,7 +227,30 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I requestContext.Ip); throw new SecurityException(defaultMessageError); + } + if (authToken.UserAgent != requestContext.UserAgent) + { + logger.LogInformation("The resulting User-Agent {ProvidedUserAgent} does not match the cached " + + "{ExpectedUserAgent} of the user {UserId} with the fingerprint {Fingerprint}.", + requestContext.UserAgent, + authToken.UserAgent, + authToken.UserId, + requestContext.Fingerprint); + + authToken.UserAgent = requestContext.UserAgent; + } + + if (authToken.Ip != requestContext.Ip) + { + logger.LogInformation("The resulting Ip {ProvidedIp} does not match the cached " + + "{ExpectedIp} of the user {UserId} with the fingerprint {Fingerprint}.", + requestContext.Ip, + authToken.Ip, + authToken.UserId, + requestContext.Fingerprint); + + authToken.Ip = requestContext.Ip; } var (token, expireIn) = GenerateAccessToken(authToken.UserId); -- 2.43.0 From 5fa545e9818779eb2a04a885127aa5fb54f2beae Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:53:59 +0300 Subject: [PATCH 409/474] fix: trim the variable to avoid the effects of whitespace characters --- Security/Services/AuthService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index e78ce75..7909cc1 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -175,7 +175,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, string username, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); - + username = username.Trim(); await VerifyUserOrThrowError(requestContext, user, password, username, cancellation); if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) -- 2.43.0 From 269d976ad4af9074de366dc2316e3d51ec398177 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 05:54:27 +0300 Subject: [PATCH 410/474] refactor: move arguments to a new line --- Security/Services/AuthService.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 7909cc1..645e7c3 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -12,7 +12,8 @@ using System.Threading.Tasks; namespace Mirea.Api.Security.Services; -public class AuthService(ICacheService cache, IAccessToken accessTokenService, IRevokedToken revokedToken, ILogger logger, PasswordHashService passwordService) +public class AuthService(ICacheService cache, IAccessToken accessTokenService, IRevokedToken revokedToken, ILogger logger, + PasswordHashService passwordService) { public TimeSpan Lifetime { private get; init; } public TimeSpan LifetimeFirstAuth { private get; init; } @@ -93,7 +94,8 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I throw new SecurityException("Authentication failed. Please check your credentials."); } - private async Task GenerateAuthTokensAsync(CookieOptionsParameters cookieOptions, HttpContext context, RequestContextInfo requestContext, string userId, CancellationToken cancellation = default) + private async Task GenerateAuthTokensAsync(CookieOptionsParameters cookieOptions, HttpContext context, + RequestContextInfo requestContext, string userId, CancellationToken cancellation = default) { var refreshToken = GenerateRefreshToken(); var (token, expireIn) = GenerateAccessToken(userId); @@ -116,7 +118,8 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.Fingerprint); } - public async Task LoginOAuthAsync(CookieOptionsParameters cookieOptions, HttpContext context, User user, CancellationToken cancellation = default) + public async Task LoginOAuthAsync(CookieOptionsParameters cookieOptions, HttpContext context, User user, + CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); @@ -131,7 +134,8 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return user.TwoFactorAuthenticator; } - public async Task LoginAsync(CookieOptionsParameters cookieOptions, HttpContext context, TwoFactorAuthenticator authenticator, string code, CancellationToken cancellation = default) + public async Task LoginAsync(CookieOptionsParameters cookieOptions, HttpContext context, TwoFactorAuthenticator authenticator, string code, + CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); @@ -159,7 +163,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I { await RecordFailedLoginAttempt(requestContext.Fingerprint, firstTokenAuth.UserId, cancellation); throw new SecurityException("Invalid verification code. Please try again."); - } + } await ResetFailedLoginAttempts(requestContext.Fingerprint, cancellation); } @@ -172,7 +176,8 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return true; } - public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, string username, CancellationToken cancellation = default) + public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, + string username, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); username = username.Trim(); @@ -210,8 +215,8 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I logger.LogWarning( error + "Cached refresh token {ExpectedRefreshToken} does not match the provided refresh token {RefreshToken}", - authToken.UserId, - authToken.Fingerprint, + authToken.UserId, + authToken.Fingerprint, authToken.RefreshToken, requestContext.RefreshToken); else -- 2.43.0 From ae4d2073c4012d215aa2885626884e41132b6587 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Wed, 25 Dec 2024 07:22:07 +0300 Subject: [PATCH 411/474] feat: add a 200 result schema --- .../Core/Startup/SwaggerConfiguration.cs | 1 + .../ActionResultSchemaFilter.cs | 81 +++++++++++++++++++ Endpoint/Controllers/V1/AuthController.cs | 1 - Endpoint/Controllers/V1/ImportController.cs | 5 +- Endpoint/Controllers/V1/SecurityController.cs | 2 +- 5 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 Endpoint/Configuration/SwaggerOptions/ActionResultSchemaFilter.cs diff --git a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs index 0eddcac..8d51276 100644 --- a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs @@ -19,6 +19,7 @@ public static class SwaggerConfiguration { options.SchemaFilter(); options.OperationFilter(); + options.OperationFilter(); var basePath = AppDomain.CurrentDomain.BaseDirectory; options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme diff --git a/Endpoint/Configuration/SwaggerOptions/ActionResultSchemaFilter.cs b/Endpoint/Configuration/SwaggerOptions/ActionResultSchemaFilter.cs new file mode 100644 index 0000000..f1584e2 --- /dev/null +++ b/Endpoint/Configuration/SwaggerOptions/ActionResultSchemaFilter.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; + +public class ActionResultSchemaFilter : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var returnType = context.MethodInfo.ReturnType; + if (!returnType.IsEquivalentTo(typeof(ActionResult)) && + !returnType.IsEquivalentTo(typeof(ContentResult)) && + !returnType.IsEquivalentTo(typeof(FileStreamResult)) && + !returnType.IsGenericType) + return; + + if (returnType.IsGenericType && + !returnType.GetGenericTypeDefinition().IsEquivalentTo(typeof(ActionResult<>)) && + !returnType.GetGenericTypeDefinition().IsEquivalentTo(typeof(Task<>))) + return; + + var genericType = returnType.IsGenericType ? returnType.GetGenericArguments().FirstOrDefault() : returnType; + if (genericType == null) + return; + + var responseTypeAttributes = context.MethodInfo.GetCustomAttributes(typeof(ProducesResponseTypeAttribute), false) + .Cast() + .Where(attr => attr.StatusCode == 200) + .ToList(); + + var contentType = "application/json"; + + if (context.MethodInfo.GetCustomAttributes(typeof(ProducesAttribute), false) + .FirstOrDefault() is ProducesAttribute producesAttribute) + contentType = producesAttribute.ContentTypes.FirstOrDefault() ?? "application/json"; + + if (responseTypeAttributes.Count != 0) + { + var responseType = responseTypeAttributes.First().Type; + genericType = responseType; + } + + if (genericType.IsEquivalentTo(typeof(ContentResult)) || genericType.IsEquivalentTo(typeof(FileStreamResult))) + { + operation.Responses["200"] = new OpenApiResponse + { + Description = "OK", + Content = new Dictionary + { + [contentType] = new() + } + }; + } + else if (genericType == typeof(ActionResult)) + { + operation.Responses["200"] = new OpenApiResponse { Description = "OK" }; + } + else + { + OpenApiSchema schema; + if (genericType.IsGenericType && genericType.GetGenericTypeDefinition() == typeof(ActionResult<>)) + schema = context.SchemaGenerator.GenerateSchema(genericType.GetGenericArguments().FirstOrDefault(), + context.SchemaRepository); + else + schema = context.SchemaGenerator.GenerateSchema(genericType, context.SchemaRepository); + + operation.Responses["200"] = new OpenApiResponse + { + Description = "OK", + Content = new Dictionary + { + [contentType] = new() { Schema = schema } + } + }; + } + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index a9ea5a3..0ce1e4d 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -48,7 +48,6 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot OAuth2([FromQuery] string code, [FromQuery] string state) diff --git a/Endpoint/Controllers/V1/ImportController.cs b/Endpoint/Controllers/V1/ImportController.cs index a7882e9..7b76fef 100644 --- a/Endpoint/Controllers/V1/ImportController.cs +++ b/Endpoint/Controllers/V1/ImportController.cs @@ -40,7 +40,7 @@ public class ImportController(IMediator mediator, IOptionsSnapshotExcel file [HttpPost("ImportToExcel")] [Produces("application/vnd.ms-excel")] - public async Task ImportToExcel([FromBody] ScheduleRequest request) + public async Task ImportToExcel([FromBody] ScheduleRequest request) { var result = (await mediator.Send(new GetScheduleListQuery { @@ -51,9 +51,6 @@ public class ImportController(IMediator mediator, IOptionsSnapshot generalConfig) : [HttpGet("GenerateTotpQrCode")] [Produces("image/svg+xml")] [MaintenanceModeIgnore] - public IActionResult GenerateTotpQrCode( + public ContentResult GenerateTotpQrCode( [FromQuery] string totpKey, [FromQuery] string label, [FromQuery] string? backgroundColor = null, -- 2.43.0 From cfe08dcf9b6e73321b2fb87a10f6ae1aa20503df Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 26 Dec 2024 08:42:24 +0300 Subject: [PATCH 412/474] refactor: get interface instead array --- Endpoint/Common/MapperDto/AvailableProvidersConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs b/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs index 9a01f17..3e32074 100644 --- a/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs +++ b/Endpoint/Common/MapperDto/AvailableProvidersConverter.cs @@ -17,7 +17,7 @@ public static class AvailableProvidersConverter _ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null) }; - public static List ConvertToDto(this (OAuthProvider Provider, Uri Redirect)[] data) => + public static List ConvertToDto(this IEnumerable<(OAuthProvider Provider, Uri Redirect)> data) => data.Select(x => new AvailableOAuthProvidersResponse() { ProviderName = Enum.GetName(x.Provider)!, -- 2.43.0 From 97187a8e45b25283ffc2aae34883564f5b745501 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 26 Dec 2024 08:44:05 +0300 Subject: [PATCH 413/474] sec: save readonly byte array instead string --- Security/DependencyInjection.cs | 27 +++++++++++++++++++++++++-- Security/Services/OAuthService.cs | 6 ++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs index d3d4b38..4ddceb6 100644 --- a/Security/DependencyInjection.cs +++ b/Security/DependencyInjection.cs @@ -6,11 +6,29 @@ using Mirea.Api.Security.Common.Interfaces; using Mirea.Api.Security.Services; using System; using System.Collections.Generic; +using System.Text; namespace Mirea.Api.Security; public static class DependencyInjection { + private static ReadOnlyMemory NormalizeKey(string key, int requiredLength) + { + var keyBytes = Encoding.UTF8.GetBytes(key); + + if (keyBytes.Length < requiredLength) + { + var normalizedKey = new byte[requiredLength]; + Array.Copy(keyBytes, normalizedKey, keyBytes.Length); + return new ReadOnlyMemory(normalizedKey); + } + + if (keyBytes.Length > requiredLength) + Array.Resize(ref keyBytes, requiredLength); + + return new ReadOnlyMemory(keyBytes); + } + public static IServiceCollection AddSecurityServices(this IServiceCollection services, IConfiguration configuration) { var saltSize = int.Parse(configuration["SECURITY_SALT_SIZE"]!); @@ -61,8 +79,13 @@ public static class DependencyInjection providers.Add(provider, (clientId, secret)); } - services.AddSingleton(provider => new OAuthService(provider.GetRequiredService>(), providers, - configuration["SECURITY_ENCRYPTION_TOKEN"]!)); + services.AddSingleton(provider => new OAuthService( + provider.GetRequiredService>(), + providers, + provider.GetRequiredService()) + { + SecretKey = NormalizeKey(configuration["SECURITY_ENCRYPTION_TOKEN"]!, 32) + }); return services; } diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 929f1b7..a0a5964 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -20,6 +20,8 @@ namespace Mirea.Api.Security.Services; public class OAuthService(ILogger logger, Dictionary providers, string secretKey) { + public required ReadOnlyMemory SecretKey { private get; init; } + private static readonly Dictionary ProviderData = new() { [OAuthProvider.Google] = new OAuthProviderUrisData @@ -101,9 +103,9 @@ public class OAuthService(ILogger logger, Dictionary Date: Thu, 26 Dec 2024 08:47:56 +0300 Subject: [PATCH 414/474] sec: add payload --- Endpoint/Controllers/V1/AuthController.cs | 24 +++-- Security/Services/OAuthService.cs | 104 ++++++++++++++++++---- 2 files changed, 107 insertions(+), 21 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 0ce1e4d..e53a5f2 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -16,7 +16,7 @@ using Mirea.Api.Security.Services; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Security.Claims; +using System.Linq; using System.Threading.Tasks; using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; @@ -130,17 +130,23 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot /// The identifier of the OAuth provider to authorize with. + /// The address where the user will need to be redirected after the end of communication with the OAuth provider /// A redirect to the OAuth provider's authorization URL. /// Thrown if the specified provider is not valid. [HttpGet("AuthorizeOAuth2")] [MaintenanceModeIgnore] - public ActionResult AuthorizeOAuth2([FromQuery] int provider) + public ActionResult AuthorizeOAuth2([FromQuery] int provider, [FromQuery] Uri callback) { if (!Enum.IsDefined(typeof(OAuthProvider), provider)) throw new ControllerArgumentException("There is no selected provider"); - return Redirect(oAuthService.GetProviderRedirect(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!), - (OAuthProvider)provider).AbsoluteUri); + if (!callback.IsAbsoluteUri) + throw new ControllerArgumentException("The callback URL must be absolute."); + + return Redirect(oAuthService.GetProviderRedirect(HttpContext, GetCookieParams(), + HttpContext.GetApiUrl(Url.Action("OAuth2")!), + (OAuthProvider)provider, + callback).AbsoluteUri); } /// @@ -152,9 +158,17 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshotA list of available providers and their redirect URLs. [HttpGet("AvailableProviders")] [MaintenanceModeIgnore] - public ActionResult> AvailableProviders() => + public ActionResult> AvailableProviders([FromQuery] Uri callback) => Ok(oAuthService .GetAvailableProviders(HttpContext.GetApiUrl(Url.Action("AuthorizeOAuth2")!)) + .Select(x => + { + if (!callback.IsAbsoluteUri) + throw new ControllerArgumentException("The callback URL must be absolute."); + + x.Redirect = new Uri(x.Redirect + "&callback=" + Uri.EscapeDataString(callback.AbsoluteUri)); + return x; + }) .ConvertToDto()); /// diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index a0a5964..c3ab569 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -6,6 +6,7 @@ using Mirea.Api.Security.Common.Domain.OAuth2.UserInfo; using Mirea.Api.Security.Common.Interfaces; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -110,23 +111,83 @@ public class OAuthService(ILogger logger, Dictionary(data) ?? + throw new NullReferenceException($"Couldn't convert data to {nameof(OAuthPayload)}."); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Couldn't decrypt the data OAuth request."); + throw new InvalidOperationException("Couldn't decrypt the data.", ex); + } + } + + public Uri GetProviderRedirect(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUri, + OAuthProvider provider, Uri callback) { var (clientId, _) = providers[provider]; var requestInfo = new RequestContextInfo(context, cookieOptions); - var state = GetHmacString(requestInfo, secretKey); + var payload = EncryptPayload(new OAuthPayload() + { + Provider = provider, + Callback = callback.AbsoluteUri + }); + + var checksum = GetHmacString(requestInfo); var redirectUrl = $"?client_id={clientId}" + "&response_type=code" + $"&redirect_uri={redirectUri}" + $"&scope={ProviderData[provider].Scope}" + - $"&state={Uri.EscapeDataString(state + "_" + Enum.GetName(provider))}"; + $"&state={Uri.EscapeDataString(payload + "_" + checksum)}"; logger.LogInformation("Redirecting user Fingerprint: {Fingerprint} to OAuth provider {Provider} with state: {State}", requestInfo.Fingerprint, provider, - state); + checksum); return new Uri(ProviderData[provider].RedirectUrl + redirectUrl); } @@ -137,27 +198,38 @@ public class OAuthService(ILogger logger, Dictionary LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) { - var partsState = state.Split('_'); + + var parts = state.Split('_'); - if (!Enum.TryParse(partsState.Last(), true, out var provider) || - !providers.TryGetValue(provider, out var providerInfo) || - !ProviderData.TryGetValue(provider, out var currentProviderStruct)) + if (parts.Length != 2) { - logger.LogWarning("Failed to parse OAuth provider from state: {State}", state); - throw new InvalidOperationException("Invalid authorization request."); + throw new SecurityException("The request data is invalid or malformed."); + } - var secretStateData = string.Join("_", partsState.SkipLast(1)); - var requestInfo = new RequestContextInfo(context, cookieOptions); - var secretData = GetHmacString(requestInfo, secretKey); + var payload = DecryptPayload(parts[0]); + var checksum = parts[1]; - if (secretData != secretStateData) + if (!providers.TryGetValue(payload.Provider, out var providerInfo) || + !ProviderData.TryGetValue(payload.Provider, out var currentProviderStruct)) + { + logger.LogWarning("The OAuth provider specified in the payload " + + "is not registered as a possible data recipient from state: {State}", + state); + + throw new SecurityException("Invalid authorization request. Please try again later."); + } + var requestInfo = new RequestContextInfo(context, cookieOptions); + var checksumRequest = GetHmacString(requestInfo); + + + if (checksumRequest != checksum) { logger.LogWarning( "Fingerprint mismatch. Possible CSRF attack detected. Fingerprint: {Fingerprint}, State: {State}, ExpectedState: {ExpectedState}", requestInfo.Fingerprint, - secretData, - secretStateData + checksumRequest, + checksum ); throw new SecurityException("Suspicious activity detected. Please try again."); } -- 2.43.0 From dcdd43469b41b7455f8eacbbba6eb9c54a174fe2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 26 Dec 2024 08:48:17 +0300 Subject: [PATCH 415/474] feat: add payload model --- Security/Common/Domain/OAuth2/OAuthPayload.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Security/Common/Domain/OAuth2/OAuthPayload.cs diff --git a/Security/Common/Domain/OAuth2/OAuthPayload.cs b/Security/Common/Domain/OAuth2/OAuthPayload.cs new file mode 100644 index 0000000..87799a5 --- /dev/null +++ b/Security/Common/Domain/OAuth2/OAuthPayload.cs @@ -0,0 +1,7 @@ +namespace Mirea.Api.Security.Common.Domain.OAuth2; + +public class OAuthPayload +{ + public required OAuthProvider Provider { get; set; } + public required string Callback { get; set; } +} \ No newline at end of file -- 2.43.0 From 43edab2912b73834a07d48e6db50fe9d13dbb970 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 26 Dec 2024 08:51:22 +0300 Subject: [PATCH 416/474] sec: return the token instead of performing actions with the user --- Endpoint/Controllers/V1/AuthController.cs | 121 +++++++++------------ Security/Common/Domain/LoginOAuthResult.cs | 11 ++ Security/Services/OAuthService.cs | 63 +++++++---- 3 files changed, 104 insertions(+), 91 deletions(-) create mode 100644 Security/Common/Domain/LoginOAuthResult.cs diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index e53a5f2..8c0c7e8 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -33,94 +33,73 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot"; + var callbackUrl = callback?.ToString(); - return $"{title}

{title}

{message}

Это информационная страница. Вы можете закрыть её.

{(!string.IsNullOrEmpty(traceId) ? $"TraceId={traceId}" : string.Empty)}
{script}"; + var script = callback == null ? string.Empty : + $""; + + var blockInfo = "

" + (callback == null ? + "Вернитесь обратно и попробуйте снова позже.

" : + $"Если вы не будете автоматически перенаправлены, нажмите ниже.

" + + $"Перейти вручную"); + + return $"{title}

{title}

{blockInfo}

{message}

TraceId={traceId}
{script}"; } [HttpGet("OAuth2")] [BadRequestResponse] [Produces("text/html")] [MaintenanceModeIgnore] - public async Task OAuth2([FromQuery] string code, [FromQuery] string state) + public async Task OAuth2([FromQuery] string? code, [FromQuery] string? state) { - var userId = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - string title; - string message; - OAuthProvider provider; - OAuthUser oAuthUser; var traceId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - try - { - (provider, oAuthUser) = await oAuthService.LoginOAuth(HttpContext, GetCookieParams(), + if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(state)) + return Content(GenerateHtmlResponse( + "Ошибка передачи данных!", + "Провайдер OAuth не передал нужных данных.", + null, + traceId, + true), "text/html"); + + var result = await oAuthService.LoginOAuth(HttpContext, GetCookieParams(), HttpContext.GetApiUrl(Url.Action("OAuth2")!), code, state); - } - catch (Exception e) + + string? callbackUrl = null; + + if (result.Callback != null) + callbackUrl = result.Callback + (result.Callback.Query.Length > 0 ? "&" : "?") + + $"result={Uri.EscapeDataString(result.Token)}"; + + string title, message; + + if (!result.Success) { - title = "Произошла ошибка при общении с провайдером OAuth!"; - message = e.Message; - return Content(GenerateHtmlResponse(title, message, null, true, traceId), "text/html"); + if (callbackUrl != null) + callbackUrl += $"&traceId={Uri.EscapeDataString(traceId)}"; + + title = "Ошибка авторизации!"; + message = result.ErrorMessage ?? "Произошла ошибка. Попробуйте ещё раз."; } - - var userEntity = user.Value; - - if (userId != null) + else { - userEntity.OAuthProviders ??= []; - - if (!userEntity.OAuthProviders.TryAdd(provider, oAuthUser)) - { - title = "Ошибка связи аккаунта!"; - message = "Этот OAuth провайдер уже связан с вашей учетной записью. " + - "Пожалуйста, используйте другого провайдера или удалите связь с аккаунтом."; - return Content(GenerateHtmlResponse(title, message, provider, true, traceId), "text/html"); - } - - userEntity.SaveSetting(); - - title = "Учетная запись успешно связана."; - message = "Вы успешно связали свою учетную запись с провайдером OAuth. Вы можете продолжить использовать приложение."; - return Content(GenerateHtmlResponse(title, message, provider), "text/html"); + title = "Авторизация завершена!"; + message = "Вы будете перенаправлены обратно через несколько секунд."; } - if (userEntity.OAuthProviders != null && - userEntity.OAuthProviders.TryGetValue(provider, out var userOAuth) && - userOAuth.Id == oAuthUser.Id) - { - await auth.LoginOAuthAsync(GetCookieParams(), HttpContext, new User - { - Id = 1.ToString(), - Username = userEntity.Username, - Email = userEntity.Email, - PasswordHash = userEntity.PasswordHash, - Salt = userEntity.Salt, - TwoFactorAuthenticator = userEntity.TwoFactorAuthenticator, - SecondFactorToken = userEntity.Secret, - OAuthProviders = userEntity.OAuthProviders - }); - - title = "Успешный вход в аккаунт."; - message = "Вы успешно вошли в свою учетную запись. Добро пожаловать!"; - return Content(GenerateHtmlResponse(title, message, provider), "text/html"); - } - - title = "Вы успешно зарегистрированы."; - message = "Процесс завершен. Вы можете закрыть эту страницу."; - userEntity.Email = string.IsNullOrEmpty(oAuthUser.Email) ? string.Empty : oAuthUser.Email; - userEntity.Username = string.IsNullOrEmpty(oAuthUser.Username) ? string.Empty : oAuthUser.Username; - userEntity.OAuthProviders ??= []; - userEntity.OAuthProviders.Add(provider, oAuthUser); - userEntity.SaveSetting(); - return Content(GenerateHtmlResponse(title, message, provider), "text/html"); + return Content(GenerateHtmlResponse( + title, + message, + callbackUrl == null ? null : new Uri(callbackUrl), + traceId, + !result.Success), "text/html"); } /// diff --git a/Security/Common/Domain/LoginOAuthResult.cs b/Security/Common/Domain/LoginOAuthResult.cs new file mode 100644 index 0000000..b3f0b1e --- /dev/null +++ b/Security/Common/Domain/LoginOAuthResult.cs @@ -0,0 +1,11 @@ +using System; + +namespace Mirea.Api.Security.Common.Domain; + +public class LoginOAuthResult +{ + public bool Success { get; set; } + public required string Token { get; set; } + public Uri? Callback { get; set; } + public string? ErrorMessage { get; set; } +} \ No newline at end of file diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index c3ab569..8ba5018 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -10,7 +10,6 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; -using System.Security; using System.Security.Cryptography; using System.Text; using System.Text.Json; @@ -19,7 +18,8 @@ using System.Threading.Tasks; namespace Mirea.Api.Security.Services; -public class OAuthService(ILogger logger, Dictionary providers, string secretKey) +public class OAuthService(ILogger logger, Dictionary providers, + ICacheService cache) { public required ReadOnlyMemory SecretKey { private get; init; } @@ -195,21 +195,26 @@ public class OAuthService(ILogger logger, Dictionary [.. providers.Select(x => (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; - public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, + public async Task LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) { - + var result = new LoginOAuthResult() + { + Token = GeneratorKey.GenerateBase64(32) + }; var parts = state.Split('_'); if (parts.Length != 2) { - throw new SecurityException("The request data is invalid or malformed."); - + result.ErrorMessage = "The request data is invalid or malformed."; + return result; } var payload = DecryptPayload(parts[0]); var checksum = parts[1]; + result.Callback = new Uri(payload.Callback); + if (!providers.TryGetValue(payload.Provider, out var providerInfo) || !ProviderData.TryGetValue(payload.Provider, out var currentProviderStruct)) { @@ -217,12 +222,15 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary logger, Dictionary Date: Thu, 26 Dec 2024 13:38:43 +0300 Subject: [PATCH 417/474] refactor: distribute the domain folder --- .../MapperDto/PasswordPolicyConverter.cs | 6 +-- .../TwoFactorAuthenticationConverter.cs | 13 +++-- Endpoint/Configuration/Model/Admin.cs | 1 + Endpoint/Configuration/Model/GeneralConfig.cs | 2 +- .../Configuration/SetupController.cs | 5 +- Endpoint/Controllers/V1/AuthController.cs | 21 ++++++-- .../Common/Domain/Caching/FirstAuthToken.cs | 4 +- .../Domain/{OAuth2 => }/OAuthPayload.cs | 4 +- .../{OAuth2 => }/OAuthProviderUrisData.cs | 2 +- Security/Common/Domain/RequestContextInfo.cs | 2 +- .../CookieOptions.cs} | 6 +-- .../{Domain => Model}/PasswordPolicy.cs | 2 +- .../TwoFactorAuthenticator.cs | 2 +- Security/Common/{Domain => Model}/User.cs | 5 +- .../{Domain => }/OAuth2/OAuthTokenResponse.cs | 4 +- .../OAuth2/UserInfo/GoogleUserInfo.cs | 5 +- .../OAuth2/UserInfo/MailRuUserInfo.cs | 5 +- .../OAuth2/UserInfo/YandexUserInfo.cs | 5 +- .../LoginOAuth.cs} | 4 +- Security/Services/AuthService.cs | 54 ++++++++++--------- Security/Services/OAuthService.cs | 11 ++-- Security/Services/PasswordPolicyService.cs | 2 +- 22 files changed, 98 insertions(+), 67 deletions(-) rename Security/Common/Domain/{OAuth2 => }/OAuthPayload.cs (57%) rename Security/Common/Domain/{OAuth2 => }/OAuthProviderUrisData.cs (86%) rename Security/Common/{Domain/CookieOptionsParameters.cs => Model/CookieOptions.cs} (82%) rename Security/Common/{Domain => Model}/PasswordPolicy.cs (92%) rename Security/Common/{Domain => Model}/TwoFactorAuthenticator.cs (54%) rename Security/Common/{Domain => Model}/User.cs (79%) rename Security/Common/{Domain => }/OAuth2/OAuthTokenResponse.cs (71%) rename Security/Common/{Domain => }/OAuth2/UserInfo/GoogleUserInfo.cs (87%) rename Security/Common/{Domain => }/OAuth2/UserInfo/MailRuUserInfo.cs (85%) rename Security/Common/{Domain => }/OAuth2/UserInfo/YandexUserInfo.cs (88%) rename Security/Common/{Domain/LoginOAuthResult.cs => ViewModel/LoginOAuth.cs} (71%) diff --git a/Endpoint/Common/MapperDto/PasswordPolicyConverter.cs b/Endpoint/Common/MapperDto/PasswordPolicyConverter.cs index 85d27d4..a016c66 100644 --- a/Endpoint/Common/MapperDto/PasswordPolicyConverter.cs +++ b/Endpoint/Common/MapperDto/PasswordPolicyConverter.cs @@ -1,17 +1,17 @@ -using Mirea.Api.Dto.Common; +using PasswordPolicy = Mirea.Api.Dto.Common.PasswordPolicy; namespace Mirea.Api.Endpoint.Common.MapperDto; public static class PasswordPolicyConverter { - public static Security.Common.Domain.PasswordPolicy ConvertFromDto(this PasswordPolicy policy) => + public static Security.Common.Model.PasswordPolicy ConvertFromDto(this PasswordPolicy policy) => new(policy.MinimumLength, policy.RequireLetter, policy.RequireLettersDifferentCase, policy.RequireDigit, policy.RequireSpecialCharacter); - public static PasswordPolicy ConvertToDto(this Security.Common.Domain.PasswordPolicy policy) => + public static PasswordPolicy ConvertToDto(this Security.Common.Model.PasswordPolicy policy) => new() { MinimumLength = policy.MinimumLength, diff --git a/Endpoint/Common/MapperDto/TwoFactorAuthenticationConverter.cs b/Endpoint/Common/MapperDto/TwoFactorAuthenticationConverter.cs index 4086790..5239444 100644 --- a/Endpoint/Common/MapperDto/TwoFactorAuthenticationConverter.cs +++ b/Endpoint/Common/MapperDto/TwoFactorAuthenticationConverter.cs @@ -1,24 +1,23 @@ using Mirea.Api.Dto.Common; -using Mirea.Api.Security.Common.Domain; using System; namespace Mirea.Api.Endpoint.Common.MapperDto; public static class TwoFactorAuthenticationConverter { - public static TwoFactorAuthentication ConvertToDto(this TwoFactorAuthenticator authenticator) => + public static TwoFactorAuthentication ConvertToDto(this Security.Common.Model.TwoFactorAuthenticator authenticator) => authenticator switch { - TwoFactorAuthenticator.None => TwoFactorAuthentication.None, - TwoFactorAuthenticator.Totp => TwoFactorAuthentication.TotpRequired, + Security.Common.Model.TwoFactorAuthenticator.None => TwoFactorAuthentication.None, + Security.Common.Model.TwoFactorAuthenticator.Totp => TwoFactorAuthentication.TotpRequired, _ => throw new ArgumentOutOfRangeException(nameof(authenticator), authenticator, null) }; - public static TwoFactorAuthenticator ConvertFromDto(this TwoFactorAuthentication authentication) => + public static Security.Common.Model.TwoFactorAuthenticator ConvertFromDto(this TwoFactorAuthentication authentication) => authentication switch { - TwoFactorAuthentication.None => TwoFactorAuthenticator.None, - TwoFactorAuthentication.TotpRequired => TwoFactorAuthenticator.Totp, + TwoFactorAuthentication.None => Security.Common.Model.TwoFactorAuthenticator.None, + TwoFactorAuthentication.TotpRequired => Security.Common.Model.TwoFactorAuthenticator.Totp, _ => throw new ArgumentOutOfRangeException(nameof(authentication), authentication, null) }; } \ No newline at end of file diff --git a/Endpoint/Configuration/Model/Admin.cs b/Endpoint/Configuration/Model/Admin.cs index 381981b..d9488f8 100644 --- a/Endpoint/Configuration/Model/Admin.cs +++ b/Endpoint/Configuration/Model/Admin.cs @@ -1,5 +1,6 @@ using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Model; using System.Collections.Generic; using System.IO; using System.Text.Json; diff --git a/Endpoint/Configuration/Model/GeneralConfig.cs b/Endpoint/Configuration/Model/GeneralConfig.cs index f363517..331c681 100644 --- a/Endpoint/Configuration/Model/GeneralConfig.cs +++ b/Endpoint/Configuration/Model/GeneralConfig.cs @@ -1,6 +1,6 @@ using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; -using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Model; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 2d64f7d..7c3441e 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -18,7 +18,7 @@ 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.Security.Common.Domain; +using Mirea.Api.Security.Common.Model; using Mirea.Api.Security.Services; using MySqlConnector; using Npgsql; @@ -32,6 +32,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography; +using CookieOptions = Microsoft.AspNetCore.Http.CookieOptions; using PasswordPolicy = Mirea.Api.Dto.Common.PasswordPolicy; namespace Mirea.Api.Endpoint.Controllers.Configuration; @@ -539,7 +540,7 @@ public class SetupController( [TokenAuthentication] public ActionResult SetPasswordPolicy([FromBody] PasswordPolicy? policy = null) { - GeneralConfig.PasswordPolicy = policy?.ConvertFromDto() ?? new Security.Common.Domain.PasswordPolicy(); + GeneralConfig.PasswordPolicy = policy?.ConvertFromDto() ?? new Security.Common.Model.PasswordPolicy(); cache.Set("password", true); return true; } diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 8c0c7e8..7bf58f9 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -11,13 +11,14 @@ using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.MapperDto; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Model; -using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Model; using Mirea.Api.Security.Services; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using CookieOptions = Mirea.Api.Security.Common.Model.CookieOptions; using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; namespace Mirea.Api.Endpoint.Controllers.V1; @@ -26,7 +27,7 @@ namespace Mirea.Api.Endpoint.Controllers.V1; public class AuthController(IOptionsSnapshot user, IOptionsSnapshot generalConfig, AuthService auth, PasswordHashService passwordService, OAuthService oAuthService) : BaseController { - private CookieOptionsParameters GetCookieParams() => + private CookieOptions GetCookieParams() => new() { Domain = HttpContext.GetCurrentDomain(), @@ -53,6 +54,19 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot{title}

{title}

{blockInfo}

{message}

TraceId={traceId}
{script}"; } + /// + /// Handles the callback from an OAuth2 provider and finalizes the authorization process. + /// + /// + /// This method processes the response from an OAuth provider after the user authorizes the application. + /// Upon successful authorization, it redirects the user back to the specified callback URL. + /// + /// The authorization code returned by the OAuth provider. + /// The state parameter to ensure the request's integrity and prevent CSRF attacks. + /// + /// An HTML response indicating the success or failure of the authorization process. + /// If a callback URL is provided, the user will be redirected to it. + /// [HttpGet("OAuth2")] [BadRequestResponse] [Produces("text/html")] @@ -163,6 +177,7 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot user, IOptionsSnapshot LoginOAuthAsync(CookieOptionsParameters cookieOptions, HttpContext context, User user, - CancellationToken cancellation = default) - { - var requestContext = new RequestContextInfo(context, cookieOptions); - - if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) - { - await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id, cancellation); - return TwoFactorAuthenticator.None; - } - - await StoreFirstAuthTokenInCache(user, requestContext, cancellation); - - return user.TwoFactorAuthenticator; - } - - public async Task LoginAsync(CookieOptionsParameters cookieOptions, HttpContext context, TwoFactorAuthenticator authenticator, string code, + public async Task LoginAsync(CookieOptions cookieOptions, HttpContext context, TwoFactorAuthenticator authenticator, string code, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); @@ -176,12 +162,12 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return true; } - public async Task LoginAsync(CookieOptionsParameters cookieOptions, User user, HttpContext context, string password, - string username, CancellationToken cancellation = default) + private async Task LoginAsync(CookieOptions cookieOptions, + HttpContext context, + User user, + CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); - username = username.Trim(); - await VerifyUserOrThrowError(requestContext, user, password, username, cancellation); if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) { @@ -194,7 +180,27 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I return user.TwoFactorAuthenticator; } - public async Task RefreshTokenAsync(CookieOptionsParameters cookieOptions, HttpContext context, CancellationToken cancellation = default) + public Task LoginOAuthAsync(CookieOptions cookieOptions, + HttpContext context, + User user, + CancellationToken cancellation = default) => + LoginAsync(cookieOptions, context, user, cancellation); + + public async Task LoginAsync(CookieOptions cookieOptions, + HttpContext context, + User user, + string password, + string username, + CancellationToken cancellation = default) + { + var requestContext = new RequestContextInfo(context, cookieOptions); + username = username.Trim(); + await VerifyUserOrThrowError(requestContext, user, password, username, cancellation); + + return await LoginAsync(cookieOptions, context, user, cancellation); + } + + public async Task RefreshTokenAsync(CookieOptions cookieOptions, HttpContext context, CancellationToken cancellation = default) { const string defaultMessageError = "The session time has expired"; var requestContext = new RequestContextInfo(context, cookieOptions); @@ -271,7 +277,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); } - public async Task LogoutAsync(CookieOptionsParameters cookieOptions, HttpContext context, CancellationToken cancellation = default) + public async Task LogoutAsync(CookieOptions cookieOptions, HttpContext context, CancellationToken cancellation = default) { var requestContext = new RequestContextInfo(context, cookieOptions); diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 8ba5018..cc0c3dc 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -1,9 +1,10 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Mirea.Api.Security.Common.Domain; -using Mirea.Api.Security.Common.Domain.OAuth2; -using Mirea.Api.Security.Common.Domain.OAuth2.UserInfo; using Mirea.Api.Security.Common.Interfaces; +using Mirea.Api.Security.Common.OAuth2; +using Mirea.Api.Security.Common.OAuth2.UserInfo; +using Mirea.Api.Security.Common.ViewModel; using System; using System.Collections.Generic; using System.IO; @@ -15,6 +16,7 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using CookieOptions = Mirea.Api.Security.Common.Model.CookieOptions; namespace Mirea.Api.Security.Services; @@ -165,6 +167,7 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary [.. providers.Select(x => (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; - public async Task LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, + public async Task LoginOAuth(HttpContext context, CookieOptions cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) { - var result = new LoginOAuthResult() + var result = new LoginOAuth() { Token = GeneratorKey.GenerateBase64(32) }; diff --git a/Security/Services/PasswordPolicyService.cs b/Security/Services/PasswordPolicyService.cs index 63f421e..2179c9c 100644 --- a/Security/Services/PasswordPolicyService.cs +++ b/Security/Services/PasswordPolicyService.cs @@ -1,4 +1,4 @@ -using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Model; using System.Linq; using System.Security; -- 2.43.0 From 157708d00f43d0c2572516dd20e7dc328b7a74fd Mon Sep 17 00:00:00 2001 From: nikita Date: Thu, 26 Dec 2024 14:18:12 +0300 Subject: [PATCH 418/474] feat: store the result at each stage --- .../Domain/Caching/OAuthUserExtension.cs | 9 ++++ Security/Services/OAuthService.cs | 48 ++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 Security/Common/Domain/Caching/OAuthUserExtension.cs diff --git a/Security/Common/Domain/Caching/OAuthUserExtension.cs b/Security/Common/Domain/Caching/OAuthUserExtension.cs new file mode 100644 index 0000000..344663b --- /dev/null +++ b/Security/Common/Domain/Caching/OAuthUserExtension.cs @@ -0,0 +1,9 @@ +namespace Mirea.Api.Security.Common.Domain.Caching; + +internal class OAuthUserExtension +{ + public string? Message { get; set; } + public bool IsSuccess { get; set; } + public required OAuthProvider? Provider { get; set; } + public OAuthUser? User { get; set; } +} \ No newline at end of file diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index cc0c3dc..506ce72 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Mirea.Api.Security.Common.Domain; +using Mirea.Api.Security.Common.Domain.Caching; using Mirea.Api.Security.Common.Interfaces; using Mirea.Api.Security.Common.OAuth2; using Mirea.Api.Security.Common.OAuth2.UserInfo; @@ -11,6 +12,7 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Security; using System.Security.Cryptography; using System.Text; using System.Text.Json; @@ -166,7 +168,14 @@ public class OAuthService(ILogger logger, Dictionary + cache.SetAsync( + key, + JsonSerializer.SerializeToUtf8Bytes(data), + slidingExpiration: TimeSpan.FromMinutes(15), + cancellationToken: cancellation); + + public Uri GetProviderRedirect(HttpContext context, CookieOptions cookieOptions, string redirectUri, OAuthProvider provider, Uri callback) { @@ -205,17 +214,30 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary logger, Dictionary logger, Dictionary logger, Dictionary logger, Dictionary logger, Dictionary Date: Thu, 26 Dec 2024 14:32:28 +0300 Subject: [PATCH 419/474] feat: add a method for getting info about a token --- Security/Services/OAuthService.cs | 60 ++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 506ce72..61a5c29 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -12,7 +12,6 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; -using System.Security; using System.Security.Cryptography; using System.Text; using System.Text.Json; @@ -327,5 +326,64 @@ public class OAuthService(ILogger logger, Dictionary GetOAuthUser(HttpContext context, CookieOptions cookieOptions, string token, CancellationToken cancellation = default) + { + var requestInfo = new RequestContextInfo(context, cookieOptions); + var result = await cache.GetAsync(token, cancellation); + string tokenFailedKey = $"{requestInfo.Fingerprint}_oauth_token_failed"; + + if (result == null) + { + var failedTokenAttemptsCount = await cache.GetAsync( + tokenFailedKey, + cancellation) ?? 1; + + var failedTokenCacheExpiration = TimeSpan.FromHours(1); + + if (failedTokenAttemptsCount > 5) + { + logger.LogWarning( + "Multiple unsuccessful token attempts detected. Token {Token}, Fingerprint: {Fingerprint}. Attempt count: {AttemptCount}.", + token, + requestInfo.Fingerprint, + failedTokenAttemptsCount); + + return (null, "Too many unsuccessful token attempts. Please try again later.", false); + } + + logger.LogInformation( + "Cache data not found or expired for token: {Token}. Fingerprint: {Fingerprint}. Attempt count: {AttemptNumber}.", + token, + requestInfo.Fingerprint, + failedTokenAttemptsCount); + + await cache.SetAsync(tokenFailedKey, + failedTokenAttemptsCount + 1, + slidingExpiration: failedTokenCacheExpiration, + cancellationToken: cancellation); + + return (null, "Invalid or expired token.", false); + } + + await cache.RemoveAsync(tokenFailedKey, cancellation); + + const string log = "Cache data retrieved for token: {Token}. Fingerprint: {Fingerprint}."; + + if (result.User != null) + logger.LogInformation(log + "Provider: {Provider}. UserId: {UserId}.", + token, + requestInfo.Fingerprint, + result.User.Id, + result.Provider); + else if (result.Provider != null) + logger.LogInformation(log + "Provider: {Provider}.", + token, + requestInfo.Fingerprint, + result.Provider); + else + logger.LogInformation(log, token, requestInfo.Fingerprint); + + return (result.User, result.Message, result.IsSuccess); + } } \ No newline at end of file -- 2.43.0 From c4a4478b8c5f1ce33c86fffd1a134b8de57ead37 Mon Sep 17 00:00:00 2001 From: nikita Date: Thu, 26 Dec 2024 15:46:30 +0300 Subject: [PATCH 420/474] refactor: standardize the order of arguments --- Endpoint/Controllers/V1/AuthController.cs | 4 ++-- Security/Services/OAuthService.cs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 7bf58f9..93ab423 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -83,7 +83,7 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot user, IOptionsSnapshot logger, Dictionary logger, Dictionary [.. providers.Select(x => (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; - public async Task LoginOAuth(HttpContext context, CookieOptions cookieOptions, + public async Task LoginOAuth(CookieOptions cookieOptions, HttpContext context, string redirectUrl, string code, string state, CancellationToken cancellation = default) { var result = new LoginOAuth() @@ -326,7 +326,8 @@ public class OAuthService(ILogger logger, Dictionary GetOAuthUser(HttpContext context, CookieOptions cookieOptions, string token, CancellationToken cancellation = default) + public async Task<(OAuthUser? User, string? Message, bool IsSuccess)> + GetOAuthUser(CookieOptions cookieOptions, HttpContext context, string token, CancellationToken cancellation = default) { var requestInfo = new RequestContextInfo(context, cookieOptions); -- 2.43.0 From 5b7412f20f3a0080097c78934be04df1e35bd2b7 Mon Sep 17 00:00:00 2001 From: nikita Date: Thu, 26 Dec 2024 15:46:55 +0300 Subject: [PATCH 421/474] feat: return the provider --- Security/Services/OAuthService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index a74bada..27945df 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -326,7 +326,7 @@ public class OAuthService(ILogger logger, Dictionary + public async Task<(OAuthUser? User, string? Message, bool IsSuccess, OAuthProvider? Provider)> GetOAuthUser(CookieOptions cookieOptions, HttpContext context, string token, CancellationToken cancellation = default) { var requestInfo = new RequestContextInfo(context, cookieOptions); @@ -350,7 +350,7 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary logger, Dictionary Date: Thu, 26 Dec 2024 15:47:38 +0300 Subject: [PATCH 422/474] sec: add verification for OAuth authorization --- Security/Services/AuthService.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 97592b7..25234f9 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -183,8 +183,18 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I public Task LoginOAuthAsync(CookieOptions cookieOptions, HttpContext context, User user, - CancellationToken cancellation = default) => - LoginAsync(cookieOptions, context, user, cancellation); + OAuthUser oAuthUser, + OAuthProvider provider, + CancellationToken cancellation = default) + { + if (user.OAuthProviders == null || !user.OAuthProviders.TryGetValue(provider, out var value)) + throw new SecurityException($"This provider '{Enum.GetName(provider)}' is not linked to the account."); + + if (value.Id != oAuthUser.Id) + throw new SecurityException("This account was not linked"); + + return LoginAsync(cookieOptions, context, user, cancellation); + } public async Task LoginAsync(CookieOptions cookieOptions, HttpContext context, -- 2.43.0 From 9d5007ef3abb624c7e6836de32fce90bf8063920 Mon Sep 17 00:00:00 2001 From: nikita Date: Thu, 26 Dec 2024 16:14:28 +0300 Subject: [PATCH 423/474] refactor: add user converter --- Endpoint/Common/MapperDto/UserConverter.cs | 20 ++++++++++++++++++++ Endpoint/Controllers/V1/AuthController.cs | 12 +----------- 2 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 Endpoint/Common/MapperDto/UserConverter.cs diff --git a/Endpoint/Common/MapperDto/UserConverter.cs b/Endpoint/Common/MapperDto/UserConverter.cs new file mode 100644 index 0000000..920e340 --- /dev/null +++ b/Endpoint/Common/MapperDto/UserConverter.cs @@ -0,0 +1,20 @@ +using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Security.Common.Model; + +namespace Mirea.Api.Endpoint.Common.MapperDto; + +public static class UserConverter +{ + public static User ConvertToSecurity(this Admin data) => + new() + { + Id = 1.ToString(), + Email = data.Email, + Username = data.Username, + PasswordHash = data.PasswordHash, + Salt = data.Salt, + SecondFactorToken = data.Secret, + TwoFactorAuthenticator = data.TwoFactorAuthenticator, + OAuthProviders = data.OAuthProviders + }; +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 93ab423..640c80c 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -178,17 +178,7 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot Date: Thu, 26 Dec 2024 16:14:55 +0300 Subject: [PATCH 424/474] feat: add a token handler --- ApiDto/Common/OAuthAction.cs | 17 ++++++ Endpoint/Controllers/V1/AuthController.cs | 64 ++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 ApiDto/Common/OAuthAction.cs diff --git a/ApiDto/Common/OAuthAction.cs b/ApiDto/Common/OAuthAction.cs new file mode 100644 index 0000000..1cc0908 --- /dev/null +++ b/ApiDto/Common/OAuthAction.cs @@ -0,0 +1,17 @@ +namespace Mirea.Api.Dto.Common; + +/// +/// Defines the actions that can be performed with an OAuth token. +/// +public enum OAuthAction +{ + /// + /// The action to log in the user using the provided OAuth token. + /// + Login, + + /// + /// The action to bind an OAuth provider to the user's account. + /// + Bind +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 640c80c..544533d 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -11,12 +11,13 @@ using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.MapperDto; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Model; -using Mirea.Api.Security.Common.Model; using Mirea.Api.Security.Services; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; using CookieOptions = Mirea.Api.Security.Common.Model.CookieOptions; using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; @@ -164,6 +165,67 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot + /// Processes the OAuth token + ///
+ /// The OAuth token used for authentication or binding. + /// The action to be performed: Login or Bind. + /// If return Ok. If return + [HttpGet("HandleToken")] + [MaintenanceModeIgnore] + [BadRequestResponse] + public async Task HandleToken([FromQuery][MinLength(2)] string token, [FromQuery] OAuthAction action) + { + var (oAuthUser, error, isSuccess, provider) = await oAuthService.GetOAuthUser(GetCookieParams(), HttpContext, token); + + if (!isSuccess || oAuthUser == null || provider == null) + throw new ControllerArgumentException(error ?? "Token processing error."); + + switch (action) + { + case OAuthAction.Login: + return Ok(await auth.LoginOAuthAsync(GetCookieParams(), HttpContext, user.Value.ConvertToSecurity(), oAuthUser, provider.Value)); + + case OAuthAction.Bind: + var userId = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + var admin = user.Value; + + if (string.IsNullOrEmpty(userId) || !int.TryParse(userId, out var result) || result != 1) + return Unauthorized(new ProblemDetails + { + Type = "https://tools.ietf.org/html/rfc9110#section-15.5.2", + Title = "Unauthorized", + Status = StatusCodes.Status401Unauthorized, + Detail = "The user is not logged in to link accounts.", + Extensions = new Dictionary() + { + { "traceId", HttpContext.TraceIdentifier } + } + }); + + if (admin.OAuthProviders != null && admin.OAuthProviders.ContainsKey(provider.Value)) + return Conflict(new ProblemDetails + { + Type = "https://tools.ietf.org/html/rfc9110#section-15.5.10", + Title = "Conflict", + Status = StatusCodes.Status409Conflict, + Detail = "This OAuth provider is already associated with the account.", + Extensions = new Dictionary() + { + { "traceId", HttpContext.TraceIdentifier } + } + }); + + admin.OAuthProviders ??= []; + admin.OAuthProviders.Add(provider.Value, oAuthUser); + admin.SaveSetting(); + + return Ok(); + default: + throw new ControllerArgumentException("The action cannot be processed."); + } + } + /// /// Logs in a user using their username or email and password. /// -- 2.43.0 From 0c6d1c9bfb0a72447cb0ecdce4dddfc309b4a18d Mon Sep 17 00:00:00 2001 From: nikita Date: Thu, 26 Dec 2024 16:16:33 +0300 Subject: [PATCH 425/474] refactor: compact two factor auth --- Endpoint/Controllers/V1/AuthController.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 544533d..2b01b54 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -253,11 +253,8 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshotA boolean indicating whether the two-factor authentication was successful. [HttpPost("2FA")] [BadRequestResponse] - public async Task> TwoFactorAuth([FromBody] TwoFactorAuthRequest request) - { - var tokenResult = await auth.LoginAsync(GetCookieParams(), HttpContext, request.Method.ConvertFromDto(), request.Code); - return Ok(tokenResult); - } + public async Task> TwoFactorAuth([FromBody] TwoFactorAuthRequest request) => + await auth.LoginAsync(GetCookieParams(), HttpContext, request.Method.ConvertFromDto(), request.Code); /// /// Refreshes the authentication token using the existing refresh token. -- 2.43.0 From 7f87b4d8566c3c9823194580daa9bec43990a3d3 Mon Sep 17 00:00:00 2001 From: nikita Date: Thu, 26 Dec 2024 16:38:13 +0300 Subject: [PATCH 426/474] style: add space between point and provider --- Security/Services/OAuthService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 27945df..621a566 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -372,13 +372,13 @@ public class OAuthService(ILogger logger, Dictionary Date: Thu, 26 Dec 2024 16:38:53 +0300 Subject: [PATCH 427/474] refactor: add standard traceId --- Endpoint/Controllers/V1/AuthController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 2b01b54..b7c49f3 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -199,7 +199,7 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot() { - { "traceId", HttpContext.TraceIdentifier } + { "traceId", Activity.Current?.Id ?? HttpContext.TraceIdentifier } } }); @@ -212,7 +212,7 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot() { - { "traceId", HttpContext.TraceIdentifier } + { "traceId", Activity.Current?.Id ?? HttpContext.TraceIdentifier } } }); -- 2.43.0 From 538f1d67c855bb3f499fc0e16dd505c779fb28bd Mon Sep 17 00:00:00 2001 From: nikita Date: Thu, 26 Dec 2024 16:39:29 +0300 Subject: [PATCH 428/474] fix: change the link to the error type --- .../Core/Middleware/CustomExceptionHandlerMiddleware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index 23c848b..ad6cca5 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -34,7 +34,7 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger Date: Thu, 26 Dec 2024 16:40:30 +0300 Subject: [PATCH 429/474] sec: do not return the error text to the user --- .../Core/Middleware/CustomExceptionHandlerMiddleware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index ad6cca5..a82854f 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -37,7 +37,7 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger() { { "traceId", traceId } -- 2.43.0 From 61a11ea2233a291a58afd37d8968b2704e4ff5a8 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 28 Dec 2024 06:47:21 +0300 Subject: [PATCH 430/474] fix: return exception message if controller exception --- .../Core/Middleware/CustomExceptionHandlerMiddleware.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index a82854f..f7871fe 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -65,6 +65,7 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger Date: Sat, 28 Dec 2024 07:46:06 +0300 Subject: [PATCH 431/474] refactor: to enable oauth during registration, use the appropriate controller. --- .../Configuration/SetupController.cs | 57 ++++++++++++++----- Endpoint/Controllers/V1/AuthController.cs | 1 - Security/Services/OAuthService.cs | 2 +- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs index 7c3441e..cf974a7 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Requests.Configuration; @@ -18,6 +17,7 @@ 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.Security.Common.Domain; using Mirea.Api.Security.Common.Model; using Mirea.Api.Security.Services; using MySqlConnector; @@ -26,13 +26,17 @@ using Serilog; using StackExchange.Redis; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Data; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography; +using System.Threading.Tasks; using CookieOptions = Microsoft.AspNetCore.Http.CookieOptions; +using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; using PasswordPolicy = Mirea.Api.Dto.Common.PasswordPolicy; namespace Mirea.Api.Endpoint.Controllers.Configuration; @@ -45,7 +49,7 @@ public class SetupController( IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache, PasswordHashService passwordHashService, - IOptionsSnapshot user) : BaseController + OAuthService oAuthService) : BaseController { private const string CacheGeneralKey = "config_general"; private const string CacheAdminKey = "config_admin"; @@ -319,29 +323,54 @@ public class SetupController( return Ok(true); } - [HttpGet("UpdateAdminConfiguration")] + [HttpGet("HandleToken")] [TokenAuthentication] - public ActionResult UpdateAdminConfiguration() + public async Task HandleToken([FromQuery][MinLength(2)] string token) { - if (string.IsNullOrEmpty(user.Value.Email)) - return Ok(); + var (user, error, isSuccess, provider) = await oAuthService.GetOAuthUser(new Security.Common.Model.CookieOptions + { + Domain = HttpContext.GetCurrentDomain(), + Path = UrlHelper.GetSubPathWithoutFirstApiName + "api" + }, HttpContext, token); + + if (!isSuccess || user == null || provider == null) + throw new ControllerArgumentException(error ?? "Token processing error."); if (!cache.TryGetValue(CacheAdminKey, out var admin)) { - admin = user.Value; + admin = new Admin() + { + Email = user.Email ?? string.Empty, + Username = user.Username ?? string.Empty, + PasswordHash = string.Empty, + Salt = string.Empty, + OAuthProviders = new Dictionary + { + {provider.Value, user} + } + }; + cache.Set(CacheAdminKey, admin); return Ok(); } - admin!.OAuthProviders = user.Value.OAuthProviders; - - if (string.IsNullOrEmpty(admin.Email)) - admin.Email = user.Value.Email; - - if (string.IsNullOrEmpty(admin.Username)) - admin.Username = user.Value.Username; + if (admin!.OAuthProviders != null && admin.OAuthProviders.ContainsKey(provider.Value)) + return Conflict(new ProblemDetails + { + Type = "https://tools.ietf.org/html/rfc9110#section-15.5.10", + Title = "Conflict", + Status = StatusCodes.Status409Conflict, + Detail = "This OAuth provider is already associated with the account.", + Extensions = new Dictionary() + { + { "traceId", Activity.Current?.Id ?? HttpContext.TraceIdentifier } + } + }); + admin.OAuthProviders ??= []; + admin.OAuthProviders.Add(provider.Value, user); cache.Set(CacheAdminKey, admin); + return Ok(); } diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index b7c49f3..2588dd8 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -172,7 +172,6 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshotThe action to be performed: Login or Bind. /// If return Ok. If return [HttpGet("HandleToken")] - [MaintenanceModeIgnore] [BadRequestResponse] public async Task HandleToken([FromQuery][MinLength(2)] string token, [FromQuery] OAuthAction action) { diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 621a566..1145fe1 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -332,7 +332,7 @@ public class OAuthService(ILogger logger, Dictionary(token, cancellation); - string tokenFailedKey = $"{requestInfo.Fingerprint}_oauth_token_failed"; + var tokenFailedKey = $"{requestInfo.Fingerprint}_oauth_token_failed"; if (result == null) { -- 2.43.0 From d9f4176acafc716b416cf07dc57268ae12c045bf Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 28 Dec 2024 08:15:43 +0300 Subject: [PATCH 432/474] fix: return message if 401 --- .../Core/Middleware/CustomExceptionHandlerMiddleware.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs index f7871fe..5105512 100644 --- a/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs +++ b/Endpoint/Configuration/Core/Middleware/CustomExceptionHandlerMiddleware.cs @@ -71,6 +71,7 @@ public class CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger Date: Sat, 28 Dec 2024 08:16:00 +0300 Subject: [PATCH 433/474] fix: add force select account --- Security/Services/OAuthService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 1145fe1..25e67bb 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -193,7 +193,9 @@ public class OAuthService(ILogger logger, Dictionary Date: Sat, 28 Dec 2024 08:29:06 +0300 Subject: [PATCH 434/474] refactor: improve logging --- Security/Services/AuthService.cs | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 25234f9..38ee706 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -226,26 +226,18 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I cookieOptions.DropCookie(context, CookieNames.AccessToken); cookieOptions.DropCookie(context, CookieNames.RefreshToken); - const string error = "Token validation failed for user ID {UserId}. Fingerprint: {Fingerprint}. "; - if (authToken.RefreshToken != requestContext.RefreshToken) - logger.LogWarning( - error + - "Cached refresh token {ExpectedRefreshToken} does not match the provided refresh token {RefreshToken}", - authToken.UserId, - authToken.Fingerprint, - authToken.RefreshToken, - requestContext.RefreshToken); - else - logger.LogWarning( - error + - "User-Agent {ExpectedUserAgent} and IP {ExpectedUserIp} in cache do not match the provided " + - "User-Agent {ProvidedUserAgent} and IP {ProvidedIp}", - authToken.UserId, - authToken.Fingerprint, - authToken.UserAgent, - authToken.Ip, - requestContext.UserAgent, - requestContext.Ip); + logger.LogWarning("Token validation failed for user ID {UserId}. Fingerprint: {Fingerprint}. " + + "RefreshToken: {ExpectedRefreshToken} -> {RefreshToken}, " + + "UserAgent: {ExpectedUserAgent} -> {ProvidedUserAgent}, " + + "Ip: {ExpectedUserIp} -> {ProvidedIp}", + authToken.UserId, + authToken.Fingerprint, + authToken.RefreshToken, + requestContext.RefreshToken, + authToken.UserAgent, + requestContext.UserAgent, + authToken.Ip, + requestContext.Ip); throw new SecurityException(defaultMessageError); } -- 2.43.0 From e79ddf220ffbdf8e5b808d6eadf56ab13e9a0cf3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 28 Dec 2024 08:29:31 +0300 Subject: [PATCH 435/474] sec: set the absolute time of the token --- Security/Services/OAuthService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 25e67bb..814052d 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -171,7 +171,7 @@ public class OAuthService(ILogger logger, Dictionary Date: Sat, 28 Dec 2024 08:30:56 +0300 Subject: [PATCH 436/474] sec: to establish the ownership of the token for the first one who received it --- .../Domain/Caching/OAuthUserExtension.cs | 3 ++ Security/Services/OAuthService.cs | 34 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Security/Common/Domain/Caching/OAuthUserExtension.cs b/Security/Common/Domain/Caching/OAuthUserExtension.cs index 344663b..de0578a 100644 --- a/Security/Common/Domain/Caching/OAuthUserExtension.cs +++ b/Security/Common/Domain/Caching/OAuthUserExtension.cs @@ -5,5 +5,8 @@ internal class OAuthUserExtension public string? Message { get; set; } public bool IsSuccess { get; set; } public required OAuthProvider? Provider { get; set; } + public string? UserAgent { get; set; } = null; + public string? Ip { get; set; } = null; + public string? Fingerprint { get; set; } = null; public OAuthUser? User { get; set; } } \ No newline at end of file diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 814052d..cddeea3 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -369,8 +369,6 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary {ProvidedFingerprint}, " + + "UserAgent: {ExpectedUserAgent} -> {ProvidedUserAgent}, " + + "Ip: {ExpectedUserIp} -> {ProvidedIp}", + token, + result.Fingerprint, + requestInfo.Fingerprint, + result.UserAgent, + requestInfo.UserAgent, + result.Ip, + requestInfo.Ip); + + await cache.RemoveAsync(token, cancellation); + + return (null, "Invalid or expired token.", false, null); + } + + await cache.RemoveAsync(tokenFailedKey, cancellation); + + result.Ip = requestInfo.Ip; + result.UserAgent = requestInfo.UserAgent; + result.Fingerprint = requestInfo.Fingerprint; + return (result.User, result.Message, result.IsSuccess, result.Provider); } } \ No newline at end of file -- 2.43.0 From 92081156cf96f90410352bcc22c18597e4f03637 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 28 Dec 2024 08:34:19 +0300 Subject: [PATCH 437/474] fix: save token after update --- Security/Services/OAuthService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index cddeea3..6a2389b 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -417,6 +417,8 @@ public class OAuthService(ILogger logger, Dictionary Date: Fri, 24 Jan 2025 17:10:18 +0300 Subject: [PATCH 438/474] refactor: change const name to class with name --- Security/Common/Domain/RequestContextInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/Common/Domain/RequestContextInfo.cs b/Security/Common/Domain/RequestContextInfo.cs index 1e522a5..9571f61 100644 --- a/Security/Common/Domain/RequestContextInfo.cs +++ b/Security/Common/Domain/RequestContextInfo.cs @@ -28,7 +28,7 @@ internal class RequestContextInfo UserAgent = userAgent; Fingerprint = fingerprint; Ip = ip; - RefreshToken = context.Request.Cookies["refresh_token"] ?? string.Empty; + RefreshToken = context.Request.Cookies[CookieNames.RefreshToken] ?? string.Empty; } public string UserAgent { get; private set; } -- 2.43.0 From 7eb307b65ed08984920a5be4db3af9bdc00ec823 Mon Sep 17 00:00:00 2001 From: nikita Date: Fri, 24 Jan 2025 17:10:46 +0300 Subject: [PATCH 439/474] fix: return empty string if null --- Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index a9ed8c5..775ef8f 100644 --- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -95,7 +95,7 @@ public static class LoggerConfiguration diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value); diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme); diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent); - diagnosticContext.Set("RemoteIPAddress", httpContext.Connection.RemoteIpAddress?.ToString()); + diagnosticContext.Set("RemoteIPAddress", httpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty); }; }); } -- 2.43.0 From fd26178a24267653c968e729950d2e4307171652 Mon Sep 17 00:00:00 2001 From: nikita Date: Fri, 24 Jan 2025 17:12:39 +0300 Subject: [PATCH 440/474] build: update ref --- Endpoint/Endpoint.csproj | 28 +++++++++++++------------- Security/Security.csproj | 4 ++-- SqlData/Application/Application.csproj | 2 +- SqlData/Persistence/Persistence.csproj | 18 ++++++++--------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 2692750..1b03869 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -24,43 +24,43 @@ - - + + - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + - - + + - - + + - + diff --git a/Security/Security.csproj b/Security/Security.csproj index 6854234..a56c5b5 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index b7f766d..8cf1e6f 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -16,7 +16,7 @@ - + diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index df77b4e..0dfce28 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -13,16 +13,16 @@ - - - - - - - + + + + + + + - - + + -- 2.43.0 From 38fba5556fd3a446bdc99140922650e6706e6611 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Feb 2025 16:46:20 +0300 Subject: [PATCH 441/474] feat: add filter by type of occupation (lesson type) --- ApiDto/Requests/ScheduleRequest.cs | 10 +++--- Endpoint/Controllers/V1/ImportController.cs | 3 +- Endpoint/Controllers/V1/ScheduleController.cs | 31 +++++++++++++------ .../GetScheduleList/GetScheduleListQuery.cs | 1 + .../GetScheduleListQueryHandler.cs | 3 ++ 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/ApiDto/Requests/ScheduleRequest.cs b/ApiDto/Requests/ScheduleRequest.cs index 476d8f3..dc5dbb6 100644 --- a/ApiDto/Requests/ScheduleRequest.cs +++ b/ApiDto/Requests/ScheduleRequest.cs @@ -8,30 +8,30 @@ public class ScheduleRequest /// /// Gets or sets an array of group IDs. /// - /// This array can contain null values. public int[]? Groups { get; set; } = null; /// /// Gets or sets a value indicating whether to retrieve schedules for even weeks. /// - /// This property can contain null. public bool? IsEven { get; set; } = null; /// /// Gets or sets an array of discipline IDs. /// - /// This array can contain null values. public int[]? Disciplines { get; set; } = null; /// /// Gets or sets an array of professor IDs. /// - /// This array can contain null values. public int[]? Professors { get; set; } = null; /// /// Gets or sets an array of lecture hall IDs. /// - /// This array can contain null values. public int[]? LectureHalls { get; set; } = null; + + /// + /// Gets or sets an array of lesson type IDs. + /// + public int[]? LessonType { get; set; } = null; } diff --git a/Endpoint/Controllers/V1/ImportController.cs b/Endpoint/Controllers/V1/ImportController.cs index 7b76fef..835b86a 100644 --- a/Endpoint/Controllers/V1/ImportController.cs +++ b/Endpoint/Controllers/V1/ImportController.cs @@ -48,7 +48,8 @@ public class ImportController(IMediator mediator, IOptionsSnapshotAn array of discipline IDs. /// An array of professor IDs. /// An array of lecture hall IDs. + /// An array of type of occupation IDs. /// A response containing schedules for the specified group. [HttpGet("GetByGroup/{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] @@ -110,14 +112,16 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot + [FromQuery] int[]? lectureHalls = null, + [FromQuery] int[]? lessonType = null) => await Get(new ScheduleRequest { Disciplines = disciplines, IsEven = isEven, Groups = [id], Professors = professors, - LectureHalls = lectureHalls + LectureHalls = lectureHalls, + LessonType = lessonType }); /// @@ -128,6 +132,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshotAn array of discipline IDs. /// An array of group IDs. /// An array of lecture hall IDs. + /// An array of type of occupation IDs. /// A response containing schedules for the specified professor. [HttpGet("GetByProfessor/{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] @@ -137,14 +142,16 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot + [FromQuery] int[]? lectureHalls = null, + [FromQuery] int[]? lessonType = null) => await Get(new ScheduleRequest { Disciplines = disciplines, IsEven = isEven, Groups = groups, Professors = [id], - LectureHalls = lectureHalls + LectureHalls = lectureHalls, + LessonType = lessonType }); /// @@ -155,6 +162,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshotAn array of discipline IDs. /// An array of professor IDs. /// An array of group IDs. + /// An array of type of occupation IDs. /// A response containing schedules for the specified lecture hall. [HttpGet("GetByLectureHall/{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] @@ -164,14 +172,16 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot + [FromQuery] int[]? professors = null, + [FromQuery] int[]? lessonType = null) => await Get(new ScheduleRequest { Disciplines = disciplines, IsEven = isEven, Groups = groups, Professors = professors, - LectureHalls = [id] + LectureHalls = [id], + LessonType = lessonType }); /// @@ -182,6 +192,7 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshotAn array of group IDs. /// An array of professor IDs. /// An array of lecture hall IDs. + /// An array of type of occupation IDs. /// A response containing schedules for the specified discipline. [HttpGet("GetByDiscipline/{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] @@ -191,13 +202,15 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot + [FromQuery] int[]? lectureHalls = null, + [FromQuery] int[]? lessonType = null) => await Get(new ScheduleRequest { Disciplines = [id], IsEven = isEven, Groups = groups, Professors = professors, - LectureHalls = lectureHalls + LectureHalls = lectureHalls, + LessonType = lessonType }); } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs index 939e1c6..dd42846 100644 --- a/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs +++ b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQuery.cs @@ -8,5 +8,6 @@ public class GetScheduleListQuery : IRequest public int[]? DisciplineIds { get; set; } public int[]? LectureHallIds { get; set; } public int[]? ProfessorIds { get; set; } + public int[]? LessonTypeIds { get; set; } public bool? IsEven { get; set; } } \ No newline at end of file diff --git a/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs index 626bee5..646109c 100644 --- a/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs +++ b/SqlData/Application/Cqrs/Schedule/Queries/GetScheduleList/GetScheduleListQueryHandler.cs @@ -16,6 +16,9 @@ public class GetScheduleListQueryHandler(ILessonDbContext dbContext) : IRequestH if (request.IsEven != null) query = query.Where(l => l.IsEven == request.IsEven); + if (request.LessonTypeIds != null && request.LessonTypeIds.Length != 0) + query = query.Where(l => l.LessonAssociations!.Any(la => request.LessonTypeIds.Contains(la.TypeOfOccupationId))); + if (request.GroupIds != null && request.GroupIds.Length != 0) query = query.Where(l => request.GroupIds.Contains(l.GroupId)); -- 2.43.0 From 5bcb7bfbc19a5b476610255f6275c3794bfaebd7 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Feb 2025 17:06:02 +0300 Subject: [PATCH 442/474] feat: allow filter by lesson type --- Endpoint/Controllers/V1/ScheduleController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Endpoint/Controllers/V1/ScheduleController.cs b/Endpoint/Controllers/V1/ScheduleController.cs index 9057629..bb76a79 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -51,7 +51,8 @@ public class ScheduleController(IMediator mediator, IOptionsSnapshot Date: Sat, 1 Feb 2025 17:08:00 +0300 Subject: [PATCH 443/474] feat: add lesson type controller --- ApiDto/Responses/FacultyResponse.cs | 2 +- ApiDto/Responses/LessonTypeResponse.cs | 21 +++++++++ .../Controllers/V1/LessonTypeController.cs | 43 +++++++++++++++++++ .../GetTypeOfOccupationListQuery.cs | 9 ++++ .../GetTypeOfOccupationListQueryHandler.cs | 31 +++++++++++++ .../TypeOfOccupationListVm.cs | 14 ++++++ .../TypeOfOccupationLookupDto.cs | 17 ++++++++ 7 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 ApiDto/Responses/LessonTypeResponse.cs create mode 100644 Endpoint/Controllers/V1/LessonTypeController.cs create mode 100644 SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/GetTypeOfOccupationListQuery.cs create mode 100644 SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/GetTypeOfOccupationListQueryHandler.cs create mode 100644 SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/TypeOfOccupationListVm.cs create mode 100644 SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/TypeOfOccupationLookupDto.cs diff --git a/ApiDto/Responses/FacultyResponse.cs b/ApiDto/Responses/FacultyResponse.cs index 1e172c2..8eafaba 100644 --- a/ApiDto/Responses/FacultyResponse.cs +++ b/ApiDto/Responses/FacultyResponse.cs @@ -3,7 +3,7 @@ namespace Mirea.Api.Dto.Responses; /// -/// Represents basic information about a faculty. +/// Represents information about a faculty. /// public class FacultyResponse { diff --git a/ApiDto/Responses/LessonTypeResponse.cs b/ApiDto/Responses/LessonTypeResponse.cs new file mode 100644 index 0000000..9b3bf2d --- /dev/null +++ b/ApiDto/Responses/LessonTypeResponse.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses; + +/// +/// Represents information about a lesson type. +/// +public class LessonTypeResponse +{ + /// + /// Gets or sets the unique identifier of the lesson type. + /// + [Required] + public int Id { get; set; } + + /// + /// Gets or sets the name of the lesson type. + /// + [Required] + public required string Name { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/LessonTypeController.cs b/Endpoint/Controllers/V1/LessonTypeController.cs new file mode 100644 index 0000000..1a9c2e6 --- /dev/null +++ b/Endpoint/Controllers/V1/LessonTypeController.cs @@ -0,0 +1,43 @@ +using Asp.Versioning; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Queries.GetTypeOfOccupationList; +using Mirea.Api.Dto.Responses; +using Mirea.Api.Endpoint.Common.Attributes; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace Mirea.Api.Endpoint.Controllers.V1; + +[ApiVersion("1.0")] +[CacheMaxAge(true)] +public class LessonTypeController(IMediator mediator) : BaseController +{ + /// + /// Gets a paginated list of type of occupation. + /// + /// Page number. Start from 0. + /// Number of items per page. + /// Paginated list of type of occupation. + [HttpGet] + [BadRequestResponse] + public async Task>> Get([FromQuery][Range(0, int.MaxValue)] int? page, + [FromQuery][Range(1, int.MaxValue)] int? pageSize) + { + var result = await mediator.Send(new GetTypeOfOccupationListQuery() + { + Page = page, + PageSize = pageSize + }); + + return Ok(result.TypeOfOccupations + .Select(f => new LessonTypeResponse() + { + Id = f.Id, + Name = f.Name + }) + ); + } +} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/GetTypeOfOccupationListQuery.cs b/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/GetTypeOfOccupationListQuery.cs new file mode 100644 index 0000000..a6dcd96 --- /dev/null +++ b/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/GetTypeOfOccupationListQuery.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Queries.GetTypeOfOccupationList; + +public class GetTypeOfOccupationListQuery : IRequest +{ + public int? Page { get; set; } + public int? PageSize { get; set; } +} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/GetTypeOfOccupationListQueryHandler.cs b/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/GetTypeOfOccupationListQueryHandler.cs new file mode 100644 index 0000000..7e8fbc2 --- /dev/null +++ b/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/GetTypeOfOccupationListQueryHandler.cs @@ -0,0 +1,31 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Queries.GetTypeOfOccupationList; + +public class GetTypeOfOccupationListQueryHandler(ITypeOfOccupationDbContext dbContext) : IRequestHandler +{ + public async Task Handle(GetTypeOfOccupationListQuery request, CancellationToken cancellationToken) + { + var dtos = dbContext.TypeOfOccupations.OrderBy(t => t.Id) + .Select(t => new TypeOfOccupationLookupDto() + { + Id = t.Id, + Name = t.ShortName + }); + + if (request is { PageSize: not null, Page: not null }) + dtos = dtos.Skip(request.Page.Value * request.PageSize.Value).Take(request.PageSize.Value); + + var result = await dtos.ToListAsync(cancellationToken); + + return new TypeOfOccupationListVm + { + TypeOfOccupations = result + }; + } +} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/TypeOfOccupationListVm.cs b/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/TypeOfOccupationListVm.cs new file mode 100644 index 0000000..ea2f92e --- /dev/null +++ b/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/TypeOfOccupationListVm.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Queries.GetTypeOfOccupationList; + +/// +/// Represents a view model containing multiple type of occupations. +/// +public class TypeOfOccupationListVm +{ + /// + /// The list of type of occupations. + /// + public IEnumerable TypeOfOccupations { get; set; } = []; +} \ No newline at end of file diff --git a/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/TypeOfOccupationLookupDto.cs b/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/TypeOfOccupationLookupDto.cs new file mode 100644 index 0000000..740826f --- /dev/null +++ b/SqlData/Application/Cqrs/TypeOfOccupation/Queries/GetTypeOfOccupationList/TypeOfOccupationLookupDto.cs @@ -0,0 +1,17 @@ +namespace Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Queries.GetTypeOfOccupationList; + +/// +/// Represents type of occupations. +/// +public class TypeOfOccupationLookupDto +{ + /// + /// The unique identifier for the occupation. + /// + public int Id { get; set; } + + /// + /// The name of the occupation. + /// + public required string Name { get; set; } +} \ No newline at end of file -- 2.43.0 From bc86e077bd135dc289df68c26909f3589f5b7358 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Feb 2025 19:39:02 +0300 Subject: [PATCH 444/474] refactor: move to SetupConfiguration namespace --- .../{Configuration => SetupConfiguration}/SetupController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Endpoint/Controllers/{Configuration => SetupConfiguration}/SetupController.cs (99%) diff --git a/Endpoint/Controllers/Configuration/SetupController.cs b/Endpoint/Controllers/SetupConfiguration/SetupController.cs similarity index 99% rename from Endpoint/Controllers/Configuration/SetupController.cs rename to Endpoint/Controllers/SetupConfiguration/SetupController.cs index cf974a7..4477a0c 100644 --- a/Endpoint/Controllers/Configuration/SetupController.cs +++ b/Endpoint/Controllers/SetupConfiguration/SetupController.cs @@ -39,7 +39,7 @@ using CookieOptions = Microsoft.AspNetCore.Http.CookieOptions; using OAuthProvider = Mirea.Api.Security.Common.Domain.OAuthProvider; using PasswordPolicy = Mirea.Api.Dto.Common.PasswordPolicy; -namespace Mirea.Api.Endpoint.Controllers.Configuration; +namespace Mirea.Api.Endpoint.Controllers.SetupConfiguration; [ApiVersion("1.0")] [MaintenanceModeIgnore] -- 2.43.0 From 52de98969dee421b2275eb892ebb8812a03d628e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Feb 2025 20:45:08 +0300 Subject: [PATCH 445/474] refactor: remove unused brackets --- .../Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs | 2 -- Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs | 4 ---- 2 files changed, 6 deletions(-) diff --git a/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs index eae4d2e..2de861c 100644 --- a/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs +++ b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs @@ -12,9 +12,7 @@ public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : public void Configure(SwaggerGenOptions options) { foreach (var description in provider.ApiVersionDescriptions) - { options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); - } } private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) diff --git a/Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs b/Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs index 290f981..a6b9dd4 100644 --- a/Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs +++ b/Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs @@ -23,16 +23,12 @@ public class SwaggerDefaultValues : IOperationFilter foreach (var contentType in response.Content.Keys) { if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType)) - { response.Content.Remove(contentType); - } } } if (operation.Parameters == null) - { return; - } foreach (var parameter in operation.Parameters) { -- 2.43.0 From 5870eef552d85c985269be135ee873b5521ae063 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Feb 2025 20:47:08 +0300 Subject: [PATCH 446/474] feat: add a tag schema to combine similar controllers. --- .../Core/Startup/SwaggerConfiguration.cs | 1 + .../SwaggerOptions/SwaggerTagSchemeFilter.cs | 40 +++++++++++++++++++ .../ConfigurationBaseController.cs | 11 +++++ 3 files changed, 52 insertions(+) create mode 100644 Endpoint/Configuration/SwaggerOptions/SwaggerTagSchemeFilter.cs create mode 100644 Endpoint/Controllers/ConfigurationBaseController.cs diff --git a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs index 8d51276..9947041 100644 --- a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs @@ -17,6 +17,7 @@ public static class SwaggerConfiguration { services.AddSwaggerGen(options => { + options.OperationFilter(); options.SchemaFilter(); options.OperationFilter(); options.OperationFilter(); diff --git a/Endpoint/Configuration/SwaggerOptions/SwaggerTagSchemeFilter.cs b/Endpoint/Configuration/SwaggerOptions/SwaggerTagSchemeFilter.cs new file mode 100644 index 0000000..3e3291b --- /dev/null +++ b/Endpoint/Configuration/SwaggerOptions/SwaggerTagSchemeFilter.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Linq; +using System.Reflection; + +namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; + +public class SwaggerTagSchemeFilter : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (context.ApiDescription.ActionDescriptor is not ControllerActionDescriptor controllerActionDescriptor) + return; + + var controllerType = controllerActionDescriptor.ControllerTypeInfo; + + var tagsAttribute = controllerType.GetCustomAttributes(inherit: true).FirstOrDefault(); + + if (tagsAttribute == null) + { + var baseType = controllerType.BaseType; + while (baseType != null) + { + tagsAttribute = baseType.GetCustomAttributes(inherit: true).FirstOrDefault(); + if (tagsAttribute != null) + break; + + baseType = baseType.BaseType; + } + } + + if (tagsAttribute == null) + return; + + operation.Tags ??= []; + operation.Tags.Add(new OpenApiTag { Name = tagsAttribute.Tags[0] }); + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/ConfigurationBaseController.cs b/Endpoint/Controllers/ConfigurationBaseController.cs new file mode 100644 index 0000000..7b8bdb3 --- /dev/null +++ b/Endpoint/Controllers/ConfigurationBaseController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Mirea.Api.Endpoint.Controllers; + +[Route("api/v{version:apiVersion}/Configuration/[controller]")] +[Authorize] +[ProducesResponseType(StatusCodes.Status401Unauthorized)] +[Tags("Configuration")] +public class ConfigurationBaseController : BaseController; \ No newline at end of file -- 2.43.0 From 2453b2bd51e09c72ab4c26af6613408fbeaa3c05 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Feb 2025 20:47:25 +0300 Subject: [PATCH 447/474] build: upgrade ref --- Endpoint/Endpoint.csproj | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 1b03869..0d86702 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -28,10 +28,9 @@ + - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -40,6 +39,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -55,10 +55,17 @@ - + + + + + + - + + + -- 2.43.0 From a67b72b7fbbe669fa64eb073c01468ae5fe880f2 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Feb 2025 21:18:56 +0300 Subject: [PATCH 448/474] refactor: rename cancellation to cancellationToken --- Security/Services/AuthService.cs | 64 +++++++++++++++---------------- Security/Services/OAuthService.cs | 48 +++++++++++------------ 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs index 38ee706..5bb7262 100644 --- a/Security/Services/AuthService.cs +++ b/Security/Services/AuthService.cs @@ -29,14 +29,14 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I private static string GetFirstAuthCacheKey(string fingerprint) => $"{fingerprint}_auth_token_first"; private static string GetAttemptFailedCountKey(string fingerprint) => $"{fingerprint}_login_failed"; - private Task StoreAuthTokenInCache(AuthToken data, CancellationToken cancellation) => + private Task StoreAuthTokenInCache(AuthToken data, CancellationToken cancellationToken) => cache.SetAsync( GetAuthCacheKey(data.Fingerprint), JsonSerializer.SerializeToUtf8Bytes(data), slidingExpiration: Lifetime, - cancellationToken: cancellation); + cancellationToken: cancellationToken); - private Task StoreFirstAuthTokenInCache(User data, RequestContextInfo requestContext, CancellationToken cancellation) => + private Task StoreFirstAuthTokenInCache(User data, RequestContextInfo requestContext, CancellationToken cancellationToken) => cache.SetAsync( GetFirstAuthCacheKey(requestContext.Fingerprint), JsonSerializer.SerializeToUtf8Bytes(new FirstAuthToken(requestContext) @@ -46,14 +46,14 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I TwoFactorAuthenticator = data.TwoFactorAuthenticator }), slidingExpiration: LifetimeFirstAuth, - cancellationToken: cancellation); + cancellationToken: cancellationToken); private Task RevokeAccessToken(string token) => revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); - private async Task RecordFailedLoginAttempt(string fingerprint, string userId, CancellationToken cancellation) + private async Task RecordFailedLoginAttempt(string fingerprint, string userId, CancellationToken cancellationToken) { - var failedLoginAttemptsCount = await cache.GetAsync(GetAttemptFailedCountKey(fingerprint), cancellation) ?? 1; + var failedLoginAttemptsCount = await cache.GetAsync(GetAttemptFailedCountKey(fingerprint), cancellationToken) ?? 1; var failedLoginCacheExpiration = TimeSpan.FromHours(1); if (failedLoginAttemptsCount > 5) @@ -74,30 +74,30 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I failedLoginAttemptsCount); await cache.SetAsync(GetAttemptFailedCountKey(fingerprint), failedLoginAttemptsCount + 1, - slidingExpiration: failedLoginCacheExpiration, cancellationToken: cancellation); + slidingExpiration: failedLoginCacheExpiration, cancellationToken: cancellationToken); } - private Task ResetFailedLoginAttempts(string fingerprint, CancellationToken cancellation) => - cache.RemoveAsync(GetAttemptFailedCountKey(fingerprint), cancellation); + private Task ResetFailedLoginAttempts(string fingerprint, CancellationToken cancellationToken) => + cache.RemoveAsync(GetAttemptFailedCountKey(fingerprint), cancellationToken); private async Task VerifyUserOrThrowError(RequestContextInfo requestContext, User user, string password, string username, - CancellationToken cancellation = default) + CancellationToken cancellationToken = default) { if ((user.Email.Equals(username, StringComparison.OrdinalIgnoreCase) || user.Username.Equals(username, StringComparison.OrdinalIgnoreCase)) && passwordService.VerifyPassword(password, user.Salt, user.PasswordHash)) { - await ResetFailedLoginAttempts(requestContext.Fingerprint, cancellation); + await ResetFailedLoginAttempts(requestContext.Fingerprint, cancellationToken); return; } - await RecordFailedLoginAttempt(requestContext.Fingerprint, user.Id, cancellation); + await RecordFailedLoginAttempt(requestContext.Fingerprint, user.Id, cancellationToken); throw new SecurityException("Authentication failed. Please check your credentials."); } private async Task GenerateAuthTokensAsync(CookieOptions cookieOptions, HttpContext context, - RequestContextInfo requestContext, string userId, CancellationToken cancellation = default) + RequestContextInfo requestContext, string userId, CancellationToken cancellationToken = default) { var refreshToken = GenerateRefreshToken(); var (token, expireIn) = GenerateAccessToken(userId); @@ -110,7 +110,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I AccessToken = token }; - await StoreAuthTokenInCache(authToken, cancellation); + await StoreAuthTokenInCache(authToken, cancellationToken); cookieOptions.SetCookie(context, CookieNames.AccessToken, authToken.AccessToken, expireIn); cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); @@ -121,11 +121,11 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I } public async Task LoginAsync(CookieOptions cookieOptions, HttpContext context, TwoFactorAuthenticator authenticator, string code, - CancellationToken cancellation = default) + CancellationToken cancellationToken = default) { var requestContext = new RequestContextInfo(context, cookieOptions); - var firstTokenAuth = await cache.GetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellation); + var firstTokenAuth = await cache.GetAsync(GetFirstAuthCacheKey(requestContext.Fingerprint), cancellationToken: cancellationToken); if (firstTokenAuth == null || authenticator != firstTokenAuth.TwoFactorAuthenticator) throw new SecurityException("Session expired. Please log in again."); @@ -147,35 +147,35 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I if (!totp.VerifyToken(code)) { - await RecordFailedLoginAttempt(requestContext.Fingerprint, firstTokenAuth.UserId, cancellation); + await RecordFailedLoginAttempt(requestContext.Fingerprint, firstTokenAuth.UserId, cancellationToken); throw new SecurityException("Invalid verification code. Please try again."); } - await ResetFailedLoginAttempts(requestContext.Fingerprint, cancellation); + await ResetFailedLoginAttempts(requestContext.Fingerprint, cancellationToken); } break; default: throw new InvalidOperationException("Unsupported authorization method."); } - await GenerateAuthTokensAsync(cookieOptions, context, requestContext, firstTokenAuth.UserId, cancellation); + await GenerateAuthTokensAsync(cookieOptions, context, requestContext, firstTokenAuth.UserId, cancellationToken); return true; } private async Task LoginAsync(CookieOptions cookieOptions, HttpContext context, User user, - CancellationToken cancellation = default) + CancellationToken cancellationToken = default) { var requestContext = new RequestContextInfo(context, cookieOptions); if (user.TwoFactorAuthenticator == TwoFactorAuthenticator.None) { - await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id, cancellation); + await GenerateAuthTokensAsync(cookieOptions, context, requestContext, user.Id, cancellationToken); return TwoFactorAuthenticator.None; } - await StoreFirstAuthTokenInCache(user, requestContext, cancellation); + await StoreFirstAuthTokenInCache(user, requestContext, cancellationToken); return user.TwoFactorAuthenticator; } @@ -201,20 +201,20 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I User user, string password, string username, - CancellationToken cancellation = default) + CancellationToken cancellationToken = default) { var requestContext = new RequestContextInfo(context, cookieOptions); username = username.Trim(); - await VerifyUserOrThrowError(requestContext, user, password, username, cancellation); + await VerifyUserOrThrowError(requestContext, user, password, username, cancellationToken); - return await LoginAsync(cookieOptions, context, user, cancellation); + return await LoginAsync(cookieOptions, context, user, cancellationToken); } - public async Task RefreshTokenAsync(CookieOptions cookieOptions, HttpContext context, CancellationToken cancellation = default) + public async Task RefreshTokenAsync(CookieOptions cookieOptions, HttpContext context, CancellationToken cancellationToken = default) { const string defaultMessageError = "The session time has expired"; var requestContext = new RequestContextInfo(context, cookieOptions); - var authToken = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation) ?? + var authToken = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellationToken) ?? throw new SecurityException(defaultMessageError); if (authToken.RefreshToken != requestContext.RefreshToken || @@ -222,7 +222,7 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.Ip != requestContext.Ip) { await RevokeAccessToken(authToken.AccessToken); - await cache.RemoveAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation); + await cache.RemoveAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellationToken); cookieOptions.DropCookie(context, CookieNames.AccessToken); cookieOptions.DropCookie(context, CookieNames.RefreshToken); @@ -274,24 +274,24 @@ public class AuthService(ICacheService cache, IAccessToken accessTokenService, I authToken.AccessToken = token; authToken.RefreshToken = newRefreshToken; - await StoreAuthTokenInCache(authToken, cancellation); + await StoreAuthTokenInCache(authToken, cancellationToken); cookieOptions.SetCookie(context, CookieNames.AccessToken, authToken.AccessToken, expireIn); cookieOptions.SetCookie(context, CookieNames.RefreshToken, authToken.RefreshToken, DateTime.UtcNow.Add(Lifetime)); } - public async Task LogoutAsync(CookieOptions cookieOptions, HttpContext context, CancellationToken cancellation = default) + public async Task LogoutAsync(CookieOptions cookieOptions, HttpContext context, CancellationToken cancellationToken = default) { var requestContext = new RequestContextInfo(context, cookieOptions); cookieOptions.DropCookie(context, CookieNames.AccessToken); cookieOptions.DropCookie(context, CookieNames.RefreshToken); - var authTokenStruct = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellation); + var authTokenStruct = await cache.GetAsync(GetAuthCacheKey(requestContext.Fingerprint), cancellationToken); if (authTokenStruct == null) return; await RevokeAccessToken(authTokenStruct.AccessToken); - await cache.RemoveAsync(requestContext.Fingerprint, cancellation); + await cache.RemoveAsync(requestContext.Fingerprint, cancellationToken); } } \ No newline at end of file diff --git a/Security/Services/OAuthService.cs b/Security/Services/OAuthService.cs index 6a2389b..ea5a984 100644 --- a/Security/Services/OAuthService.cs +++ b/Security/Services/OAuthService.cs @@ -58,7 +58,7 @@ public class OAuthService(ILogger logger, Dictionary ExchangeCodeForTokensAsync(string requestUri, string redirectUrl, string code, - string clientId, string secret, CancellationToken cancellation) + string clientId, string secret, CancellationToken cancellationToken) { var tokenRequest = new HttpRequestMessage(HttpMethod.Post, requestUri) { @@ -75,8 +75,8 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary GetUserProfileAsync(string requestUri, string authHeader, string accessToken, OAuthProvider provider, - CancellationToken cancellation) + CancellationToken cancellationToken) { var request = new HttpRequestMessage(HttpMethod.Get, requestUri); @@ -97,8 +97,8 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary + private Task StoreOAuthUserInCache(string key, OAuthUserExtension data, CancellationToken cancellationToken) => cache.SetAsync( key, JsonSerializer.SerializeToUtf8Bytes(data), absoluteExpirationRelativeToNow: TimeSpan.FromMinutes(15), - cancellationToken: cancellation); + cancellationToken: cancellationToken); public Uri GetProviderRedirect(CookieOptions cookieOptions, HttpContext context, string redirectUri, @@ -209,7 +209,7 @@ public class OAuthService(ILogger logger, Dictionary (x.Key, new Uri(redirectUri.TrimEnd('/') + "/?provider=" + (int)x.Key)))]; public async Task LoginOAuth(CookieOptions cookieOptions, HttpContext context, - string redirectUrl, string code, string state, CancellationToken cancellation = default) + string redirectUrl, string code, string state, CancellationToken cancellationToken = default) { var result = new LoginOAuth() { @@ -226,7 +226,7 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary logger, Dictionary logger, Dictionary logger, Dictionary logger, Dictionary logger, Dictionary - GetOAuthUser(CookieOptions cookieOptions, HttpContext context, string token, CancellationToken cancellation = default) + GetOAuthUser(CookieOptions cookieOptions, HttpContext context, string token, CancellationToken cancellationToken = default) { var requestInfo = new RequestContextInfo(context, cookieOptions); - var result = await cache.GetAsync(token, cancellation); + var result = await cache.GetAsync(token, cancellationToken); var tokenFailedKey = $"{requestInfo.Fingerprint}_oauth_token_failed"; if (result == null) { var failedTokenAttemptsCount = await cache.GetAsync( tokenFailedKey, - cancellation) ?? 1; + cancellationToken) ?? 1; var failedTokenCacheExpiration = TimeSpan.FromHours(1); @@ -364,7 +364,7 @@ public class OAuthService(ILogger logger, Dictionary logger, Dictionary Date: Sat, 1 Feb 2025 21:19:56 +0300 Subject: [PATCH 449/474] fix: set long, because the value may be greater than int --- .../Configuration/Core/BackgroundTasks/ScheduleSyncService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs index 726ba8a..8d49770 100644 --- a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -73,7 +73,7 @@ public class ScheduleSyncService : IHostedService, IDisposable delay = 1; _cancellationTokenSource = new CancellationTokenSource(); - _timer = new Timer(ExecuteTask, null, (int)delay, Timeout.Infinite); + _timer = new Timer(ExecuteTask, null, (long)delay, Timeout.Infinite); } private async void ExecuteTask(object? state) -- 2.43.0 From dda0a2930084cbaa4f425b46f69e8bae8356108c Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sat, 1 Feb 2025 21:23:51 +0300 Subject: [PATCH 450/474] refactor: subscribe to onChange instead of waiting for the event to be received from the manager --- .../Common/Services/ScheduleSyncManager.cs | 5 ---- .../BackgroundTasks/ScheduleSyncService.cs | 23 +++++++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Endpoint/Common/Services/ScheduleSyncManager.cs b/Endpoint/Common/Services/ScheduleSyncManager.cs index 7bff018..e380d54 100644 --- a/Endpoint/Common/Services/ScheduleSyncManager.cs +++ b/Endpoint/Common/Services/ScheduleSyncManager.cs @@ -4,12 +4,7 @@ namespace Mirea.Api.Endpoint.Common.Services; public static class ScheduleSyncManager { - public static event Action? OnUpdateIntervalRequested; public static event Action? OnForceSyncRequested; - - public static void RequestIntervalUpdate() => - OnUpdateIntervalRequested?.Invoke(); - public static void RequestForceSync() => OnForceSyncRequested?.Invoke(); } \ No newline at end of file diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs index 8d49770..9c57870 100644 --- a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -15,23 +15,32 @@ namespace Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks; public class ScheduleSyncService : IHostedService, IDisposable { private Timer? _timer; - private readonly IOptionsMonitor _generalConfigMonitor; + private string _cronUpdate; private readonly ILogger _logger; private CancellationTokenSource _cancellationTokenSource = new(); private readonly IServiceProvider _serviceProvider; + private readonly IDisposable? _onChangeUpdateCron; public ScheduleSyncService(IOptionsMonitor generalConfigMonitor, ILogger logger, IServiceProvider serviceProvider) { - _generalConfigMonitor = generalConfigMonitor; _logger = logger; _serviceProvider = serviceProvider; + _cronUpdate = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSchedule; ScheduleSyncManager.OnForceSyncRequested += OnForceSyncRequested; - ScheduleSyncManager.OnUpdateIntervalRequested += OnUpdateIntervalRequested; + _onChangeUpdateCron = generalConfigMonitor.OnChange((config) => + { + if (config.ScheduleSettings?.CronUpdateSchedule == null || _cronUpdate == config.ScheduleSettings.CronUpdateSchedule) + return; + + _cronUpdate = config.ScheduleSettings.CronUpdateSchedule; + OnUpdateIntervalRequested(); + }); } private void OnForceSyncRequested() { + _logger.LogInformation("It was requested to synchronize the data immediately."); StopAsync(CancellationToken.None).ContinueWith(_ => { _cancellationTokenSource = new CancellationTokenSource(); @@ -41,6 +50,7 @@ public class ScheduleSyncService : IHostedService, IDisposable private void OnUpdateIntervalRequested() { + _logger.LogInformation("It was requested to update the time interval immediately."); StopAsync(CancellationToken.None).ContinueWith(_ => { StartAsync(CancellationToken.None); @@ -49,14 +59,13 @@ public class ScheduleSyncService : IHostedService, IDisposable private void ScheduleNextRun() { - var cronExpression = _generalConfigMonitor.CurrentValue.ScheduleSettings?.CronUpdateSchedule; - if (string.IsNullOrEmpty(cronExpression)) + if (string.IsNullOrEmpty(_cronUpdate)) { _logger.LogWarning("Cron expression is not set. The scheduled task will not run."); return; } - var nextRunTime = CronExpression.Parse(cronExpression).GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local); + var nextRunTime = CronExpression.Parse(_cronUpdate).GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local); if (!nextRunTime.HasValue) { @@ -112,7 +121,7 @@ public class ScheduleSyncService : IHostedService, IDisposable StopAsync(CancellationToken.None).GetAwaiter().GetResult(); _timer?.Dispose(); ScheduleSyncManager.OnForceSyncRequested -= OnForceSyncRequested; - ScheduleSyncManager.OnUpdateIntervalRequested -= OnUpdateIntervalRequested; + _onChangeUpdateCron?.Dispose(); _cancellationTokenSource.Dispose(); GC.SuppressFinalize(this); -- 2.43.0 From ad8f356fc1678a8ad84cfc9d02af436f6ba288c0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 01:57:08 +0300 Subject: [PATCH 451/474] fix: get non negative number --- .../Configuration/Core/BackgroundTasks/ScheduleSyncService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs index 9c57870..a2896f8 100644 --- a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -82,7 +82,7 @@ public class ScheduleSyncService : IHostedService, IDisposable delay = 1; _cancellationTokenSource = new CancellationTokenSource(); - _timer = new Timer(ExecuteTask, null, (long)delay, Timeout.Infinite); + _timer = new Timer(ExecuteTask, null, Math.Abs((long)delay), Timeout.Infinite); } private async void ExecuteTask(object? state) -- 2.43.0 From c9bc6a3565a18d7da1ddf2a63a4f5887735c74bd Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 03:28:24 +0300 Subject: [PATCH 452/474] refactor: remove "swagger" in class name --- Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs | 6 +++--- .../{SwaggerDefaultValues.cs => DefaultValues.cs} | 2 +- .../{SwaggerExampleFilter.cs => ExampleFilter.cs} | 2 +- .../{SwaggerTagSchemeFilter.cs => TagSchemeFilter.cs} | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename Endpoint/Configuration/SwaggerOptions/{SwaggerDefaultValues.cs => DefaultValues.cs} (97%) rename Endpoint/Configuration/SwaggerOptions/{SwaggerExampleFilter.cs => ExampleFilter.cs} (90%) rename Endpoint/Configuration/SwaggerOptions/{SwaggerTagSchemeFilter.cs => TagSchemeFilter.cs} (95%) diff --git a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs index 9947041..bdabff9 100644 --- a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs @@ -17,9 +17,9 @@ public static class SwaggerConfiguration { services.AddSwaggerGen(options => { - options.OperationFilter(); - options.SchemaFilter(); - options.OperationFilter(); + options.OperationFilter(); + options.SchemaFilter(); + options.OperationFilter(); options.OperationFilter(); var basePath = AppDomain.CurrentDomain.BaseDirectory; diff --git a/Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs b/Endpoint/Configuration/SwaggerOptions/DefaultValues.cs similarity index 97% rename from Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs rename to Endpoint/Configuration/SwaggerOptions/DefaultValues.cs index a6b9dd4..7ad61a9 100644 --- a/Endpoint/Configuration/SwaggerOptions/SwaggerDefaultValues.cs +++ b/Endpoint/Configuration/SwaggerOptions/DefaultValues.cs @@ -8,7 +8,7 @@ using System.Text.Json; namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; -public class SwaggerDefaultValues : IOperationFilter +public class DefaultValues : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { diff --git a/Endpoint/Configuration/SwaggerOptions/SwaggerExampleFilter.cs b/Endpoint/Configuration/SwaggerOptions/ExampleFilter.cs similarity index 90% rename from Endpoint/Configuration/SwaggerOptions/SwaggerExampleFilter.cs rename to Endpoint/Configuration/SwaggerOptions/ExampleFilter.cs index 0309293..060aa52 100644 --- a/Endpoint/Configuration/SwaggerOptions/SwaggerExampleFilter.cs +++ b/Endpoint/Configuration/SwaggerOptions/ExampleFilter.cs @@ -5,7 +5,7 @@ using System.Reflection; namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; -public class SwaggerExampleFilter : ISchemaFilter +public class ExampleFilter : ISchemaFilter { public void Apply(OpenApiSchema schema, SchemaFilterContext context) { diff --git a/Endpoint/Configuration/SwaggerOptions/SwaggerTagSchemeFilter.cs b/Endpoint/Configuration/SwaggerOptions/TagSchemeFilter.cs similarity index 95% rename from Endpoint/Configuration/SwaggerOptions/SwaggerTagSchemeFilter.cs rename to Endpoint/Configuration/SwaggerOptions/TagSchemeFilter.cs index 3e3291b..4c2be6f 100644 --- a/Endpoint/Configuration/SwaggerOptions/SwaggerTagSchemeFilter.cs +++ b/Endpoint/Configuration/SwaggerOptions/TagSchemeFilter.cs @@ -7,7 +7,7 @@ using System.Reflection; namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; -public class SwaggerTagSchemeFilter : IOperationFilter +public class TagSchemeFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { -- 2.43.0 From 16afc0bc6915ac4a0662ab00ca9b18375cc621e4 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 03:29:19 +0300 Subject: [PATCH 453/474] feat: show enum name instead value --- .../Core/Startup/SwaggerConfiguration.cs | 1 + .../SwaggerOptions/EnumSchemaFilter.cs | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 Endpoint/Configuration/SwaggerOptions/EnumSchemaFilter.cs diff --git a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs index bdabff9..ae6f3f9 100644 --- a/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/SwaggerConfiguration.cs @@ -21,6 +21,7 @@ public static class SwaggerConfiguration options.SchemaFilter(); options.OperationFilter(); options.OperationFilter(); + options.SchemaFilter(); var basePath = AppDomain.CurrentDomain.BaseDirectory; options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme diff --git a/Endpoint/Configuration/SwaggerOptions/EnumSchemaFilter.cs b/Endpoint/Configuration/SwaggerOptions/EnumSchemaFilter.cs new file mode 100644 index 0000000..f64fc87 --- /dev/null +++ b/Endpoint/Configuration/SwaggerOptions/EnumSchemaFilter.cs @@ -0,0 +1,28 @@ +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Linq; + +namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; + +public class EnumSchemaFilter : ISchemaFilter +{ + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + if (!context.Type.IsEnum) + return; + + schema.Enum.Clear(); + + var enumValues = Enum.GetNames(context.Type) + .Select(name => new OpenApiString(name)) + .ToList(); + + foreach (var value in enumValues) + schema.Enum.Add(value); + + schema.Type = "string"; + schema.Format = null; + } +} \ No newline at end of file -- 2.43.0 From ce6b0f2673f1a309ca11d7efce11b57d92cf8762 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 03:30:52 +0300 Subject: [PATCH 454/474] feat: add cron skipping date --- .../Common/Services/CronUpdateSkipService.cs | 69 +++++++++++++++++++ .../BackgroundTasks/ScheduleSyncService.cs | 38 +++++++--- .../Model/GeneralSettings/ScheduleSettings.cs | 24 +++++++ 3 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 Endpoint/Common/Services/CronUpdateSkipService.cs diff --git a/Endpoint/Common/Services/CronUpdateSkipService.cs b/Endpoint/Common/Services/CronUpdateSkipService.cs new file mode 100644 index 0000000..4f44fea --- /dev/null +++ b/Endpoint/Common/Services/CronUpdateSkipService.cs @@ -0,0 +1,69 @@ +using Cronos; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mirea.Api.Endpoint.Common.Services; + +public static class CronUpdateSkipService +{ + public static ScheduleSettings.CronUpdateSkip Get(this Dto.Common.CronUpdateSkip date) + { + if (date.Date.HasValue) + return new ScheduleSettings.CronUpdateSkip(date.Date.Value); + if (date is { Start: not null, End: not null }) + return new ScheduleSettings.CronUpdateSkip(date.Start.Value, date.End.Value); + + throw new ArgumentException("It is impossible to create a structure because it has incorrect values."); + } + + public static List Filter(this List data, DateOnly? currentDate = null) + { + currentDate ??= DateOnly.FromDateTime(DateTime.Now); + return data.OrderBy(x => x.End ?? x.Date) + .Where(x => x.Date == currentDate || (x.Start <= currentDate && x.End >= currentDate)) + .ToList(); + } + + public static List Filter(this List data, DateTime? currentDate = null) => + data.Filter(DateOnly.FromDateTime(currentDate ?? DateTime.Now)); + + public static List GetNextTask(this List data, + CronExpression expression, int depth = 1, DateOnly? currentDate = null) + { + if (depth <= 0) + return []; + + currentDate ??= DateOnly.FromDateTime(DateTime.UtcNow); + DateTimeOffset nextRunTime = currentDate.Value.ToDateTime(new TimeOnly(0, 0, 0)); + + List result = []; + + do + { + var lastSkip = data.Filter(nextRunTime.DateTime).LastOrDefault(); + + if (lastSkip is { Start: not null, End: not null }) + nextRunTime = new DateTimeOffset(lastSkip.End.Value.AddDays(1), new TimeOnly(0, 0, 0), TimeSpan.Zero); + else if (lastSkip.Date.HasValue) + nextRunTime = new DateTimeOffset(lastSkip.Date.Value.AddDays(1), new TimeOnly(0, 0, 0), TimeSpan.Zero); + + var next = expression.GetNextOccurrence(nextRunTime, TimeZoneInfo.Local); + + if (!next.HasValue) + return result; + + nextRunTime = next.Value; + + if (!data.Filter(nextRunTime.DateTime).Any()) + continue; + + result.Add(nextRunTime); + nextRunTime = nextRunTime.AddMinutes(1); + + } while (result.Count < depth); + + return result; + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs index a2896f8..5ed3575 100644 --- a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -5,8 +5,11 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; using Mirea.Api.Endpoint.Sync; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -16,6 +19,7 @@ public class ScheduleSyncService : IHostedService, IDisposable { private Timer? _timer; private string _cronUpdate; + private List _cronUpdateSkip; private readonly ILogger _logger; private CancellationTokenSource _cancellationTokenSource = new(); private readonly IServiceProvider _serviceProvider; @@ -26,15 +30,28 @@ public class ScheduleSyncService : IHostedService, IDisposable _logger = logger; _serviceProvider = serviceProvider; _cronUpdate = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSchedule; + _cronUpdateSkip = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSkipDateList; ScheduleSyncManager.OnForceSyncRequested += OnForceSyncRequested; _onChangeUpdateCron = generalConfigMonitor.OnChange((config) => { - if (config.ScheduleSettings?.CronUpdateSchedule == null || _cronUpdate == config.ScheduleSettings.CronUpdateSchedule) - return; + var updated = false; + if (config.ScheduleSettings?.CronUpdateSchedule != null && _cronUpdate != config.ScheduleSettings.CronUpdateSchedule) + { + _cronUpdate = config.ScheduleSettings.CronUpdateSchedule; + updated = true; + } - _cronUpdate = config.ScheduleSettings.CronUpdateSchedule; - OnUpdateIntervalRequested(); + if (config.ScheduleSettings?.CronUpdateSkipDateList != null && !config.ScheduleSettings.CronUpdateSkipDateList.SequenceEqual(_cronUpdateSkip)) + { + _cronUpdateSkip = config.ScheduleSettings.CronUpdateSkipDateList + .OrderBy(x => x.End ?? x.Date) + .ToList(); + updated = true; + } + + if (updated) + OnUpdateIntervalRequested(); }); } @@ -65,17 +82,20 @@ public class ScheduleSyncService : IHostedService, IDisposable return; } - var nextRunTime = CronExpression.Parse(_cronUpdate).GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local); + var expression = CronExpression.Parse(_cronUpdate); - if (!nextRunTime.HasValue) + var nextRunTime = _cronUpdateSkip.GetNextTask(expression).FirstOrDefault(); + + if (nextRunTime == default) { - _logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}", TimeZoneInfo.Local.DisplayName); + _logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}", + TimeZoneInfo.Local.DisplayName); return; } - _logger.LogInformation("Next task run in {Time}", nextRunTime.Value.ToString("G")); + _logger.LogInformation("Next task run in {Time}", nextRunTime.ToString("G")); - var delay = (nextRunTime.Value - DateTimeOffset.Now).TotalMilliseconds; + var delay = (nextRunTime - DateTimeOffset.Now).TotalMilliseconds; // The chance is small, but it's better to check if (delay <= 0) diff --git a/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs index ac1e6d4..bb66c7b 100644 --- a/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs @@ -31,9 +31,33 @@ public class ScheduleSettings : IIsConfigured public PairPeriodTime(Dto.Common.PairPeriodTime time) : this(time.Start, time.End) { } } + public record struct CronUpdateSkip + { + public DateOnly? Start { get; set; } + public DateOnly? End { get; set; } + public DateOnly? Date { get; set; } + + public CronUpdateSkip(DateOnly d1, DateOnly d2) + { + if (d1 > d2) + { + Start = d2; + End = d1; + } + else + { + Start = d1; + End = d2; + } + } + + public CronUpdateSkip(DateOnly d1) => Date = d1; + } + public required string CronUpdateSchedule { get; set; } public DateOnly StartTerm { get; set; } public required IDictionary PairPeriod { get; set; } + public List CronUpdateSkipDateList { get; set; } = []; public bool IsConfigured() { -- 2.43.0 From 8d1b709b43ae07d25d7f30bbb2d616e31070a310 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 03:39:30 +0300 Subject: [PATCH 455/474] feat: add start term update and cron schedule update --- ApiDto/Common/CronUpdateSkip.cs | 24 ++++ .../CronUpdateScheduleResponse.cs | 23 +++ .../MapperDto/CronUpdateSkipConverter.cs | 19 +++ .../V1/Configuration/ScheduleController.cs | 132 ++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 ApiDto/Common/CronUpdateSkip.cs create mode 100644 ApiDto/Responses/Configuration/CronUpdateScheduleResponse.cs create mode 100644 Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs create mode 100644 Endpoint/Controllers/V1/Configuration/ScheduleController.cs diff --git a/ApiDto/Common/CronUpdateSkip.cs b/ApiDto/Common/CronUpdateSkip.cs new file mode 100644 index 0000000..822dfc6 --- /dev/null +++ b/ApiDto/Common/CronUpdateSkip.cs @@ -0,0 +1,24 @@ +using System; + +namespace Mirea.Api.Dto.Common; + +/// +/// Represents a date or date range to skip during cron update scheduling. +/// +public class CronUpdateSkip +{ + /// + /// Gets or sets the start date of the skip range. + /// + public DateOnly? Start { get; set; } + + /// + /// Gets or sets the end date of the skip range. + /// + public DateOnly? End { get; set; } + + /// + /// Gets or sets a specific date to skip. + /// + public DateOnly? Date { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/Configuration/CronUpdateScheduleResponse.cs b/ApiDto/Responses/Configuration/CronUpdateScheduleResponse.cs new file mode 100644 index 0000000..46bab01 --- /dev/null +++ b/ApiDto/Responses/Configuration/CronUpdateScheduleResponse.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses.Configuration; + +/// +/// Represents the response containing the cron update schedule and the next scheduled task dates. +/// +public class CronUpdateScheduleResponse +{ + /// + /// Gets or sets the cron expression representing the update schedule. + /// + [Required] + public required string Cron { get; set; } + + /// + /// Gets or sets the list of next scheduled task dates based on the cron expression. + /// + [Required] + public required List NextStart { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs b/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs new file mode 100644 index 0000000..baa6a0d --- /dev/null +++ b/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs @@ -0,0 +1,19 @@ +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; +using System.Collections.Generic; +using System.Linq; + +namespace Mirea.Api.Endpoint.Common.MapperDto; + +public static class CronUpdateSkipConverter +{ + public static List ConvertToDto(this IEnumerable pairPeriod) => + pairPeriod.Select(x => new Dto.Common.CronUpdateSkip() + { + Start = x.Start, + End = x.End, + Date = x.Date + }).ToList(); + public static List ConvertFromDto(this IEnumerable pairPeriod) => + pairPeriod.Select(x => x.Get()).ToList(); +} diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs new file mode 100644 index 0000000..6194520 --- /dev/null +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -0,0 +1,132 @@ +using Asp.Versioning; +using Cronos; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Mirea.Api.DataAccess.Persistence; +using Mirea.Api.Dto.Common; +using Mirea.Api.Dto.Responses.Configuration; +using Mirea.Api.Endpoint.Common.Exceptions; +using Mirea.Api.Endpoint.Common.MapperDto; +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace Mirea.Api.Endpoint.Controllers.V1.Configuration; + +[ApiVersion("1.0")] +public class ScheduleController(ILogger logger, IOptionsSnapshot config, UberDbContext dbContext) : ConfigurationBaseController +{ + /// + /// Retrieves the cron update schedule and calculates the next scheduled tasks based on the provided depth. + /// + /// The depth of the next tasks to retrieve. + /// Cron expression and the list of next scheduled task dates. + [HttpGet("CronUpdateSchedule")] + public ActionResult CronUpdateSchedule([FromQuery][Range(0, 5)] int depth = 2) + { + var cronExpression = CronExpression.Parse(config.Value.ScheduleSettings!.CronUpdateSchedule); + var nextTasks = config.Value.ScheduleSettings!.CronUpdateSkipDateList.GetNextTask(cronExpression, depth); + + return new CronUpdateScheduleResponse() + { + Cron = config.Value.ScheduleSettings!.CronUpdateSchedule, + NextStart = nextTasks.Select(x => DateTime.SpecifyKind(x.DateTime, DateTimeKind.Local)).ToList() + }; + } + + /// + /// Updates the cron update schedule with the provided cron expression. + /// + /// The cron expression to set as the new schedule. + /// Cron expression and the list of next scheduled task dates. + /// Thrown if the provided cron expression is invalid. + [HttpPost("CronUpdateSchedule")] + public ActionResult CronUpdateSchedule([FromBody] string cron) + { + cron = cron.Trim(); + if (!CronExpression.TryParse(cron, CronFormat.Standard, out _)) + throw new ControllerArgumentException("Incorrect cron value."); + + if (config.Value.ScheduleSettings!.CronUpdateSchedule == cron) + return Ok(CronUpdateSchedule()); + + config.Value.ScheduleSettings!.CronUpdateSchedule = cron; + config.Value.SaveSetting(); + + return Ok(CronUpdateSchedule()); + } + + /// + /// Retrieves the start term date from the configuration. + /// + /// Start term date. + [HttpGet("StartTerm")] + public ActionResult StartTerm() => + config.Value.ScheduleSettings!.StartTerm; + + /// + /// Updates the start term date in the configuration. + /// + /// The new start term date to set. + /// If true, forces an update by deleting all existing lessons. + /// Success or failure. + /// Thrown if the start term date is more than 6 months in the past or future. + [HttpPost("StartTerm")] + public ActionResult StartTerm([FromBody] DateOnly startTerm, [FromQuery] bool force = false) + { + var differentByTime = DateTime.Now - startTerm.ToDateTime(new TimeOnly(0, 0, 0)); + if (differentByTime > TimeSpan.FromDays(190) || differentByTime.Multiply(-1) > TimeSpan.FromDays(190)) + throw new ControllerArgumentException("The semester can't start more than 6 months from now, and it can't have started more than 6 months ago either."); + + config.Value.ScheduleSettings!.StartTerm = startTerm; + config.Value.SaveSetting(); + + if (!force) + return Ok(); + + logger.LogWarning("A force update is being performed at the beginning of the semester (all classes will be deleted)."); + + dbContext.Lessons.RemoveRange(dbContext.Lessons.ToList()); + dbContext.SaveChanges(); + + return Ok(); + } + + /// + /// Retrieves the list of cron update skip dates filtered by the current date. + /// + /// Cron update skip dates. + [HttpGet("CronUpdateSkip")] + public ActionResult> CronUpdateSkip() => + config.Value.ScheduleSettings!.CronUpdateSkipDateList.Filter(DateTime.Now).ConvertToDto(); + + /// + /// Updates the list of cron update skip dates in the configuration. + /// + /// The list of cron update skip dates to set. + /// Success or failure. + /// Thrown if the provided list of cron update skip dates is invalid. + [HttpPost("CronUpdateSkip")] + public ActionResult CronUpdateSkip([FromBody] List cronUpdateDate) + { + List result; + try + { + result = cronUpdateDate.ConvertFromDto(); + } + catch (ArgumentException ex) + { + throw new ControllerArgumentException(ex.Message); + } + + config.Value.ScheduleSettings!.CronUpdateSkipDateList = result.Filter(DateTime.Now); + config.Value.SaveSetting(); + + return Ok(); + } +} \ No newline at end of file -- 2.43.0 From 1687e9d89b97bfe2b2cd9bafb33bd8061f2fce8f Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 04:49:25 +0300 Subject: [PATCH 456/474] fix: continue if in filter exist value --- Endpoint/Common/Services/CronUpdateSkipService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Common/Services/CronUpdateSkipService.cs b/Endpoint/Common/Services/CronUpdateSkipService.cs index 4f44fea..50ccab3 100644 --- a/Endpoint/Common/Services/CronUpdateSkipService.cs +++ b/Endpoint/Common/Services/CronUpdateSkipService.cs @@ -56,7 +56,7 @@ public static class CronUpdateSkipService nextRunTime = next.Value; - if (!data.Filter(nextRunTime.DateTime).Any()) + if (data.Filter(nextRunTime.DateTime).Any()) continue; result.Add(nextRunTime); -- 2.43.0 From 7c7707b1e28c8422c0a7e728b5ea1f2b3ba57766 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 04:50:04 +0300 Subject: [PATCH 457/474] fix: if delay more than int set max of int --- .../Configuration/Core/BackgroundTasks/ScheduleSyncService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs index 5ed3575..92dcadb 100644 --- a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -102,7 +102,7 @@ public class ScheduleSyncService : IHostedService, IDisposable delay = 1; _cancellationTokenSource = new CancellationTokenSource(); - _timer = new Timer(ExecuteTask, null, Math.Abs((long)delay), Timeout.Infinite); + _timer = new Timer(ExecuteTask, null, delay > int.MaxValue ? int.MaxValue : (int)delay, Timeout.Infinite); } private async void ExecuteTask(object? state) -- 2.43.0 From c725cfed32fced8fac374698d17af27d50e751ba Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 04:50:35 +0300 Subject: [PATCH 458/474] refactor: increase max value --- Endpoint/Controllers/V1/Configuration/ScheduleController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs index 6194520..6b548d3 100644 --- a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -27,7 +27,7 @@ public class ScheduleController(ILogger logger, IOptionsSnap /// The depth of the next tasks to retrieve. /// Cron expression and the list of next scheduled task dates. [HttpGet("CronUpdateSchedule")] - public ActionResult CronUpdateSchedule([FromQuery][Range(0, 5)] int depth = 2) + public ActionResult CronUpdateSchedule([FromQuery][Range(0, 10)] int depth = 5) { var cronExpression = CronExpression.Parse(config.Value.ScheduleSettings!.CronUpdateSchedule); var nextTasks = config.Value.ScheduleSettings!.CronUpdateSkipDateList.GetNextTask(cronExpression, depth); -- 2.43.0 From 93912caf0122042b3ca6f37f8da70af8f65b0001 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 04:50:54 +0300 Subject: [PATCH 459/474] fix: return correct value --- Endpoint/Controllers/V1/Configuration/ScheduleController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs index 6b548d3..30397e1 100644 --- a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -53,12 +53,12 @@ public class ScheduleController(ILogger logger, IOptionsSnap throw new ControllerArgumentException("Incorrect cron value."); if (config.Value.ScheduleSettings!.CronUpdateSchedule == cron) - return Ok(CronUpdateSchedule()); + return CronUpdateSchedule(); config.Value.ScheduleSettings!.CronUpdateSchedule = cron; config.Value.SaveSetting(); - return Ok(CronUpdateSchedule()); + return CronUpdateSchedule(); } /// -- 2.43.0 From 7d6b21c5bb61945c34c47302cb665daf8e83aff0 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 04:51:09 +0300 Subject: [PATCH 460/474] fix: move from body to query --- Endpoint/Controllers/V1/Configuration/ScheduleController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs index 30397e1..1123a8a 100644 --- a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -46,7 +46,7 @@ public class ScheduleController(ILogger logger, IOptionsSnap /// Cron expression and the list of next scheduled task dates. /// Thrown if the provided cron expression is invalid. [HttpPost("CronUpdateSchedule")] - public ActionResult CronUpdateSchedule([FromBody] string cron) + public ActionResult CronUpdateSchedule([FromQuery] string cron) { cron = cron.Trim(); if (!CronExpression.TryParse(cron, CronFormat.Standard, out _)) @@ -77,7 +77,7 @@ public class ScheduleController(ILogger logger, IOptionsSnap /// Success or failure. /// Thrown if the start term date is more than 6 months in the past or future. [HttpPost("StartTerm")] - public ActionResult StartTerm([FromBody] DateOnly startTerm, [FromQuery] bool force = false) + public ActionResult StartTerm([FromQuery] DateOnly startTerm, [FromQuery] bool force = false) { var differentByTime = DateTime.Now - startTerm.ToDateTime(new TimeOnly(0, 0, 0)); if (differentByTime > TimeSpan.FromDays(190) || differentByTime.Multiply(-1) > TimeSpan.FromDays(190)) -- 2.43.0 From b3a0964aac8328f62c4dd34948975d53b481f31d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 20:28:04 +0300 Subject: [PATCH 461/474] fix: correct filter data --- .../Common/Services/CronUpdateSkipService.cs | 20 ++++++++++++++----- .../V1/Configuration/ScheduleController.cs | 6 ++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Endpoint/Common/Services/CronUpdateSkipService.cs b/Endpoint/Common/Services/CronUpdateSkipService.cs index 50ccab3..802dd64 100644 --- a/Endpoint/Common/Services/CronUpdateSkipService.cs +++ b/Endpoint/Common/Services/CronUpdateSkipService.cs @@ -18,7 +18,7 @@ public static class CronUpdateSkipService throw new ArgumentException("It is impossible to create a structure because it has incorrect values."); } - public static List Filter(this List data, DateOnly? currentDate = null) + public static List FilterDateEntry(this List data, DateOnly? currentDate = null) { currentDate ??= DateOnly.FromDateTime(DateTime.Now); return data.OrderBy(x => x.End ?? x.Date) @@ -26,8 +26,18 @@ public static class CronUpdateSkipService .ToList(); } - public static List Filter(this List data, DateTime? currentDate = null) => - data.Filter(DateOnly.FromDateTime(currentDate ?? DateTime.Now)); + public static List FilterDateEntry(this List data, DateTime? currentDate = null) => + data.FilterDateEntry(DateOnly.FromDateTime(currentDate ?? DateTime.Now)); + + public static List Filter(this List data, DateOnly? currentDate = null) + { + currentDate ??= DateOnly.FromDateTime(DateTime.Now); + + return data.Where(x => x.Date >= currentDate || x.End >= currentDate) + .OrderBy(x => x.End ?? x.Date) + .ToList(); + } + public static List GetNextTask(this List data, CronExpression expression, int depth = 1, DateOnly? currentDate = null) @@ -42,7 +52,7 @@ public static class CronUpdateSkipService do { - var lastSkip = data.Filter(nextRunTime.DateTime).LastOrDefault(); + var lastSkip = data.FilterDateEntry(nextRunTime.DateTime).LastOrDefault(); if (lastSkip is { Start: not null, End: not null }) nextRunTime = new DateTimeOffset(lastSkip.End.Value.AddDays(1), new TimeOnly(0, 0, 0), TimeSpan.Zero); @@ -56,7 +66,7 @@ public static class CronUpdateSkipService nextRunTime = next.Value; - if (data.Filter(nextRunTime.DateTime).Any()) + if (data.FilterDateEntry(nextRunTime.DateTime).Any()) continue; result.Add(nextRunTime); diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs index 1123a8a..6327a01 100644 --- a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -103,7 +103,9 @@ public class ScheduleController(ILogger logger, IOptionsSnap /// Cron update skip dates. [HttpGet("CronUpdateSkip")] public ActionResult> CronUpdateSkip() => - config.Value.ScheduleSettings!.CronUpdateSkipDateList.Filter(DateTime.Now).ConvertToDto(); + config.Value.ScheduleSettings!.CronUpdateSkipDateList + .Filter() + .ConvertToDto(); /// /// Updates the list of cron update skip dates in the configuration. @@ -124,7 +126,7 @@ public class ScheduleController(ILogger logger, IOptionsSnap throw new ControllerArgumentException(ex.Message); } - config.Value.ScheduleSettings!.CronUpdateSkipDateList = result.Filter(DateTime.Now); + config.Value.ScheduleSettings!.CronUpdateSkipDateList = result.Filter(); config.Value.SaveSetting(); return Ok(); -- 2.43.0 From dc08285ec8ab100ead9011c5c84ffbd802498980 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 20:31:52 +0300 Subject: [PATCH 462/474] feat: clear old records --- .../V1/Configuration/ScheduleController.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs index 6327a01..b841b9f 100644 --- a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -102,10 +102,18 @@ public class ScheduleController(ILogger logger, IOptionsSnap /// /// Cron update skip dates. [HttpGet("CronUpdateSkip")] - public ActionResult> CronUpdateSkip() => - config.Value.ScheduleSettings!.CronUpdateSkipDateList - .Filter() + public ActionResult> CronUpdateSkip() + { + var generalConfig = config.Value; + + generalConfig.ScheduleSettings!.CronUpdateSkipDateList = + generalConfig.ScheduleSettings.CronUpdateSkipDateList.Filter(); + generalConfig.SaveSetting(); + + return generalConfig.ScheduleSettings!.CronUpdateSkipDateList .ConvertToDto(); + } + /// /// Updates the list of cron update skip dates in the configuration. -- 2.43.0 From 885b937b0b4a093a83e16c22b43b4dd388bee673 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 3 Feb 2025 03:44:40 +0300 Subject: [PATCH 463/474] feat: add parsing from files --- .../V1/Configuration/ScheduleController.cs | 68 ++++- Endpoint/Endpoint.csproj | 7 +- Endpoint/Sync/ScheduleSynchronizer.cs | 272 ++++++++++++++++-- 3 files changed, 319 insertions(+), 28 deletions(-) diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs index b841b9f..178704b 100644 --- a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -1,6 +1,8 @@ using Asp.Versioning; using Cronos; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Mirea.Api.DataAccess.Persistence; @@ -11,15 +13,19 @@ using Mirea.Api.Endpoint.Common.MapperDto; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; +using Mirea.Api.Endpoint.Sync; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace Mirea.Api.Endpoint.Controllers.V1.Configuration; [ApiVersion("1.0")] -public class ScheduleController(ILogger logger, IOptionsSnapshot config, UberDbContext dbContext) : ConfigurationBaseController +public class ScheduleController(ILogger logger, IOptionsSnapshot config, UberDbContext dbContext, IServiceProvider provider) : ConfigurationBaseController { /// /// Retrieves the cron update schedule and calculates the next scheduled tasks based on the provided depth. @@ -113,7 +119,7 @@ public class ScheduleController(ILogger logger, IOptionsSnap return generalConfig.ScheduleSettings!.CronUpdateSkipDateList .ConvertToDto(); } - + /// /// Updates the list of cron update skip dates in the configuration. @@ -139,4 +145,62 @@ public class ScheduleController(ILogger logger, IOptionsSnap return Ok(); } + + /// + /// Uploads schedule files and initiates synchronization. + /// + /// The list of schedule files to upload. + /// The default campus for each uploaded file. Must match the number of files. + /// If true, removes all existing lessons before synchronization. Default is false. + /// Success or failure. + /// + /// Thrown if: + /// - No files are provided. + /// - The number of default campuses does not match the number of files. + /// - Any default campus is null or empty. + /// + [HttpPost("Upload")] + public async Task UploadScheduleFiles(List? files, [FromQuery] string[]? defaultCampus, [FromQuery] bool force = false) + { + if (files == null || files.Count == 0) + throw new ControllerArgumentException("No files were found."); + + if (defaultCampus == null || files.Count != defaultCampus.Length) + throw new ControllerArgumentException("No default campuses are specified for the file."); + + if (defaultCampus.Any(string.IsNullOrEmpty)) + throw new ControllerArgumentException("Each file should have a default campus."); + + var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(Path.GetRandomFileName())); + + if (!Directory.Exists(tempDirectory)) + Directory.CreateDirectory(tempDirectory); + + List<(string, string)> filePaths = []; + + for (var i = 0; i < files.Count; i++) + { + if (files[i].Length <= 0) + continue; + + var filePath = Path.Combine(tempDirectory, files[i].FileName); + + await using var stream = new FileStream(filePath, FileMode.Create); + await files[i].CopyToAsync(stream); + + filePaths.Add((filePath, defaultCampus[i])); + } + + var sync = (ScheduleSynchronizer)ActivatorUtilities.GetServiceOrCreateInstance(provider, typeof(ScheduleSynchronizer)); + + if (force) + { + dbContext.Lessons.RemoveRange(dbContext.Lessons.ToList()); + await dbContext.SaveChangesAsync(); + } + + _ = sync.StartSync(filePaths, CancellationToken.None); + + return Ok(); + } } \ No newline at end of file diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 0d86702..6f8bd07 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0-rc6 - 1.0.2.6 - 1.0.2.6 + 1.0-rc7 + 1.0.2.7 + 1.0.2.7 Mirea.Api.Endpoint $(AssemblyName) Exe @@ -41,6 +41,7 @@ + diff --git a/Endpoint/Sync/ScheduleSynchronizer.cs b/Endpoint/Sync/ScheduleSynchronizer.cs index fc6f3f6..2dd533c 100644 --- a/Endpoint/Sync/ScheduleSynchronizer.cs +++ b/Endpoint/Sync/ScheduleSynchronizer.cs @@ -6,9 +6,11 @@ using Mirea.Api.DataAccess.Persistence; using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Endpoint.Sync.Common; +using Mirea.Tools.Schedule.Parser.Domain; using Mirea.Tools.Schedule.WebParser; using Mirea.Tools.Schedule.WebParser.Common.Domain; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -220,35 +222,15 @@ internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSna await dbContext.LessonAssociations.BulkSynchronizeAsync(_lessonAssociation.GetAll(), bulkOperation => bulkOperation.BatchSize = 1000, cancellationToken); } - public async Task StartSync(CancellationToken cancellationToken) + private async Task Sync(Func>> parseDataAsync, CancellationToken cancellationToken) { - var pairPeriods = config.Value.ScheduleSettings?.PairPeriod; - var startTerm = config.Value.ScheduleSettings?.StartTerm; - - if (pairPeriods == null || startTerm == null) - { - logger.LogWarning("It is not possible to synchronize the schedule due to the fact that the {Arg1} or {Arg2} variable is not initialized.", - nameof(pairPeriods), - nameof(startTerm)); - - return; - } - Stopwatch watch = new(); watch.Start(); - var parser = new Parser - { - Pairs = pairPeriods - .ToDictionary(x => x.Key, - x => (x.Value.Start, x.Value.End)), - TermStart = startTerm.Value.ToDateTime(new TimeOnly(0, 0, 0)) - }; - try { logger.LogDebug("Start parsing schedule"); - var data = await parser.ParseAsync(cancellationToken); + var data = await parseDataAsync(cancellationToken); watch.Stop(); var parsingTime = watch.ElapsedMilliseconds; @@ -282,11 +264,255 @@ internal partial class ScheduleSynchronizer(UberDbContext dbContext, IOptionsSna catch (Exception ex) { logger.LogError(ex, "An error occurred during synchronization."); - maintenanceMode.DisableMaintenanceMode(); throw; } + finally + { + maintenanceMode.DisableMaintenanceMode(); + } } + public async Task StartSync(CancellationToken cancellationToken) + { + var pairPeriods = config.Value.ScheduleSettings?.PairPeriod + .ToDictionary(x => x.Key, x => (x.Value.Start, x.Value.End)); + + var startTerm = config.Value.ScheduleSettings?.StartTerm; + + if (pairPeriods == null || startTerm == null) + { + logger.LogWarning("It is not possible to synchronize the schedule due to the fact that the {Arg1} or {Arg2} variable is not initialized.", + nameof(pairPeriods), + nameof(startTerm)); + + return; + } + + var parser = new Parser + { + Pairs = pairPeriods + .ToDictionary(x => x.Key, + x => (x.Value.Start, x.Value.End)), + TermStart = startTerm.Value.ToDateTime(new TimeOnly(0, 0, 0)) + }; + + try + { + await Sync(parser.ParseAsync, cancellationToken); + } + catch (Exception ex) + { + logger.LogError(ex, "An error occurred during synchronization."); + throw; + } + finally + { + maintenanceMode.DisableMaintenanceMode(); + } + } + + public async Task StartSync(List<(string File, string Campus)> files, CancellationToken cancellationToken) + { + await Task.Yield(); + var pairPeriods = config.Value.ScheduleSettings?.PairPeriod + .ToDictionary(x => x.Key, x => (x.Value.Start, x.Value.End)); + + if (pairPeriods == null) + { + logger.LogWarning("It is not possible to synchronize the schedule due to the fact that the {Arg1} variable is not initialized.", + nameof(pairPeriods)); + + return; + } + + try + { + Task> ParseTask(CancellationToken ctx) + { + var mappedData = new ConcurrentBag(); + + ParallelOptions options = new() { CancellationToken = ctx, MaxDegreeOfParallelism = Environment.ProcessorCount }; + Parallel.ForEach(files, options, (file) => + { + var parser = new Tools.Schedule.Parser.Parser(); + var result = ConvertToGroupResults(parser.Parse(file.File, pairPeriods), file.Campus); + + foreach (var item in result) mappedData.Add(item); + }); + + return Task.FromResult(mappedData.ToList()); + } + + await Sync(ParseTask, cancellationToken); + } + catch (Exception ex) + { + logger.LogError(ex, "An error occurred during synchronization."); + throw; + } + finally + { + maintenanceMode.DisableMaintenanceMode(); + } + } + + private static List ConvertToGroupResults(IEnumerable groups, string campusDefault, CancellationToken cancellationToken = default) + { + var result = new List(); + + foreach (var group in groups) + { + cancellationToken.ThrowIfCancellationRequested(); + + foreach (var day in group.Days) + { + foreach (var pair in day.Lessons) + { + foreach (var lesson in pair.Value) + { + if (string.IsNullOrWhiteSpace(lesson.TypeOfOccupation)) + continue; + + var (weeks, isExclude) = ParseWeeks(lesson.Discipline); + + var (lectureHalls, campuses) = ParseLectureHalls(lesson.LectureHall, campusDefault); + + var groupResult = new GroupResult + { + Day = day.DayOfWeek, + Pair = pair.Key, + IsEven = lesson.IsEven, + Group = group.GroupName, + Discipline = NormalizeDiscipline(lesson.Discipline), + Professor = ParseProfessors(lesson.Professor), + TypeOfOccupation = lesson.TypeOfOccupation, + LectureHalls = lectureHalls, + Campuses = campuses, + SpecialWeek = weeks, + IsExclude = isExclude + }; + + result.Add(groupResult); + } + } + } + } + + return result; + } + + private static string[]? ParseProfessors(string? input) + { + if (string.IsNullOrWhiteSpace(input)) return null; + + var normalized = Regex.Replace(input + .Replace("\n", " ") + .Replace(",", " "), + @"\s+", " ").Trim(); + + return ProfessorFullName().Matches(normalized) + .Select(m => $"{m.Groups["surname"].Value} {m.Groups["initials"].Value}".Trim()) + .Where(x => !string.IsNullOrEmpty(x)) + .ToArray(); + } + + private static (int[]? weeks, bool? isExclude) ParseWeeks(string discipline) + { + var match = ParseSpecificWeeks().Match(discipline); + + if (!match.Success) return (null, null); + + var numbers = new List(); + var ranges = match.Groups[2].Value.Split(','); + + foreach (var range in ranges) + { + if (range.Contains('-')) + { + var parts = range.Split('-'); + if (int.TryParse(parts[0], out var start) && + int.TryParse(parts[1], out var end)) + { + numbers.AddRange(Enumerable.Range(start, end - start + 1)); + } + } + else + if (int.TryParse(range, out var num)) numbers.Add(num); + } + + return ( + weeks: numbers.Distinct().OrderBy(x => x).ToArray(), + isExclude: match.Groups[1].Success + ); + } + + private static string NormalizeDiscipline(string input) + { + var normalized = Regex.Replace(input + .Replace("\n", " ") + .Replace("\r", " "), + @"\s{2,}", " "); + + normalized = Regex.Replace(normalized, + @"(\S+)\s(\S{3,})", + "$1 $2"); + + normalized = ParseSpecificWeeks().Replace(normalized, ""); + + return normalized.Trim(); + } + + private static (string[]? lectureHalls, string[]? campuses) ParseLectureHalls(string? input, string defaultCampus) + { + if (string.IsNullOrWhiteSpace(input)) + return (null, null); + + var matches = ParseLectureCampus().Matches(input); + var lectureHalls = new List(); + var campuses = new List(); + + foreach (Match match in matches) + { + if (match.Groups["lectureWithCampus"].Success) + { + var raw = match.Value.Split('('); + var campus = raw.LastOrDefault()?.Trim(')').Trim(); + var lecture = raw.FirstOrDefault()?.Trim(); + + if (string.IsNullOrEmpty(campus) || string.IsNullOrEmpty(lecture)) + continue; + + campuses.Add(campus); + lectureHalls.Add(lecture); + } + else if (match.Groups["lecture"].Success) + { + var lecture = match.Value.Trim(); + if (string.IsNullOrEmpty(lecture)) + continue; + + campuses.Add(defaultCampus); + lectureHalls.Add(lecture); + } + } + + return ( + lectureHalls: lectureHalls.ToArray(), + campuses: campuses.ToArray() + ); + } + + + [GeneratedRegex(@"\w{4}-\d{2}-\d{2}(?=\s?\d?\s?[Пп]/?[Гг]\s?\d?)?")] private static partial Regex OnlyGroupName(); + + [GeneratedRegex(@"(?[А-ЯЁ][а-яё]+(-[А-ЯЁ][а-яё]+)?)\s*(?[А-ЯЁ]\.[А-ЯЁ]?\.?)?", RegexOptions.IgnorePatternWhitespace)] + private static partial Regex ProfessorFullName(); + + [GeneratedRegex(@"([Кк]р\.?)?\s*((\d+-\d+|\d+)(,\s*\d+(-\d+)?)*)\s*[Нн]\.?", RegexOptions.IgnoreCase, "ru-RU")] + private static partial Regex ParseSpecificWeeks(); + + [GeneratedRegex(@"(?[^,.\n]+\s?\([А-Яа-яA-Za-z]+-?\d+\))|(?[^,.\n]+)")] + private static partial Regex ParseLectureCampus(); } \ No newline at end of file -- 2.43.0 From b40e394bcf115f562a1f1de48ac4c4a252fa3d52 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 3 Feb 2025 10:55:47 +0300 Subject: [PATCH 464/474] fix: System.ObjectDisposedException for db context into sync secrvice --- .../V1/Configuration/ScheduleController.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs index 178704b..6191c6d 100644 --- a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -2,6 +2,7 @@ using Cronos; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -191,15 +192,20 @@ public class ScheduleController(ILogger logger, IOptionsSnap filePaths.Add((filePath, defaultCampus[i])); } - var sync = (ScheduleSynchronizer)ActivatorUtilities.GetServiceOrCreateInstance(provider, typeof(ScheduleSynchronizer)); - if (force) { - dbContext.Lessons.RemoveRange(dbContext.Lessons.ToList()); + dbContext.Lessons.RemoveRange(await dbContext.Lessons.ToListAsync()); await dbContext.SaveChangesAsync(); } - _ = sync.StartSync(filePaths, CancellationToken.None); + var scopeFactory = provider.GetRequiredService(); + _ = Task.Run(async () => + { + using var scope = scopeFactory.CreateScope(); + var sync = scope.ServiceProvider.GetRequiredService(); + + await sync.StartSync(filePaths, CancellationToken.None); + }, CancellationToken.None); return Ok(); } -- 2.43.0 From ea4c8b61e083ce791578e511339b11f6a9154f76 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 3 Feb 2025 11:25:39 +0300 Subject: [PATCH 465/474] refactor: use thread pool instead task --- .../V1/Configuration/ScheduleController.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs index 6191c6d..f965040 100644 --- a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -199,13 +199,20 @@ public class ScheduleController(ILogger logger, IOptionsSnap } var scopeFactory = provider.GetRequiredService(); - _ = Task.Run(async () => + ThreadPool.QueueUserWorkItem(async void (_) => { + try + { using var scope = scopeFactory.CreateScope(); - var sync = scope.ServiceProvider.GetRequiredService(); + var sync = (ScheduleSynchronizer)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, typeof(ScheduleSynchronizer)); await sync.StartSync(filePaths, CancellationToken.None); - }, CancellationToken.None); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + }); return Ok(); } -- 2.43.0 From 31c1d2804ddda6989b24b031495ccb489ad0bc64 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Thu, 6 Feb 2025 16:27:20 +0300 Subject: [PATCH 466/474] fix: hotfix calculate next run time --- Endpoint/Common/Services/CronUpdateSkipService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Endpoint/Common/Services/CronUpdateSkipService.cs b/Endpoint/Common/Services/CronUpdateSkipService.cs index 802dd64..8ecfa23 100644 --- a/Endpoint/Common/Services/CronUpdateSkipService.cs +++ b/Endpoint/Common/Services/CronUpdateSkipService.cs @@ -37,7 +37,7 @@ public static class CronUpdateSkipService .OrderBy(x => x.End ?? x.Date) .ToList(); } - + public static List GetNextTask(this List data, CronExpression expression, int depth = 1, DateOnly? currentDate = null) @@ -45,8 +45,8 @@ public static class CronUpdateSkipService if (depth <= 0) return []; - currentDate ??= DateOnly.FromDateTime(DateTime.UtcNow); - DateTimeOffset nextRunTime = currentDate.Value.ToDateTime(new TimeOnly(0, 0, 0)); + currentDate ??= DateOnly.FromDateTime(DateTime.Now); + DateTimeOffset nextRunTime = currentDate.Value.ToDateTime(TimeOnly.FromDateTime(DateTime.Now)); List result = []; @@ -59,7 +59,7 @@ public static class CronUpdateSkipService else if (lastSkip.Date.HasValue) nextRunTime = new DateTimeOffset(lastSkip.Date.Value.AddDays(1), new TimeOnly(0, 0, 0), TimeSpan.Zero); - var next = expression.GetNextOccurrence(nextRunTime, TimeZoneInfo.Local); + var next = expression.GetNextOccurrence(nextRunTime.ToUniversalTime(), TimeZoneInfo.Local); if (!next.HasValue) return result; -- 2.43.0 From aabeed0aa5dbb8a97ea9f76af58db7b14607fda5 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Mon, 10 Feb 2025 16:07:51 +0300 Subject: [PATCH 467/474] feat: add backend version to swagger --- .../Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs | 4 +++- Endpoint/Endpoint.csproj | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs index 2de861c..8b40ca0 100644 --- a/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs +++ b/Endpoint/Configuration/SwaggerOptions/ConfigureSwaggerOptions.cs @@ -4,6 +4,8 @@ using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using System; +using System.Diagnostics; +using System.Reflection; namespace Mirea.Api.Endpoint.Configuration.SwaggerOptions; @@ -19,7 +21,7 @@ public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : { var info = new OpenApiInfo() { - Title = "MIREA Schedule Web API", + Title = $"MIREA Schedule Web API ({FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion})", Version = description.ApiVersion.ToString(), Description = "This API provides a convenient interface for retrieving data stored in the database. " + "Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.", diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 6f8bd07..7fdde78 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -19,6 +19,7 @@ True docs.xml $(NoWarn);1591 + false -- 2.43.0 From e7edc79ebc0eab829bc4462a31a417fd19e8d28b Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 11 Feb 2025 15:04:38 +0300 Subject: [PATCH 468/474] build: instead build run analyze --- .gitea/workflows/test.yaml | 29 ------------------ .github/workflows/code-analyze.yaml | 30 +++++++++++++++++++ .../workflows/release-version.yml | 4 +-- Backend.sln | 4 +-- Dockerfile | 8 +++-- nuget.config | 13 -------- 6 files changed, 40 insertions(+), 48 deletions(-) delete mode 100644 .gitea/workflows/test.yaml create mode 100644 .github/workflows/code-analyze.yaml rename .gitea/workflows/deploy-stage.yaml => .github/workflows/release-version.yml (96%) delete mode 100644 nuget.config diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml deleted file mode 100644 index 8ee95ea..0000000 --- a/.gitea/workflows/test.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: .NET Test Pipeline - -on: - pull_request: - push: - branches: - [master, 'release/*'] - -jobs: - build-and-test: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up .NET Core - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' - - - name: Restore dependencies - run: dotnet restore - - - name: Build the solution - run: dotnet build --configuration Release - - - name: Run tests - run: dotnet test --configuration Release --no-build --no-restore --verbosity normal \ No newline at end of file diff --git a/.github/workflows/code-analyze.yaml b/.github/workflows/code-analyze.yaml new file mode 100644 index 0000000..911543d --- /dev/null +++ b/.github/workflows/code-analyze.yaml @@ -0,0 +1,30 @@ +name: .NET Test Pipeline + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checking out + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: SonarScanner for .NET 8 with pull request decoration support + uses: highbyte/sonarscan-dotnet@v2.3.3 + with: + sonarProjectKey: $(echo "${{ github.repository }}" | cut -d'/' -f2) + sonarProjectName: $(echo "${{ github.repository }}" | cut -d'/' -f2) + sonarHostname: ${{ secrets.SONAR_HOST_URL }} + dotnetPreBuildCmd: dotnet nuget add source --name="Winsomnia" --username ${secrets.NUGET_USERNAME} --password ${{ secrets.NUGET_PASSWORD }} --store-password-in-clear-text ${secrets.NUGET_ADDRESS} && dotnet format --verify-no-changes --diagnostics -v diag --severity warn + dotnetTestArguments: --logger trx --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover + sonarBeginArguments: /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" -d:sonar.cs.vstest.reportsPaths="**/TestResults/*.trx" + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.gitea/workflows/deploy-stage.yaml b/.github/workflows/release-version.yml similarity index 96% rename from .gitea/workflows/deploy-stage.yaml rename to .github/workflows/release-version.yml index 8e22f9b..6f628d4 100644 --- a/.gitea/workflows/deploy-stage.yaml +++ b/.github/workflows/release-version.yml @@ -3,7 +3,7 @@ on: push: branches: - [master, 'release/*'] + [master] jobs: build-and-deploy: @@ -24,7 +24,7 @@ jobs: - name: Build and push Docker image run: | - docker build --build-arg NUGET_USERNAME=${{ secrets.NUGET_USERNAME }} --build-arg NUGET_PASSWORD=${{ secrets.NUGET_PASSWORD }} -t ${{ secrets.DOCKER_USERNAME }}/mirea-backend:latest . + docker build --build-arg NUGET_USERNAME=${{ secrets.NUGET_USERNAME }} --build-arg NUGET_PASSWORD=${{ secrets.NUGET_PASSWORD }} --build-arg NUGET_ADDRESS=${{ secrets.NUGET_ADDRESS }} -t ${{ secrets.DOCKER_USERNAME }}/mirea-backend:latest . docker push ${{ secrets.DOCKER_USERNAME }}/mirea-backend:latest - name: Start ssh-agent diff --git a/Backend.sln b/Backend.sln index 6cb0a6d..ee30ecc 100644 --- a/Backend.sln +++ b/Backend.sln @@ -12,11 +12,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution", .env = .env .gitattributes = .gitattributes .gitignore = .gitignore - .gitea\workflows\deploy-stage.yaml = .gitea\workflows\deploy-stage.yaml + .github\workflows\code-analyze.yaml = .github\workflows\code-analyze.yaml Dockerfile = Dockerfile LICENSE.txt = LICENSE.txt README.md = README.md - .gitea\workflows\test.yaml = .gitea\workflows\test.yaml + .github\workflows\release-version.yml = .github\workflows\release-version.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiDto", "ApiDto\ApiDto.csproj", "{0335FA36-E137-453F-853B-916674C168FE}" diff --git a/Dockerfile b/Dockerfile index 386c02f..0661923 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base LABEL company="Winsomnia" LABEL maintainer.name="Wesser" maintainer.email="support@winsomnia.net" WORKDIR /app @@ -13,10 +13,14 @@ COPY . . ARG NUGET_USERNAME ARG NUGET_PASSWORD +ARG NUGET_ADDRESS + ENV NUGET_USERNAME=$NUGET_USERNAME ENV NUGET_PASSWORD=$NUGET_PASSWORD +ENV NUGET_ADDRESS=$NUGET_ADDRESS -RUN dotnet restore ./Backend.sln --configfile nuget.config +RUN dotnet nuget add source --name="Winsomnia" --username ${NUGET_USERNAME} --store-password-in-clear-text --password ${NUGET_PASSWORD} ${NUGET_ADDRESS} +RUN dotnet restore ./Backend.sln WORKDIR /app WORKDIR /src RUN dotnet publish ./Endpoint/Endpoint.csproj -c Release --self-contained false -p:PublishSingleFile=false -o /app diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 96674ab..0000000 --- a/nuget.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file -- 2.43.0 From 90b4662dda933a09fa6d218448f8fc5ecfa0a33e Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 11 Feb 2025 15:16:51 +0300 Subject: [PATCH 469/474] Release 1.0.0 --- Endpoint/Endpoint.csproj | 16 ++++++---------- Security/Security.csproj | 6 +++--- SqlData/Application/Application.csproj | 6 +++--- SqlData/Domain/Domain.csproj | 6 +++--- SqlData/Persistence/Persistence.csproj | 6 +++--- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 7fdde78..190d9d6 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -5,17 +5,13 @@ disable enable Winsomnia - 1.0-rc7 - 1.0.2.7 - 1.0.2.7 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.Endpoint $(AssemblyName) Exe false - 65cea060-88bf-4e35-9cfb-18fc996a8f05 - Linux - . - False True docs.xml $(NoWarn);1591 @@ -28,7 +24,7 @@ - + @@ -41,7 +37,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -64,7 +60,7 @@ - + diff --git a/Security/Security.csproj b/Security/Security.csproj index a56c5b5..6a7c01e 100644 --- a/Security/Security.csproj +++ b/Security/Security.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.1.3 - 1.1.3.3 - 1.1.3.3 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.Security $(AssemblyName) Library diff --git a/SqlData/Application/Application.csproj b/SqlData/Application/Application.csproj index 8cf1e6f..31398c7 100644 --- a/SqlData/Application/Application.csproj +++ b/SqlData/Application/Application.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.3 - 1.0.3.3 - 1.0.3.3 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.DataAccess.Application $(AssemblyName) diff --git a/SqlData/Domain/Domain.csproj b/SqlData/Domain/Domain.csproj index a3244e6..940e1f5 100644 --- a/SqlData/Domain/Domain.csproj +++ b/SqlData/Domain/Domain.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.1 - 1.0.3.1 - 1.0.3.1 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.DataAccess.Domain $(AssemblyName) diff --git a/SqlData/Persistence/Persistence.csproj b/SqlData/Persistence/Persistence.csproj index 0dfce28..ad428b5 100644 --- a/SqlData/Persistence/Persistence.csproj +++ b/SqlData/Persistence/Persistence.csproj @@ -5,9 +5,9 @@ disable enable Winsomnia - 1.0.3 - 1.0.3.3 - 1.0.3.3 + 1.0.0 + 1.0.3.0 + 1.0.3.0 Mirea.Api.DataAccess.Persistence $(AssemblyName) -- 2.43.0 From 4cd476764dd56c307f80f2674f7a34e924557863 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 11 Feb 2025 15:25:47 +0300 Subject: [PATCH 470/474] build: fix secrets --- .github/workflows/code-analyze.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-analyze.yaml b/.github/workflows/code-analyze.yaml index 911543d..2bc7500 100644 --- a/.github/workflows/code-analyze.yaml +++ b/.github/workflows/code-analyze.yaml @@ -23,7 +23,7 @@ jobs: sonarProjectKey: $(echo "${{ github.repository }}" | cut -d'/' -f2) sonarProjectName: $(echo "${{ github.repository }}" | cut -d'/' -f2) sonarHostname: ${{ secrets.SONAR_HOST_URL }} - dotnetPreBuildCmd: dotnet nuget add source --name="Winsomnia" --username ${secrets.NUGET_USERNAME} --password ${{ secrets.NUGET_PASSWORD }} --store-password-in-clear-text ${secrets.NUGET_ADDRESS} && dotnet format --verify-no-changes --diagnostics -v diag --severity warn + dotnetPreBuildCmd: dotnet nuget add source --name="Winsomnia" --username ${{ secrets.NUGET_USERNAME }} --password ${{ secrets.NUGET_PASSWORD }} --store-password-in-clear-text ${{ secrets.NUGET_ADDRESS }} && dotnet format --verify-no-changes --diagnostics -v diag --severity warn dotnetTestArguments: --logger trx --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover sonarBeginArguments: /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" -d:sonar.cs.vstest.reportsPaths="**/TestResults/*.trx" env: -- 2.43.0 From 3eb043b24cbfb7640fcd70ee9af21e428a951d16 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 11 Feb 2025 15:29:43 +0300 Subject: [PATCH 471/474] build: fix code style with CRLF --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 8016c51..9e85bae 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,7 @@ indent_style = space tab_width = 4 # Предпочтения для новых строк -end_of_line = crlf +end_of_line = unset insert_final_newline = false #### Действия кода .NET #### @@ -244,7 +244,7 @@ dotnet_naming_style.begins_with_i.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 indent_size = 4 -end_of_line = crlf +end_of_line = unset dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion -- 2.43.0 From b0d9a67c1c420ca73a7392d0e7b89d37f1c9794d Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 11 Feb 2025 15:36:55 +0300 Subject: [PATCH 472/474] refacotr: clean code --- ApiDto/Common/OAuthProvider.cs | 2 +- ApiDto/Common/PasswordPolicy.cs | 2 +- ApiDto/Common/TwoFactorAuthentication.cs | 2 +- ApiDto/Requests/CreateUserRequest.cs | 2 +- ApiDto/Requests/ScheduleRequest.cs | 2 +- ApiDto/Responses/CampusDetailsResponse.cs | 2 +- ApiDto/Responses/ScheduleResponse.cs | 2 +- Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs | 2 +- Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs | 2 +- Endpoint/Configuration/Core/Startup/JwtConfiguration.cs | 2 +- Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs | 2 +- Endpoint/Configuration/ISaveSettings.cs | 2 +- Security/Common/Interfaces/ICacheService.cs | 2 +- Security/Common/Model/CookieOptions.cs | 2 +- .../Migrations/20240601023106_InitialMigration.cs | 2 +- .../Migrations/20241027034820_RemoveUnusedRef.cs | 2 +- .../Migrations/20240601021702_InitialMigration.cs | 2 +- .../PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs | 2 +- .../Migrations/20240601015714_InitialMigration.cs | 2 +- .../Migrations/20241027032931_RemoveUnusedRef.cs | 2 +- SqlData/Persistence/Common/ModelBuilderExtensions.cs | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ApiDto/Common/OAuthProvider.cs b/ApiDto/Common/OAuthProvider.cs index ccd0622..d0781a5 100644 --- a/ApiDto/Common/OAuthProvider.cs +++ b/ApiDto/Common/OAuthProvider.cs @@ -19,4 +19,4 @@ public enum OAuthProvider /// OAuth provider for Mail.ru. /// MailRu -} +} \ No newline at end of file diff --git a/ApiDto/Common/PasswordPolicy.cs b/ApiDto/Common/PasswordPolicy.cs index bd308e6..1e27ec7 100644 --- a/ApiDto/Common/PasswordPolicy.cs +++ b/ApiDto/Common/PasswordPolicy.cs @@ -29,4 +29,4 @@ public class PasswordPolicy /// Gets or sets a value indicating whether at least one special character is required in the password. /// public bool RequireSpecialCharacter { get; set; } -} +} \ No newline at end of file diff --git a/ApiDto/Common/TwoFactorAuthentication.cs b/ApiDto/Common/TwoFactorAuthentication.cs index 52dd09c..568e3e2 100644 --- a/ApiDto/Common/TwoFactorAuthentication.cs +++ b/ApiDto/Common/TwoFactorAuthentication.cs @@ -14,4 +14,4 @@ public enum TwoFactorAuthentication /// TOTP (Time-based One-Time Password) is required for additional verification. /// TotpRequired, -} +} \ No newline at end of file diff --git a/ApiDto/Requests/CreateUserRequest.cs b/ApiDto/Requests/CreateUserRequest.cs index 2d6fecf..56be856 100644 --- a/ApiDto/Requests/CreateUserRequest.cs +++ b/ApiDto/Requests/CreateUserRequest.cs @@ -27,4 +27,4 @@ public class CreateUserRequest [Required] [MinLength(2)] public required string Password { get; set; } -} +} \ No newline at end of file diff --git a/ApiDto/Requests/ScheduleRequest.cs b/ApiDto/Requests/ScheduleRequest.cs index dc5dbb6..3f971b1 100644 --- a/ApiDto/Requests/ScheduleRequest.cs +++ b/ApiDto/Requests/ScheduleRequest.cs @@ -34,4 +34,4 @@ public class ScheduleRequest /// Gets or sets an array of lesson type IDs. /// public int[]? LessonType { get; set; } = null; -} +} \ No newline at end of file diff --git a/ApiDto/Responses/CampusDetailsResponse.cs b/ApiDto/Responses/CampusDetailsResponse.cs index 82db94e..83cf0ce 100644 --- a/ApiDto/Responses/CampusDetailsResponse.cs +++ b/ApiDto/Responses/CampusDetailsResponse.cs @@ -28,4 +28,4 @@ public class CampusDetailsResponse /// Gets or sets the address of the campus (optional). /// public string? Address { get; set; } -} +} \ No newline at end of file diff --git a/ApiDto/Responses/ScheduleResponse.cs b/ApiDto/Responses/ScheduleResponse.cs index 71462b9..51379d7 100644 --- a/ApiDto/Responses/ScheduleResponse.cs +++ b/ApiDto/Responses/ScheduleResponse.cs @@ -114,4 +114,4 @@ public class ScheduleResponse /// Gets or sets the links to online meetings for the schedule entry. /// public required IEnumerable LinkToMeet { get; set; } -} +} \ No newline at end of file diff --git a/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs b/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs index baa6a0d..deeb35d 100644 --- a/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs +++ b/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs @@ -16,4 +16,4 @@ public static class CronUpdateSkipConverter }).ToList(); public static List ConvertFromDto(this IEnumerable pairPeriod) => pairPeriod.Select(x => x.Get()).ToList(); -} +} \ No newline at end of file diff --git a/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs b/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs index a9ffffc..594611d 100644 --- a/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs +++ b/Endpoint/Common/MapperDto/PairPeriodTimeConverter.cs @@ -11,4 +11,4 @@ public static class PairPeriodTimeConverter public static Dictionary ConvertFromDto(this IDictionary pairPeriod) => pairPeriod.ToDictionary(kvp => kvp.Key, kvp => new ScheduleSettings.PairPeriodTime(kvp.Value.Start, kvp.Value.End)); -} +} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs b/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs index 4188479..962d48d 100644 --- a/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/JwtConfiguration.cs @@ -64,4 +64,4 @@ public static class JwtConfiguration }; }); } -} +} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs index 775ef8f..636c650 100644 --- a/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs +++ b/Endpoint/Configuration/Core/Startup/LoggerConfiguration.cs @@ -99,4 +99,4 @@ public static class LoggerConfiguration }; }); } -} +} \ No newline at end of file diff --git a/Endpoint/Configuration/ISaveSettings.cs b/Endpoint/Configuration/ISaveSettings.cs index 860cf5a..9ebdf31 100644 --- a/Endpoint/Configuration/ISaveSettings.cs +++ b/Endpoint/Configuration/ISaveSettings.cs @@ -2,4 +2,4 @@ public interface ISaveSettings { void SaveSetting(); -} +} \ No newline at end of file diff --git a/Security/Common/Interfaces/ICacheService.cs b/Security/Common/Interfaces/ICacheService.cs index c2cb1e3..a0e10cf 100644 --- a/Security/Common/Interfaces/ICacheService.cs +++ b/Security/Common/Interfaces/ICacheService.cs @@ -13,4 +13,4 @@ public interface ICacheService Task GetAsync(string key, CancellationToken cancellationToken = default); Task RemoveAsync(string key, CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/Security/Common/Model/CookieOptions.cs b/Security/Common/Model/CookieOptions.cs index 3dc22f2..ded2f70 100644 --- a/Security/Common/Model/CookieOptions.cs +++ b/Security/Common/Model/CookieOptions.cs @@ -26,4 +26,4 @@ public class CookieOptions internal void DropCookie(HttpContext context, string name) => SetCookie(context, name, "", DateTimeOffset.MinValue); -} +} \ No newline at end of file diff --git a/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.cs b/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.cs index 573b78f..827bd0f 100644 --- a/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.cs +++ b/SqlData/Migrations/MysqlMigrations/Migrations/20240601023106_InitialMigration.cs @@ -386,4 +386,4 @@ namespace MysqlMigrations.Migrations name: "Campus"); } } -} +} \ No newline at end of file diff --git a/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.cs b/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.cs index e990751..e9d0f09 100644 --- a/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.cs +++ b/SqlData/Migrations/MysqlMigrations/Migrations/20241027034820_RemoveUnusedRef.cs @@ -80,4 +80,4 @@ namespace MysqlMigrations.Migrations onDelete: ReferentialAction.SetNull); } } -} +} \ No newline at end of file diff --git a/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.cs b/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.cs index 9015b9f..6f2a9f5 100644 --- a/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.cs +++ b/SqlData/Migrations/PsqlMigrations/Migrations/20240601021702_InitialMigration.cs @@ -362,4 +362,4 @@ namespace PsqlMigrations.Migrations name: "Campus"); } } -} +} \ No newline at end of file diff --git a/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs b/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs index 1589fd5..e79d873 100644 --- a/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs +++ b/SqlData/Migrations/PsqlMigrations/Migrations/20241027032753_RemoveUnusedRef.cs @@ -46,4 +46,4 @@ namespace PsqlMigrations.Migrations onDelete: ReferentialAction.SetNull); } } -} +} \ No newline at end of file diff --git a/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.cs b/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.cs index 9afa7f2..a88a563 100644 --- a/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.cs +++ b/SqlData/Migrations/SqliteMigrations/Migrations/20240601015714_InitialMigration.cs @@ -361,4 +361,4 @@ namespace SqliteMigrations.Migrations name: "Campus"); } } -} +} \ No newline at end of file diff --git a/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.cs b/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.cs index 4fcdfcc..a5e0474 100644 --- a/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.cs +++ b/SqlData/Migrations/SqliteMigrations/Migrations/20241027032931_RemoveUnusedRef.cs @@ -46,4 +46,4 @@ namespace SqliteMigrations.Migrations onDelete: ReferentialAction.SetNull); } } -} +} \ No newline at end of file diff --git a/SqlData/Persistence/Common/ModelBuilderExtensions.cs b/SqlData/Persistence/Common/ModelBuilderExtensions.cs index f2c8af4..04b7ecb 100644 --- a/SqlData/Persistence/Common/ModelBuilderExtensions.cs +++ b/SqlData/Persistence/Common/ModelBuilderExtensions.cs @@ -19,4 +19,4 @@ public static class ModelBuilderExtensions var applyConcreteMethod = applyGenericMethod.MakeGenericMethod(entityType); applyConcreteMethod.Invoke(modelBuilder, [configuration]); } -} +} \ No newline at end of file -- 2.43.0 From 047ccfa7548361584cae30d70669acbbed7ad0c3 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 11 Feb 2025 16:35:56 +0300 Subject: [PATCH 473/474] fix: correct calculate next occurrence --- .../Common/Services/CronUpdateSkipService.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Endpoint/Common/Services/CronUpdateSkipService.cs b/Endpoint/Common/Services/CronUpdateSkipService.cs index 8ecfa23..fc766d1 100644 --- a/Endpoint/Common/Services/CronUpdateSkipService.cs +++ b/Endpoint/Common/Services/CronUpdateSkipService.cs @@ -45,32 +45,31 @@ public static class CronUpdateSkipService if (depth <= 0) return []; - currentDate ??= DateOnly.FromDateTime(DateTime.Now); - DateTimeOffset nextRunTime = currentDate.Value.ToDateTime(TimeOnly.FromDateTime(DateTime.Now)); - + DateTimeOffset nextRunTime = (currentDate?.ToDateTime(TimeOnly.MinValue) ?? DateTime.Now).ToUniversalTime(); List result = []; do { - var lastSkip = data.FilterDateEntry(nextRunTime.DateTime).LastOrDefault(); + var lastSkippedEntry = data.FilterDateEntry(nextRunTime.DateTime).LastOrDefault(); - if (lastSkip is { Start: not null, End: not null }) - nextRunTime = new DateTimeOffset(lastSkip.End.Value.AddDays(1), new TimeOnly(0, 0, 0), TimeSpan.Zero); - else if (lastSkip.Date.HasValue) - nextRunTime = new DateTimeOffset(lastSkip.Date.Value.AddDays(1), new TimeOnly(0, 0, 0), TimeSpan.Zero); + if (lastSkippedEntry is { Start: not null, End: not null }) + nextRunTime = lastSkippedEntry.End.Value.ToDateTime(TimeOnly.MinValue).AddDays(1); + else if (lastSkippedEntry.Date.HasValue) + nextRunTime = lastSkippedEntry.Date.Value.ToDateTime(TimeOnly.MinValue).AddDays(1); - var next = expression.GetNextOccurrence(nextRunTime.ToUniversalTime(), TimeZoneInfo.Local); + var nextOccurrence = expression.GetNextOccurrence(nextRunTime.AddMinutes(-1), TimeZoneInfo.Local); - if (!next.HasValue) + if (!nextOccurrence.HasValue) return result; - nextRunTime = next.Value; - - if (data.FilterDateEntry(nextRunTime.DateTime).Any()) + if (data.FilterDateEntry(nextOccurrence.Value.DateTime).Count != 0) + { + nextRunTime = nextOccurrence.Value.AddDays(1); continue; + } - result.Add(nextRunTime); - nextRunTime = nextRunTime.AddMinutes(1); + result.Add(nextOccurrence.Value.ToLocalTime()); + nextRunTime = nextOccurrence.Value.AddMinutes(1); } while (result.Count < depth); -- 2.43.0 From 46bbc34956e590f685c6bc3e1b6f6e46e886bb41 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Tue, 11 Feb 2025 17:13:13 +0300 Subject: [PATCH 474/474] refactor: fix error WHITESPACE and FINALNEWLINE --- Endpoint/Controllers/V1/AuthController.cs | 2 +- Endpoint/Controllers/V1/Configuration/ScheduleController.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 2588dd8..3b19c92 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -315,4 +315,4 @@ public class AuthController(IOptionsSnapshot user, IOptionsSnapshot logger, IOptionsSnap { try { - using var scope = scopeFactory.CreateScope(); + using var scope = scopeFactory.CreateScope(); var sync = (ScheduleSynchronizer)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, typeof(ScheduleSynchronizer)); - await sync.StartSync(filePaths, CancellationToken.None); + await sync.StartSync(filePaths, CancellationToken.None); } catch (Exception ex) { -- 2.43.0