diff --git a/SchengenVisaApi/ApplicationLayer/ApplicationLayer.csproj b/SchengenVisaApi/ApplicationLayer/ApplicationLayer.csproj index bc6298d..fc0b07f 100644 --- a/SchengenVisaApi/ApplicationLayer/ApplicationLayer.csproj +++ b/SchengenVisaApi/ApplicationLayer/ApplicationLayer.csproj @@ -14,4 +14,8 @@ + + + + diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/Exceptions/IncorrectLoginDataException.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/Exceptions/IncorrectLoginDataException.cs new file mode 100644 index 0000000..d5ee16d --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/Exceptions/IncorrectLoginDataException.cs @@ -0,0 +1,4 @@ +namespace ApplicationLayer.AuthServices.LoginService.Exceptions +{ + public class IncorrectLoginDataException() : Exception("Incorrect email or password"); +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/ILoginService.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/ILoginService.cs new file mode 100644 index 0000000..b9f26c5 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/ILoginService.cs @@ -0,0 +1,12 @@ +using ApplicationLayer.AuthServices.Requests; + +namespace ApplicationLayer.AuthServices.LoginService +{ + /// Handles + public interface ILoginService + { + /// Handle + /// JWT-token + Task LoginAsync(UserLoginRequest request, CancellationToken cancellationToken); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/LoginService.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/LoginService.cs new file mode 100644 index 0000000..d0279a9 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/LoginService/LoginService.cs @@ -0,0 +1,21 @@ +using ApplicationLayer.AuthServices.LoginService.Exceptions; +using ApplicationLayer.AuthServices.NeededServices; +using ApplicationLayer.AuthServices.Requests; + +namespace ApplicationLayer.AuthServices.LoginService +{ + /// + public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService + { + async Task ILoginService.LoginAsync(UserLoginRequest request, CancellationToken cancellationToken) + { + var user = await users.FindByEmailAsync(request.Email, cancellationToken); + if (user is null || user.Password != request.Password) + { + throw new IncorrectLoginDataException(); + } + + return tokenGenerator.CreateToken(user); + } + } +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/NeededServices/ITokenGenerator.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/NeededServices/ITokenGenerator.cs new file mode 100644 index 0000000..1c9b2fa --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/NeededServices/ITokenGenerator.cs @@ -0,0 +1,9 @@ +using Domains.Users; + +namespace ApplicationLayer.AuthServices.NeededServices +{ + public interface ITokenGenerator + { + string CreateToken(User user); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/NeededServices/IUsersRepository.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/NeededServices/IUsersRepository.cs new file mode 100644 index 0000000..3f1f206 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/NeededServices/IUsersRepository.cs @@ -0,0 +1,15 @@ +using ApplicationLayer.GeneralNeededServices; +using Domains.Users; + +namespace ApplicationLayer.AuthServices.NeededServices +{ + /// Repository pattern for + public interface IUsersRepository : IGenericRepository + { + /// Find by email + /// 's email + /// Cancellation token + /// User or null if not found + Task FindByEmailAsync(string email, CancellationToken cancellationToken); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/Exceptions/UserAlreadyExistsException.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/Exceptions/UserAlreadyExistsException.cs new file mode 100644 index 0000000..bd32eb1 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/Exceptions/UserAlreadyExistsException.cs @@ -0,0 +1,6 @@ +using ApplicationLayer.AuthServices.Requests; + +namespace ApplicationLayer.AuthServices.RegisterService.Exceptions +{ + public class UserAlreadyExistsException(RegisterApplicantRequest request) : Exception($"User with email '{request.Email}' already exists"); +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/IRegisterService.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/IRegisterService.cs new file mode 100644 index 0000000..54c0e0c --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/IRegisterService.cs @@ -0,0 +1,11 @@ +using ApplicationLayer.AuthServices.Requests; + +namespace ApplicationLayer.AuthServices.RegisterService +{ + /// Handles + public interface IRegisterService + { + /// Handle + Task Register(RegisterApplicantRequest request, CancellationToken cancellationToken); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/RegisterService.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/RegisterService.cs new file mode 100644 index 0000000..83ab4cc --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/RegisterService/RegisterService.cs @@ -0,0 +1,31 @@ +using ApplicationLayer.AuthServices.NeededServices; +using ApplicationLayer.AuthServices.RegisterService.Exceptions; +using ApplicationLayer.AuthServices.Requests; +using Domains.Users; + +namespace ApplicationLayer.AuthServices.RegisterService +{ + /// + public class RegisterService(IUsersRepository users) : IRegisterService + { + async Task IRegisterService.Register(RegisterApplicantRequest request, CancellationToken cancellationToken) + { + if (await users.FindByEmailAsync(request.Email, cancellationToken) is not null) + { + throw new UserAlreadyExistsException(request); + } + + //TODO mapper + var user = new User + { + Email = request.Email, + Password = request.Password, + Role = Role.Applicant + }; + + await users.AddAsync(user, cancellationToken); + await users.SaveAsync(cancellationToken); + users.GetAllAsync(cancellationToken); + } + } +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/Requests/RegisterApplicantRequest.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/Requests/RegisterApplicantRequest.cs new file mode 100644 index 0000000..e919545 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/Requests/RegisterApplicantRequest.cs @@ -0,0 +1,4 @@ +namespace ApplicationLayer.AuthServices.Requests +{ + public record RegisterApplicantRequest(string Email, string Password); +} diff --git a/SchengenVisaApi/ApplicationLayer/AuthServices/Requests/UserLoginRequest.cs b/SchengenVisaApi/ApplicationLayer/AuthServices/Requests/UserLoginRequest.cs new file mode 100644 index 0000000..a1ed5cf --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/AuthServices/Requests/UserLoginRequest.cs @@ -0,0 +1,4 @@ +namespace ApplicationLayer.AuthServices.Requests +{ + public record UserLoginRequest(string Email, string Password); +} diff --git a/SchengenVisaApi/ApplicationLayer/Applicants/NeededServices/IApplicantsRepository.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/Applicants/NeededServices/IApplicantsRepository.cs similarity index 70% rename from SchengenVisaApi/ApplicationLayer/Applicants/NeededServices/IApplicantsRepository.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/Applicants/NeededServices/IApplicantsRepository.cs index 5fc23ae..1ac3667 100644 --- a/SchengenVisaApi/ApplicationLayer/Applicants/NeededServices/IApplicantsRepository.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/Applicants/NeededServices/IApplicantsRepository.cs @@ -1,7 +1,7 @@ using ApplicationLayer.GeneralNeededServices; using Domains.ApplicantDomain; -namespace ApplicationLayer.Applicants.NeededServices; +namespace ApplicationLayer.DataAccessingServices.Applicants.NeededServices; /// Repository pattern for -public interface IApplicantsRepository : IGenericRepository { } +public interface IApplicantsRepository : IGenericRepository; diff --git a/SchengenVisaApi/ApplicationLayer/DataAccessingServices/Locations/NeededServices/ICitiesRepository.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/Locations/NeededServices/ICitiesRepository.cs new file mode 100644 index 0000000..8c9efab --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/Locations/NeededServices/ICitiesRepository.cs @@ -0,0 +1,6 @@ +using ApplicationLayer.GeneralNeededServices; +using Domains.LocationDomain; + +namespace ApplicationLayer.DataAccessingServices.Locations.NeededServices; + +public interface ICitiesRepository : IGenericRepository; diff --git a/SchengenVisaApi/ApplicationLayer/Locations/NeededServices/ICountriesRepository.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/Locations/NeededServices/ICountriesRepository.cs similarity index 64% rename from SchengenVisaApi/ApplicationLayer/Locations/NeededServices/ICountriesRepository.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/Locations/NeededServices/ICountriesRepository.cs index e2ced02..ca86c9e 100644 --- a/SchengenVisaApi/ApplicationLayer/Locations/NeededServices/ICountriesRepository.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/Locations/NeededServices/ICountriesRepository.cs @@ -1,6 +1,6 @@ using ApplicationLayer.GeneralNeededServices; using Domains.LocationDomain; -namespace ApplicationLayer.Locations.NeededServices; +namespace ApplicationLayer.DataAccessingServices.Locations.NeededServices; -public interface ICountriesRepository : IGenericRepository { } +public interface ICountriesRepository : IGenericRepository; diff --git a/SchengenVisaApi/ApplicationLayer/VisaApplications/Handlers/IVisaApplicationsRequestHandler.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Handlers/IVisaApplicationsRequestHandler.cs similarity index 64% rename from SchengenVisaApi/ApplicationLayer/VisaApplications/Handlers/IVisaApplicationsRequestHandler.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Handlers/IVisaApplicationsRequestHandler.cs index 61905d5..7ef57b2 100644 --- a/SchengenVisaApi/ApplicationLayer/VisaApplications/Handlers/IVisaApplicationsRequestHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Handlers/IVisaApplicationsRequestHandler.cs @@ -1,7 +1,7 @@ -using ApplicationLayer.VisaApplications.Requests; +using ApplicationLayer.DataAccessingServices.VisaApplications.Requests; using Domains.VisaApplicationDomain; -namespace ApplicationLayer.VisaApplications.Handlers; +namespace ApplicationLayer.DataAccessingServices.VisaApplications.Handlers; public interface IVisaApplicationsRequestHandler { diff --git a/SchengenVisaApi/ApplicationLayer/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs similarity index 87% rename from SchengenVisaApi/ApplicationLayer/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs index 37e2b17..dc25a53 100644 --- a/SchengenVisaApi/ApplicationLayer/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs @@ -1,11 +1,11 @@ -using ApplicationLayer.Locations.NeededServices; -using ApplicationLayer.VisaApplications.Models; -using ApplicationLayer.VisaApplications.NeededServices; -using ApplicationLayer.VisaApplications.Requests; +using ApplicationLayer.DataAccessingServices.Locations.NeededServices; +using ApplicationLayer.DataAccessingServices.VisaApplications.Models; +using ApplicationLayer.DataAccessingServices.VisaApplications.NeededServices; +using ApplicationLayer.DataAccessingServices.VisaApplications.Requests; using Domains.ApplicantDomain; using Domains.VisaApplicationDomain; -namespace ApplicationLayer.VisaApplications.Handlers; +namespace ApplicationLayer.DataAccessingServices.VisaApplications.Handlers; /// Handles visa requests public class VisaApplicationRequestsHandler( @@ -64,6 +64,7 @@ public class VisaApplicationRequestsHandler( }; await applications.AddAsync(visaApplication, cancellationToken); + await applications.SaveAsync(cancellationToken); } private async Task ConvertPastVisitModelToPastVisit(PastVisitModel model, CancellationToken cancellationToken) diff --git a/SchengenVisaApi/ApplicationLayer/VisaApplications/Models/AddressModel.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/AddressModel.cs similarity index 77% rename from SchengenVisaApi/ApplicationLayer/VisaApplications/Models/AddressModel.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/AddressModel.cs index 0a36077..5e6909b 100644 --- a/SchengenVisaApi/ApplicationLayer/VisaApplications/Models/AddressModel.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/AddressModel.cs @@ -1,4 +1,4 @@ -namespace ApplicationLayer.VisaApplications.Models; +namespace ApplicationLayer.DataAccessingServices.VisaApplications.Models; public class AddressModel { diff --git a/SchengenVisaApi/ApplicationLayer/VisaApplications/Models/PastVisitModel.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/PastVisitModel.cs similarity index 83% rename from SchengenVisaApi/ApplicationLayer/VisaApplications/Models/PastVisitModel.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/PastVisitModel.cs index fe1396c..a22be4a 100644 --- a/SchengenVisaApi/ApplicationLayer/VisaApplications/Models/PastVisitModel.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/PastVisitModel.cs @@ -1,4 +1,4 @@ -namespace ApplicationLayer.VisaApplications.Models +namespace ApplicationLayer.DataAccessingServices.VisaApplications.Models { public class PastVisitModel { diff --git a/SchengenVisaApi/ApplicationLayer/VisaApplications/Models/PlaceOfWorkModel.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/PlaceOfWorkModel.cs similarity index 78% rename from SchengenVisaApi/ApplicationLayer/VisaApplications/Models/PlaceOfWorkModel.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/PlaceOfWorkModel.cs index 6e76b1b..42b6b1d 100644 --- a/SchengenVisaApi/ApplicationLayer/VisaApplications/Models/PlaceOfWorkModel.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Models/PlaceOfWorkModel.cs @@ -1,4 +1,4 @@ -namespace ApplicationLayer.VisaApplications.Models; +namespace ApplicationLayer.DataAccessingServices.VisaApplications.Models; public class PlaceOfWorkModel { diff --git a/SchengenVisaApi/ApplicationLayer/VisaApplications/NeededServices/IVisaApplicationsRepository.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/NeededServices/IVisaApplicationsRepository.cs similarity index 60% rename from SchengenVisaApi/ApplicationLayer/VisaApplications/NeededServices/IVisaApplicationsRepository.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/NeededServices/IVisaApplicationsRepository.cs index 8a8921e..2d28cd5 100644 --- a/SchengenVisaApi/ApplicationLayer/VisaApplications/NeededServices/IVisaApplicationsRepository.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/NeededServices/IVisaApplicationsRepository.cs @@ -1,6 +1,6 @@ using ApplicationLayer.GeneralNeededServices; using Domains.VisaApplicationDomain; -namespace ApplicationLayer.VisaApplications.NeededServices; +namespace ApplicationLayer.DataAccessingServices.VisaApplications.NeededServices; -public interface IVisaApplicationsRepository : IGenericRepository { } \ No newline at end of file +public interface IVisaApplicationsRepository : IGenericRepository; \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/VisaApplications/Requests/VisaApplicationCreateRequest.cs b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Requests/VisaApplicationCreateRequest.cs similarity index 83% rename from SchengenVisaApi/ApplicationLayer/VisaApplications/Requests/VisaApplicationCreateRequest.cs rename to SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Requests/VisaApplicationCreateRequest.cs index 5d95817..4a5bf1a 100644 --- a/SchengenVisaApi/ApplicationLayer/VisaApplications/Requests/VisaApplicationCreateRequest.cs +++ b/SchengenVisaApi/ApplicationLayer/DataAccessingServices/VisaApplications/Requests/VisaApplicationCreateRequest.cs @@ -1,8 +1,8 @@ -using ApplicationLayer.VisaApplications.Models; +using ApplicationLayer.DataAccessingServices.VisaApplications.Models; using Domains.ApplicantDomain; using Domains.VisaApplicationDomain; -namespace ApplicationLayer.VisaApplications.Requests; +namespace ApplicationLayer.DataAccessingServices.VisaApplications.Requests; /// Model of visa request from user public record VisaApplicationCreateRequest( diff --git a/SchengenVisaApi/ApplicationLayer/DependencyInjection.cs b/SchengenVisaApi/ApplicationLayer/DependencyInjection.cs index b5534a3..8fa5014 100644 --- a/SchengenVisaApi/ApplicationLayer/DependencyInjection.cs +++ b/SchengenVisaApi/ApplicationLayer/DependencyInjection.cs @@ -1,4 +1,6 @@ -using ApplicationLayer.VisaApplications.Handlers; +using ApplicationLayer.AuthServices.LoginService; +using ApplicationLayer.AuthServices.RegisterService; +using ApplicationLayer.DataAccessingServices.VisaApplications.Handlers; using Microsoft.Extensions.DependencyInjection; namespace ApplicationLayer; @@ -11,6 +13,9 @@ public static class DependencyInjection { services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + return services; } } diff --git a/SchengenVisaApi/ApplicationLayer/GeneralNeededServices/IDateTimeProvider.cs b/SchengenVisaApi/ApplicationLayer/GeneralNeededServices/IDateTimeProvider.cs new file mode 100644 index 0000000..29903d6 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/GeneralNeededServices/IDateTimeProvider.cs @@ -0,0 +1,8 @@ +namespace ApplicationLayer.GeneralNeededServices +{ + public interface IDateTimeProvider + { + /// Returns current date and time + DateTime Now(); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Locations/NeededServices/ICitiesRepository.cs b/SchengenVisaApi/ApplicationLayer/Locations/NeededServices/ICitiesRepository.cs deleted file mode 100644 index cfed58a..0000000 --- a/SchengenVisaApi/ApplicationLayer/Locations/NeededServices/ICitiesRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -using ApplicationLayer.GeneralNeededServices; -using Domains.LocationDomain; - -namespace ApplicationLayer.Locations.NeededServices; - -public interface ICitiesRepository : IGenericRepository { } diff --git a/SchengenVisaApi/Domains/Users/Role.cs b/SchengenVisaApi/Domains/Users/Role.cs new file mode 100644 index 0000000..38b9376 --- /dev/null +++ b/SchengenVisaApi/Domains/Users/Role.cs @@ -0,0 +1,13 @@ +namespace Domains.Users +{ + /// Role of + public enum Role + { + /// Requests visa applications + Applicant, + /// Approves or declines applications + ApprovingAuthority, + /// Manages approving authorities + Admin + } +} diff --git a/SchengenVisaApi/Domains/Users/User.cs b/SchengenVisaApi/Domains/Users/User.cs new file mode 100644 index 0000000..017a5b0 --- /dev/null +++ b/SchengenVisaApi/Domains/Users/User.cs @@ -0,0 +1,14 @@ +namespace Domains.Users +{ + public class User : IEntity + { + /// Unique Identifier of + public Guid Id { get; private set; } = Guid.NewGuid(); + + public Role Role { get; set; } + + public string Email { get; set; } = null!; + + public string Password { get; set; } = null!; + } +} diff --git a/SchengenVisaApi/Infrastructure/Auth/ServiceCollectionsExtensions.cs b/SchengenVisaApi/Infrastructure/Auth/ServiceCollectionsExtensions.cs new file mode 100644 index 0000000..1138163 --- /dev/null +++ b/SchengenVisaApi/Infrastructure/Auth/ServiceCollectionsExtensions.cs @@ -0,0 +1,24 @@ +using System.IdentityModel.Tokens.Jwt; +using ApplicationLayer.AuthServices.NeededServices; +using ApplicationLayer.GeneralNeededServices; +using Microsoft.Extensions.DependencyInjection; + +namespace Infrastructure.Auth +{ + public static class ServiceCollectionsExtensions + { + public static IServiceCollection AddTokenGenerator(this IServiceCollection services, TokenGeneratorOptions options) + { + services.AddSingleton(); + services.AddSingleton(provider => + { + var tokenHandler = provider.GetRequiredService(); + var dateTimeProvider = provider.GetRequiredService(); + + return new TokenGenerator(options, tokenHandler, dateTimeProvider); + }); + + return services; + } + } +} diff --git a/SchengenVisaApi/Infrastructure/Auth/TokenGenerator.cs b/SchengenVisaApi/Infrastructure/Auth/TokenGenerator.cs new file mode 100644 index 0000000..54363b4 --- /dev/null +++ b/SchengenVisaApi/Infrastructure/Auth/TokenGenerator.cs @@ -0,0 +1,30 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using ApplicationLayer.AuthServices.NeededServices; +using ApplicationLayer.GeneralNeededServices; +using Domains.Users; + +namespace Infrastructure.Auth +{ + public class TokenGenerator(TokenGeneratorOptions options, JwtSecurityTokenHandler tokenHandler, IDateTimeProvider dateTimeProvider) + : ITokenGenerator + { + public string CreateToken(User user) + { + var claims = new List + { + new(ClaimTypes.Role, user.Role.ToString()), + new(ClaimTypes.Email, user.Email) + }; + + var token = new JwtSecurityToken( + issuer: options.Issuer, + audience: options.Audience, + expires: dateTimeProvider.Now().Add(options.ValidTime), + signingCredentials: options.Credentials, + claims: claims); + + return tokenHandler.WriteToken(token); + } + } +} diff --git a/SchengenVisaApi/Infrastructure/Auth/TokenGeneratorOptions.cs b/SchengenVisaApi/Infrastructure/Auth/TokenGeneratorOptions.cs new file mode 100644 index 0000000..b12b8b2 --- /dev/null +++ b/SchengenVisaApi/Infrastructure/Auth/TokenGeneratorOptions.cs @@ -0,0 +1,6 @@ +using Microsoft.IdentityModel.Tokens; + +namespace Infrastructure.Auth +{ + public record TokenGeneratorOptions(string Issuer, string Audience, TimeSpan ValidTime, SigningCredentials Credentials); +} diff --git a/SchengenVisaApi/Infrastructure/Common/DateTimeProvider.cs b/SchengenVisaApi/Infrastructure/Common/DateTimeProvider.cs new file mode 100644 index 0000000..752872b --- /dev/null +++ b/SchengenVisaApi/Infrastructure/Common/DateTimeProvider.cs @@ -0,0 +1,10 @@ +using ApplicationLayer.GeneralNeededServices; + +namespace Infrastructure.Common +{ + /// Implements + public class DateTimeProvider : IDateTimeProvider + { + DateTime IDateTimeProvider.Now() => DateTime.Now; + } +} diff --git a/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/ApplicantsRepository.cs b/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/ApplicantsRepository.cs index 0885dbe..aaf0ece 100644 --- a/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/ApplicantsRepository.cs +++ b/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/ApplicantsRepository.cs @@ -1,4 +1,4 @@ -using ApplicationLayer.Applicants.NeededServices; +using ApplicationLayer.DataAccessingServices.Applicants.NeededServices; using Domains.ApplicantDomain; using Infrastructure.Database.Generic; using Microsoft.EntityFrameworkCore; diff --git a/SchengenVisaApi/Infrastructure/Database/Locations/Repositories/Cities/CitiesRepository.cs b/SchengenVisaApi/Infrastructure/Database/Locations/Repositories/Cities/CitiesRepository.cs index 75a1302..953a0d6 100644 --- a/SchengenVisaApi/Infrastructure/Database/Locations/Repositories/Cities/CitiesRepository.cs +++ b/SchengenVisaApi/Infrastructure/Database/Locations/Repositories/Cities/CitiesRepository.cs @@ -1,4 +1,4 @@ -using ApplicationLayer.Locations.NeededServices; +using ApplicationLayer.DataAccessingServices.Locations.NeededServices; using Domains.LocationDomain; using Infrastructure.Database.Generic; using Microsoft.EntityFrameworkCore; diff --git a/SchengenVisaApi/Infrastructure/Database/Locations/Repositories/Countries/CountriesRepository.cs b/SchengenVisaApi/Infrastructure/Database/Locations/Repositories/Countries/CountriesRepository.cs index b27908d..e17f5d8 100644 --- a/SchengenVisaApi/Infrastructure/Database/Locations/Repositories/Countries/CountriesRepository.cs +++ b/SchengenVisaApi/Infrastructure/Database/Locations/Repositories/Countries/CountriesRepository.cs @@ -1,4 +1,4 @@ -using ApplicationLayer.Locations.NeededServices; +using ApplicationLayer.DataAccessingServices.Locations.NeededServices; using Domains.LocationDomain; using Infrastructure.Database.Generic; using Microsoft.EntityFrameworkCore; diff --git a/SchengenVisaApi/Infrastructure/Database/Users/Configuration/UserConfiguration.cs b/SchengenVisaApi/Infrastructure/Database/Users/Configuration/UserConfiguration.cs new file mode 100644 index 0000000..84e47c7 --- /dev/null +++ b/SchengenVisaApi/Infrastructure/Database/Users/Configuration/UserConfiguration.cs @@ -0,0 +1,22 @@ +using Domains.Users; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Database.Users.Configuration +{ + public class UserConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder entity) + { + entity.Property(u => u.Email) + .IsUnicode(false) + .HasMaxLength(254); + + entity.HasIndex(u => u.Email).IsUnique(); + + entity.Property(u => u.Password) + .IsUnicode(false) + .HasMaxLength(50); + } + } +} diff --git a/SchengenVisaApi/Infrastructure/Database/Users/Repositories/UsersRepository.cs b/SchengenVisaApi/Infrastructure/Database/Users/Repositories/UsersRepository.cs new file mode 100644 index 0000000..665fbc7 --- /dev/null +++ b/SchengenVisaApi/Infrastructure/Database/Users/Repositories/UsersRepository.cs @@ -0,0 +1,17 @@ +using ApplicationLayer.AuthServices.NeededServices; +using Domains.Users; +using Infrastructure.Database.Generic; +using Microsoft.EntityFrameworkCore; + +namespace Infrastructure.Database.Users.Repositories +{ + /// + public class UsersRepository(IGenericReader reader, IGenericWriter writer, IUnitOfWork unitOfWork) + : GenericRepository(reader, writer, unitOfWork), IUsersRepository + { + async Task IUsersRepository.FindByEmailAsync(string email, CancellationToken cancellationToken) + { + return await LoadDomain().SingleOrDefaultAsync(u => u.Email == email, cancellationToken); + } + } +} diff --git a/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs b/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs index e213453..a287b82 100644 --- a/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs +++ b/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs @@ -1,4 +1,4 @@ -using ApplicationLayer.VisaApplications.NeededServices; +using ApplicationLayer.DataAccessingServices.VisaApplications.NeededServices; using Domains.VisaApplicationDomain; using Infrastructure.Database.Generic; using Microsoft.EntityFrameworkCore; diff --git a/SchengenVisaApi/Infrastructure/DependencyInjection.cs b/SchengenVisaApi/Infrastructure/DependencyInjection.cs index 6f59149..ac1e261 100644 --- a/SchengenVisaApi/Infrastructure/DependencyInjection.cs +++ b/SchengenVisaApi/Infrastructure/DependencyInjection.cs @@ -1,13 +1,19 @@ -using ApplicationLayer.Applicants.NeededServices; -using ApplicationLayer.Locations.NeededServices; -using ApplicationLayer.VisaApplications.NeededServices; +using ApplicationLayer.AuthServices.NeededServices; +using ApplicationLayer.DataAccessingServices.Applicants.NeededServices; +using ApplicationLayer.DataAccessingServices.Locations.NeededServices; +using ApplicationLayer.DataAccessingServices.VisaApplications.NeededServices; +using ApplicationLayer.GeneralNeededServices; +using Infrastructure.Auth; +using Infrastructure.Common; using Infrastructure.Database; using Infrastructure.Database.Applicants.Repositories; using Infrastructure.Database.Generic; using Infrastructure.Database.Locations.Repositories.Cities; using Infrastructure.Database.Locations.Repositories.Countries; +using Infrastructure.Database.Users.Repositories; using Infrastructure.Database.VisaApplications.Repositories; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using DbContext = Infrastructure.Database.DbContext; @@ -17,11 +23,14 @@ namespace Infrastructure; public static class DependencyInjection { /// Add services needed for Infrastructure layer - public static IServiceCollection AddInfrastructure(this IServiceCollection services) + public static IServiceCollection AddInfrastructure(this IServiceCollection services, + IConfigurationManager configurationManager, + bool isDevelopment) { - //TODO строка подключения + var databaseName = isDevelopment ? "developmentDB" : "normal'naya database"; + services.AddDbContextFactory(opts => - opts.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=visadb;Integrated Security=True;")); + opts.UseSqlServer(configurationManager.GetConnectionString(databaseName))); services.AddScoped(serviceProvider => serviceProvider.GetRequiredService()); services.AddScoped(serviceProvider => serviceProvider.GetRequiredService()); @@ -31,6 +40,9 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + + services.AddSingleton(); return services; } diff --git a/SchengenVisaApi/Infrastructure/Infrastructure.csproj b/SchengenVisaApi/Infrastructure/Infrastructure.csproj index 41497f6..1bca54d 100644 --- a/SchengenVisaApi/Infrastructure/Infrastructure.csproj +++ b/SchengenVisaApi/Infrastructure/Infrastructure.csproj @@ -11,6 +11,7 @@ + diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs new file mode 100644 index 0000000..237b64c --- /dev/null +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs @@ -0,0 +1,27 @@ +using ApplicationLayer.AuthServices.LoginService; +using ApplicationLayer.AuthServices.RegisterService; +using ApplicationLayer.AuthServices.Requests; +using Microsoft.AspNetCore.Identity.Data; +using Microsoft.AspNetCore.Mvc; + +namespace SchengenVisaApi.Controllers +{ + [ApiController] + [Route("auth")] + public class UsersController(IRegisterService registerService, ILoginService loginService) : Controller + { + [HttpPost] + public async Task Register(RegisterApplicantRequest request, CancellationToken cancellationToken) + { + await registerService.Register(request, cancellationToken); + return Created(); + } + + [HttpGet] + public async Task Login(string email, string password, CancellationToken cancellationToken) + { + var result = await loginService.LoginAsync(new UserLoginRequest(email, password), cancellationToken); + return Ok(result); + } + } +} diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs index 3eca65b..80cb645 100644 --- a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs @@ -1,5 +1,5 @@ -using ApplicationLayer.VisaApplications.Handlers; -using ApplicationLayer.VisaApplications.Requests; +using ApplicationLayer.DataAccessingServices.VisaApplications.Handlers; +using ApplicationLayer.DataAccessingServices.VisaApplications.Requests; using Microsoft.AspNetCore.Mvc; namespace SchengenVisaApi.Controllers; diff --git a/SchengenVisaApi/SchengenVisaApi/DependencyInjection.cs b/SchengenVisaApi/SchengenVisaApi/DependencyInjection.cs index 9e8a820..1c27bbc 100644 --- a/SchengenVisaApi/SchengenVisaApi/DependencyInjection.cs +++ b/SchengenVisaApi/SchengenVisaApi/DependencyInjection.cs @@ -1,6 +1,10 @@ using System.Reflection; +using System.Text; using ApplicationLayer; using Infrastructure; +using Infrastructure.Auth; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; namespace SchengenVisaApi; @@ -8,21 +12,61 @@ namespace SchengenVisaApi; public static class DependencyInjection { /// Add needed services - public static IServiceCollection RegisterServices(this IServiceCollection services) + public static void RegisterServices(this WebApplicationBuilder builder) { - services - .AddInfrastructure() + var config = builder.Configuration; + var environment = builder.Environment; + + builder.Services + .AddInfrastructure(config, environment.IsDevelopment()) .AddApplicationLayer() - .AddPresentation(); + .AddAuth(config) + .AddPresentation(environment); + } + + /// Add services needed for Presentation layer + private static void AddPresentation(this IServiceCollection services, + IWebHostEnvironment environment) + { + if (environment.IsDevelopment()) + { + services.AddSwagger(); + } + + services.AddControllers(); + } + + /// Adds authentication, authorization and token generator + private static IServiceCollection AddAuth(this IServiceCollection services, IConfigurationManager configurationManager) + { + var parameters = new TokenValidationParameters + { + ValidIssuer = configurationManager["JwtSettings:Issuer"], + ValidAudience = configurationManager["JwtSettings:Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationManager["JwtSettings:Key"]!)), + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true + }; + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(opts => opts.TokenValidationParameters = parameters); + services.AddAuthorization(); + + services.AddTokenGenerator(new TokenGeneratorOptions( + Issuer: parameters.ValidIssuer!, + Audience: parameters.ValidAudience!, + Credentials: new SigningCredentials(parameters.IssuerSigningKey, SecurityAlgorithms.HmacSha256), + ValidTime: TimeSpan.FromMinutes(30) + )); return services; } - /// Add services needed for Presentation layer - private static void AddPresentation(this IServiceCollection services) + /// Add swagger + private static void AddSwagger(this IServiceCollection services) { - services.AddControllers(); - services.AddEndpointsApiExplorer(); services.AddSwaggerGen(options => { var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; diff --git a/SchengenVisaApi/SchengenVisaApi/Program.cs b/SchengenVisaApi/SchengenVisaApi/Program.cs index f800273..dfb3da1 100644 --- a/SchengenVisaApi/SchengenVisaApi/Program.cs +++ b/SchengenVisaApi/SchengenVisaApi/Program.cs @@ -1,11 +1,12 @@ namespace SchengenVisaApi; + #pragma warning disable CS1591 public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); - builder.Services.RegisterServices(); + builder.RegisterServices(); var app = builder.Build(); app.ConfigurePipelineRequest(); @@ -13,4 +14,4 @@ public class Program app.Run(); } } -#pragma warning restore CS1591 \ No newline at end of file +#pragma warning restore CS1591 diff --git a/SchengenVisaApi/SchengenVisaApi/RequestPipeline.cs b/SchengenVisaApi/SchengenVisaApi/RequestPipeline.cs index cca11d1..2e5c653 100644 --- a/SchengenVisaApi/SchengenVisaApi/RequestPipeline.cs +++ b/SchengenVisaApi/SchengenVisaApi/RequestPipeline.cs @@ -11,8 +11,11 @@ public static class PipelineRequest app.UseHttpsRedirection(); + app.UseAuthentication() + .UseAuthorization(); + app.MapControllers(); return app; } -} \ No newline at end of file +} diff --git a/SchengenVisaApi/SchengenVisaApi/appsettings.json b/SchengenVisaApi/SchengenVisaApi/appsettings.json index 10f68b8..50c2c3b 100644 --- a/SchengenVisaApi/SchengenVisaApi/appsettings.json +++ b/SchengenVisaApi/SchengenVisaApi/appsettings.json @@ -5,5 +5,16 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + + "ConnectionStrings": { + "developmentDB": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=visadb;Integrated Security=True;", + "normal'naya db": "" + }, + + "JwtSettings": { + "Issuer":"visaAPI", + "Audience":"visaClient", + "Key": "frsjiajfapojrpwauflakpiowaidoaplakrf" + } }