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"
+ }
}