diff --git a/.env b/.env new file mode 100644 index 0000000..f06ff63 --- /dev/null +++ b/.env @@ -0,0 +1,101 @@ +# 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= + +# 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 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 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 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 diff --git a/ApiDto/Requests/Configuration/DatabaseRequest.cs b/ApiDto/Requests/Configuration/DatabaseRequest.cs new file mode 100644 index 0000000..5da58e3 --- /dev/null +++ b/ApiDto/Requests/Configuration/DatabaseRequest.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.DataAnnotations; + +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 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/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/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/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; } +} 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/Backend.sln b/Backend.sln index 383bd42..81a8dbe 100644 --- a/Backend.sln +++ b/Backend.sln @@ -3,28 +3,47 @@ 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}" 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiDto", "ApiDto\ApiDto.csproj", "{0335FA36-E137-453F-853B-916674C168FE}" +EndProject +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 +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 - {C27FB5CD-6A70-4FB2-847A-847B34806902} = {C27FB5CD-6A70-4FB2-847A-847B34806902} + {48C9998C-ECE2-407F-835F-1A7255A5C99E} = {48C9998C-ECE2-407F-835F-1A7255A5C99E} EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\Persistence.csproj", "{4C1E558F-633F-438E-AC3A-61CDDED917C5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MysqlMigrations", "SqlData\Migrations\MysqlMigrations\MysqlMigrations.csproj", "{5861915B-9574-4D5D-872F-D54A09651697}" ProjectSection(ProjectDependencies) = postProject - {E7F0A4D4-B032-4BB9-9526-1AF688F341A4} = {E7F0A4D4-B032-4BB9-9526-1AF688F341A4} + {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 @@ -33,26 +52,55 @@ 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 + {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 + {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 + {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 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} + {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} EndGlobalSection 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 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 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 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 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/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/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..ef57365 --- /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/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)); +} diff --git a/Endpoint/Common/Services/PathBuilder.cs b/Endpoint/Common/Services/PathBuilder.cs new file mode 100644 index 0000000..799f582 --- /dev/null +++ b/Endpoint/Common/Services/PathBuilder.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; +using System.Linq; + +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 diff --git a/Endpoint/Common/Services/Security/DistributedCacheService.cs b/Endpoint/Common/Services/Security/DistributedCacheService.cs new file mode 100644 index 0000000..bf3dc39 --- /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; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +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/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 diff --git a/Endpoint/Common/Services/Security/MemoryCacheService.cs b/Endpoint/Common/Services/Security/MemoryCacheService.cs new file mode 100644 index 0000000..a428034 --- /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; +using System.Threading; +using System.Threading.Tasks; + +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 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 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 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 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/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 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..2fa5ff9 --- /dev/null +++ b/Endpoint/Configuration/General/Settings/DbSettings.cs @@ -0,0 +1,31 @@ +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; + +[RequiredSettings] +public class DbSettings : IIsConfigured +{ + public enum DatabaseEnum + { + Mysql, + Sqlite, + PostgresSql + } + public DatabaseEnum TypeDatabase { get; set; } + public required string ConnectionStringSql { get; set; } + + 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 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/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/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 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/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/Configuration/SetupController.cs b/Endpoint/Controllers/Configuration/SetupController.cs new file mode 100644 index 0000000..1c9d275 --- /dev/null +++ b/Endpoint/Controllers/Configuration/SetupController.cs @@ -0,0 +1,312 @@ +using Cronos; +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 Mirea.Api.Endpoint.Configuration.General.Validators; +using MySqlConnector; +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; + +[ApiVersion("1.0")] +[ApiController] +[MaintenanceModeIgnore] +[ApiExplorerSettings(IgnoreApi = true)] +public class SetupController(ISetupToken setupToken, IMaintenanceModeNotConfigureService notConfigureService, IMemoryCache cache) : BaseController +{ + private const string CacheGeneralKey = "config_general"; + private const string CacheAdminKey = "config_admin"; + + private GeneralConfig GeneralConfig + { + get => cache.Get(CacheGeneralKey) ?? new GeneralConfig(); + set => cache.Set(CacheGeneralKey, value); + } + + [HttpGet("GenerateToken")] + [Localhost] + 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 \"{PathBuilder.Combine(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); + } + + 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}"); + } + } + + [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); + } + + [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); + } + + [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] + public ActionResult SetLogging([FromBody] LoggingRequest? request = null) + { + var settings = (request == null) switch + { + true => new LogSettings + { + EnableLogToFile = true, + LogFileName = "log-", + LogFilePath = OperatingSystem.IsWindows() || PathBuilder.IsDefaultPath ? + PathBuilder.Combine("logs") : + "/var/log/mirea" + }, + false => new LogSettings + { + EnableLogToFile = request.EnableLogToFile, + LogFileName = request.LogFileName, + LogFilePath = request.LogFilePath + } + }; + + var general = GeneralConfig; + general.LogSettings = settings; + GeneralConfig = general; + + return true; + } + + [HttpPost("SetEmail")] + [TokenAuthentication] + [BadRequestResponse] + public ActionResult SetEmail([FromBody] EmailRequest? request = null) + { + 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; + } + + [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; + } + + [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 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..904e3d2 100644 --- a/Endpoint/Controllers/V1/ScheduleController.cs +++ b/Endpoint/Controllers/V1/ScheduleController.cs @@ -1,19 +1,31 @@ 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; namespace Mirea.Api.Endpoint.Controllers.V1; -public class ScheduleController(IMediator mediator) : BaseControllerV1 +[ApiVersion("1.0")] +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. /// @@ -25,7 +37,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 +96,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 +153,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 +214,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 +275,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] diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 0191a2e..bd7ee16 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -22,15 +22,21 @@ - - + + + + - - + + + + + + \ No newline at end of file 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/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 3607500..87756d9 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,15 +7,26 @@ 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.Properties; +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; @@ -35,18 +47,83 @@ 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; + } + + 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.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true); + 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(builder.Configuration); + builder.Services.AddPersistence(generalConfig?.DbSettings?.DatabaseProvider ?? DatabaseProvider.Sqlite, generalConfig?.DbSettings?.ConnectionStringSql ?? string.Empty); builder.Services.AddControllers(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policy => @@ -92,8 +169,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()) @@ -111,6 +205,8 @@ public class Program } }); } + app.UseMiddleware(); + app.UseMiddleware(); app.UseHttpsRedirection(); 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 diff --git a/Persistence/DependencyInjection.cs b/Persistence/DependencyInjection.cs deleted file mode 100644 index ed6f554..0000000 --- a/Persistence/DependencyInjection.cs +++ /dev/null @@ -1,66 +0,0 @@ -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); - - 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()!); - services.AddScoped(provider => provider.GetService()!); - - return services; - } -} \ 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 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 diff --git a/Security/Common/Domain/PreAuthToken.cs b/Security/Common/Domain/PreAuthToken.cs new file mode 100644 index 0000000..f1f9684 --- /dev/null +++ b/Security/Common/Domain/PreAuthToken.cs @@ -0,0 +1,10 @@ +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/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 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 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 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 diff --git a/Security/Common/Interfaces/ICacheService.cs b/Security/Common/Interfaces/ICacheService.cs new file mode 100644 index 0000000..c2cb1e3 --- /dev/null +++ b/Security/Common/Interfaces/ICacheService.cs @@ -0,0 +1,16 @@ +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, + TimeSpan? slidingExpiration = null, + CancellationToken cancellationToken = default); + + Task GetAsync(string key, CancellationToken cancellationToken = default); + Task RemoveAsync(string key, CancellationToken cancellationToken = default); +} 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 diff --git a/Security/DependencyInjection.cs b/Security/DependencyInjection.cs new file mode 100644 index 0000000..ed16c5e --- /dev/null +++ b/Security/DependencyInjection.cs @@ -0,0 +1,57 @@ +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; + +public static class DependencyInjection +{ + 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"]!); + 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"] + }); + + 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"]!)); + + services.AddSingleton(provider => + { + var cacheService = provider.GetRequiredService(); + var accessTokenService = provider.GetRequiredService(); + var revokedTokenService = provider.GetRequiredService(); + + return new AuthService(cacheService, accessTokenService, revokedTokenService) + { + Lifetime = lifeTimeRefreshToken + }; + }); + + return services; + } +} \ No newline at end of file diff --git a/Security/Security.csproj b/Security/Security.csproj new file mode 100644 index 0000000..218d9f6 --- /dev/null +++ b/Security/Security.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + disable + enable + Winsomnia + 1.0.0-a0 + 1.0.0.0 + 1.0.0.0 + Mirea.Api.Security + $(AssemblyName) + + + + + + + + + diff --git a/Security/Services/AuthService.cs b/Security/Services/AuthService.cs new file mode 100644 index 0000000..5426532 --- /dev/null +++ b/Security/Services/AuthService.cs @@ -0,0 +1,103 @@ +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 AuthService(ICacheService cache, IAccessToken accessTokenService, IRevokedToken revokedToken) +{ + 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"; + + private Task SetAuthTokenDataToCache(string fingerprint, AuthToken data, CancellationToken cancellation) => + cache.SetAsync( + GetAuthCacheKey(fingerprint), + JsonSerializer.SerializeToUtf8Bytes(data), + slidingExpiration: Lifetime, + cancellationToken: cancellation); + + private Task RevokeAccessToken(string token) => + revokedToken.AddTokenToRevokedAsync(token, accessTokenService.GetExpireDateTime(token)); + + public async Task GenerateAuthTokensAsync(TokenRequest request, string userId, CancellationToken cancellation = default) + { + 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 + }; + } + + 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) + ?? 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() + }; + } + + 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 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 new file mode 100644 index 0000000..8673222 --- /dev/null +++ b/Security/Services/PasswordHashService.cs @@ -0,0 +1,56 @@ +using Konscious.Security.Cryptography; +using System; +using System.Text; + +namespace Mirea.Api.Security.Services; + +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 (string Salt, string Hash) HashPassword(string password) + { + var salt = GeneratorKey.GenerateBytes(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 diff --git a/Security/Services/PreAuthService.cs b/Security/Services/PreAuthService.cs new file mode 100644 index 0000000..948b997 --- /dev/null +++ b/Security/Services/PreAuthService.cs @@ -0,0 +1,64 @@ +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/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 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/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/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/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/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 + } + } +} 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/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 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 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 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 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]); + } +} diff --git a/Persistence/Contexts/Schedule/CampusDbContext.cs b/SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs similarity index 67% rename from Persistence/Contexts/Schedule/CampusDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/CampusDbContext.cs index 30a9d56..d93e3d9 100644 --- a/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/Persistence/Contexts/Schedule/DisciplineDbContext.cs b/SqlData/Persistence/Contexts/Schedule/DisciplineDbContext.cs similarity index 56% rename from Persistence/Contexts/Schedule/DisciplineDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/DisciplineDbContext.cs index d0a5612..d9ec9e1 100644 --- a/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/Persistence/Contexts/Schedule/FacultyDbContext.cs b/SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs similarity index 57% rename from Persistence/Contexts/Schedule/FacultyDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/FacultyDbContext.cs index ab6a45c..98323a6 100644 --- a/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/Persistence/Contexts/Schedule/GroupDbContext.cs b/SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs similarity index 57% rename from Persistence/Contexts/Schedule/GroupDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/GroupDbContext.cs index 5d61c70..e6bcc5e 100644 --- a/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/Persistence/Contexts/Schedule/LectureHallDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LectureHallDbContext.cs similarity index 56% rename from Persistence/Contexts/Schedule/LectureHallDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/LectureHallDbContext.cs index 67841a1..4253309 100644 --- a/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/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs similarity index 55% rename from Persistence/Contexts/Schedule/LessonAssociationDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/LessonAssociationDbContext.cs index 773bc8a..ae6be39 100644 --- a/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/Persistence/Contexts/Schedule/LessonDbContext.cs b/SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs similarity index 57% rename from Persistence/Contexts/Schedule/LessonDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/LessonDbContext.cs index d2dfcae..e727041 100644 --- a/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/Persistence/Contexts/Schedule/ProfessorDbContext.cs b/SqlData/Persistence/Contexts/Schedule/ProfessorDbContext.cs similarity index 56% rename from Persistence/Contexts/Schedule/ProfessorDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/ProfessorDbContext.cs index fcd51c6..75924ff 100644 --- a/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/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs b/SqlData/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs similarity index 56% rename from Persistence/Contexts/Schedule/SpecificWeekDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/SpecificWeekDbContext.cs index b3601bd..20bbab7 100644 --- a/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/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs b/SqlData/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs similarity index 55% rename from Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs rename to SqlData/Persistence/Contexts/Schedule/TypeOfOccupationDbContext.cs index b47ac61..ae49d35 100644 --- a/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 diff --git a/Persistence/DbInitializer.cs b/SqlData/Persistence/DbInitializer.cs similarity index 80% rename from Persistence/DbInitializer.cs rename to SqlData/Persistence/DbInitializer.cs index 87f3a9b..7bedd91 100644 --- a/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 diff --git a/SqlData/Persistence/DependencyInjection.cs b/SqlData/Persistence/DependencyInjection.cs new file mode 100644 index 0000000..f86cd87 --- /dev/null +++ b/SqlData/Persistence/DependencyInjection.cs @@ -0,0 +1,89 @@ +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, DatabaseProvider dbProvider, string connection) + { + 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(options => + { + var providerNamespace = typeof(Mark).Namespace + "." + Enum.GetName(dbProvider); + + 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()); + 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; + + DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder options) + { + return dbProvider switch + { + 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)) + }; + } + } +} \ No newline at end of file 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 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 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 diff --git a/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs similarity index 88% rename from Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs index 410d6c2..3a85a0a 100644 --- a/Persistence/EntityTypeConfigurations/Schedule/CampusConfiguration.cs +++ b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/CampusConfiguration.cs @@ -2,9 +2,9 @@ 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) { diff --git a/Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/DisciplineConfiguration.cs similarity index 96% rename from Persistence/EntityTypeConfigurations/Schedule/DisciplineConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/DisciplineConfiguration.cs index f92ac02..94356f8 100644 --- a/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/Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs similarity index 97% rename from Persistence/EntityTypeConfigurations/Schedule/FacultyConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/FacultyConfiguration.cs index d61fce5..c7cde69 100644 --- a/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/Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/GroupConfiguration.cs similarity index 97% rename from Persistence/EntityTypeConfigurations/Schedule/GroupConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/GroupConfiguration.cs index 5938bb3..e4b49d0 100644 --- a/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/Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LectureHallConfiguration.cs similarity index 97% rename from Persistence/EntityTypeConfigurations/Schedule/LectureHallConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LectureHallConfiguration.cs index 2d10805..1896d55 100644 --- a/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/Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonAssociationConfiguration.cs similarity index 98% rename from Persistence/EntityTypeConfigurations/Schedule/LessonAssociationConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonAssociationConfiguration.cs index c9ecb8c..65fb513 100644 --- a/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/Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonConfiguration.cs similarity index 98% rename from Persistence/EntityTypeConfigurations/Schedule/LessonConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/LessonConfiguration.cs index 8d9c8cd..099c7d6 100644 --- a/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/Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/ProfessorConfiguration.cs similarity index 97% rename from Persistence/EntityTypeConfigurations/Schedule/ProfessorConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/ProfessorConfiguration.cs index 7b95ab8..37352cc 100644 --- a/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/Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/SpecificWeekConfiguration.cs similarity index 97% rename from Persistence/EntityTypeConfigurations/Schedule/SpecificWeekConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/SpecificWeekConfiguration.cs index f4fc8a3..cdab80d 100644 --- a/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/Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs b/SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/TypeOfOccupationConfiguration.cs similarity index 96% rename from Persistence/EntityTypeConfigurations/Schedule/TypeOfOccupationConfiguration.cs rename to SqlData/Persistence/EntityTypeConfigurations/Sqlite/Schedule/TypeOfOccupationConfiguration.cs index 5341a5a..6d7a2da 100644 --- a/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 { 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..07ca710 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 54% rename from Persistence/UberDbContext.cs rename to SqlData/Persistence/UberDbContext.cs index ccd1877..3777ef2 100644 --- a/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