Вытащил солюшен на уровень выше, чтобы прощё было дотнетить
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-10-05 14:32:06 +03:00
parent fa87a56ad1
commit aae4b28089
242 changed files with 159 additions and 159 deletions

View File

@@ -0,0 +1,27 @@
using Domains;
using Domains.ApplicantDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.Applicants.Configuration;
public static class AddressConfiguration<T> where T : class, IEntity
{
public static void Configure(OwnedNavigationBuilder<T, Address> entity)
{
entity.Property(a => a.Country)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CountryNameLength);
entity.Property(a => a.City)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CityNameLength);
entity.Property(a => a.Street)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.StreetNameLength);
entity.Property(a => a.Building)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.BuildingNumberLength);
}
}

View File

@@ -0,0 +1,40 @@
using Domains;
using Domains.ApplicantDomain;
using Domains.Users;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.Applicants.Configuration;
public class ApplicantConfiguration : IEntityTypeConfiguration<Applicant>
{
public void Configure(EntityTypeBuilder<Applicant> entity)
{
entity.OwnsOne(a => a.Name, NameConfiguration<Applicant>.Configure);
entity.OwnsOne(a => a.FatherName, NameConfiguration<Applicant>.Configure);
entity.OwnsOne(a => a.MotherName, NameConfiguration<Applicant>.Configure);
entity.OwnsOne(a => a.Passport, PassportConfiguration<Applicant>.Configure);
entity.HasOne<User>().WithOne().HasForeignKey<Applicant>(a => a.UserId);
entity.Property(a => a.Citizenship)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CitizenshipLength);
entity.Property(a => a.CitizenshipByBirth)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CitizenshipLength);
entity.Property(a => a.CountryOfBirth)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CountryNameLength);
entity.Property(a => a.CityOfBirth)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CityNameLength);
entity.Property(a => a.JobTitle)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.JobTitleLength);
}
}

View File

@@ -0,0 +1,23 @@
using Domains;
using Domains.ApplicantDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.Applicants.Configuration;
public static class NameConfiguration<T> where T : class, IEntity
{
public static void Configure(OwnedNavigationBuilder<T, Name> entity)
{
entity.Property(p => p.FirstName)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.NameLength);
entity.Property(p => p.Surname)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.NameLength);
entity.Property(p => p.Patronymic)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.NameLength);
}
}

View File

@@ -0,0 +1,19 @@
using Domains;
using Domains.ApplicantDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.Applicants.Configuration;
public static class PassportConfiguration<T> where T : class, IEntity
{
public static void Configure(OwnedNavigationBuilder<T, Passport> entity)
{
entity.Property(p => p.Number)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.PassportNumberLength);
entity.Property(p => p.Issuer)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.IssuerNameLength);
}
}

View File

@@ -0,0 +1,22 @@
using Domains;
using Domains.ApplicantDomain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.Applicants.Configuration;
public class PlaceOfWorkConfiguration : IEntityTypeConfiguration<PlaceOfWork>
{
public void Configure(EntityTypeBuilder<PlaceOfWork> entity)
{
entity.OwnsOne(p => p.Address, AddressConfiguration<PlaceOfWork>.Configure);
entity.Property(p => p.Name)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.PlaceOfWorkNameLength);
entity.Property(p => p.PhoneNum)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.PhoneNumberLength);
}
}

View File

@@ -0,0 +1,39 @@
using ApplicationLayer.Services.Applicants.NeededServices;
using Domains.ApplicantDomain;
using Infrastructure.Database.Applicants.Repositories.Exceptions;
using Infrastructure.Database.Generic;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Database.Applicants.Repositories;
/// Repository pattern for <see cref="Applicant"/>
/// <param name="reader"><inheritdoc cref="IGenericReader"/></param>
/// <param name="writer"><inheritdoc cref="IGenericWriter"/></param>
public sealed class ApplicantsRepository(IGenericReader reader, IGenericWriter writer)
: GenericRepository<Applicant>(reader, writer), IApplicantsRepository
{
protected override IQueryable<Applicant> LoadDomain()
{
return base.LoadDomain()
.Include(a => a.PlaceOfWork);
}
/// <inheritdoc cref="IApplicantsRepository.FindByUserIdAsync"/>
public async Task<Applicant> FindByUserIdAsync(Guid userId, CancellationToken cancellationToken)
{
var result = await LoadDomain().SingleOrDefaultAsync(a => a.UserId == userId, cancellationToken);
return result ?? throw new ApplicantNotFoundByUserIdException();
}
async Task<Guid> IApplicantsRepository.GetApplicantIdByUserId(Guid userId, CancellationToken cancellationToken)
{
var result = await base.LoadDomain().SingleOrDefaultAsync(a => a.UserId == userId, cancellationToken);
return result?.Id ?? throw new ApplicantNotFoundByUserIdException();
}
async Task<bool> IApplicantsRepository.IsApplicantNonResidentByUserId(Guid userId, CancellationToken cancellationToken)
{
var applicant = await FindByUserIdAsync(userId, cancellationToken);
return applicant.IsNonResident;
}
}

View File

@@ -0,0 +1,5 @@
using ApplicationLayer.GeneralExceptions;
namespace Infrastructure.Database.Applicants.Repositories.Exceptions;
public class ApplicantNotFoundByUserIdException() : EntityNotFoundException("Applicant not found.");

View File

@@ -0,0 +1,39 @@
using System.Reflection;
using ApplicationLayer.InfrastructureServicesInterfaces;
using Infrastructure.Database.Generic;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Database;
public class DatabaseContext(DbContextOptions<DatabaseContext> opts) : DbContext(opts), IGenericWriter, IGenericReader, IUnitOfWork
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
async Task IGenericWriter.AddAsync<T>(T entity, CancellationToken cancellationToken)
{
await AddAsync(entity, cancellationToken);
}
void IGenericWriter.Update<T>(T entity)
{
Update(entity);
}
void IGenericWriter.Remove<T>(T entity)
{
Remove(entity);
}
IQueryable<T> IGenericReader.GetAll<T>()
{
return Set<T>();
}
async Task IUnitOfWork.SaveAsync(CancellationToken cancellationToken)
{
await SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,48 @@
using ApplicationLayer.GeneralExceptions;
using ApplicationLayer.InfrastructureServicesInterfaces;
using Domains;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Database.Generic;
/// Generic repository pattern
/// <param name="writer"><inheritdoc cref="IGenericWriter"/></param>
/// <typeparam name="T">Type of entity</typeparam>
/// <remarks>Should be inherited to create typed repositories</remarks>
public abstract class GenericRepository<T>(IGenericReader reader, IGenericWriter writer) : IGenericRepository<T>
where T : class, IEntity
{
/// <inheritdoc cref="IGenericRepository{T}.GetAllAsync"/>
public async Task<List<T>> GetAllAsync(CancellationToken cancellationToken)
=> await LoadDomain().ToListAsync(cancellationToken);
/// <inheritdoc cref="IGenericRepository{T}.GetByIdAsync"/>
public async Task<T> GetByIdAsync(Guid id, CancellationToken cancellationToken)
{
var result = await LoadDomain().SingleOrDefaultAsync(a => a.Id == id, cancellationToken);
return result ?? throw new EntityNotFoundByIdException<T>(id);
}
/// <inheritdoc cref="IGenericRepository{T}.AddAsync"/>
public async Task AddAsync(T entity, CancellationToken cancellationToken)
=> await writer.AddAsync(entity, cancellationToken);
/// <inheritdoc cref="IGenericRepository{T}.UpdateAsync"/>
public async Task UpdateAsync(T entity, CancellationToken cancellationToken)
{
await GetByIdAsync(entity.Id, cancellationToken);
writer.Update(entity);
}
/// <inheritdoc cref="IGenericRepository{T}.Remove"/>
public void Remove(T entity)
{
writer.Remove(entity);
}
/// Should be overriden to load navigation properties of entity
protected virtual IQueryable<T> LoadDomain()
{
return reader.GetAll<T>();
}
}

View File

@@ -0,0 +1,11 @@
using Domains;
namespace Infrastructure.Database.Generic;
/// Reads from data storage
public interface IGenericReader
{
/// Get all entities of type T stored in storage
/// <typeparam name="T">Entity type to seek in storage</typeparam>
IQueryable<T> GetAll<T>() where T : class, IEntity;
}

View File

@@ -0,0 +1,25 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using Domains;
namespace Infrastructure.Database.Generic;
/// Writes data to data storage
/// <remarks><see cref="IUnitOfWork"/> should be used to save changes</remarks>
public interface IGenericWriter
{
/// Add entity to data storage
/// <param name="entity">Entity to add</param>
/// <param name="cancellationToken">Cancellation Token</param>
/// <typeparam name="T">Entity type</typeparam>
Task AddAsync<T>(T entity, CancellationToken cancellationToken) where T : class, IEntity;
/// Update entity in data storage
/// <param name="entity">Entity to update</param>
/// <typeparam name="T">Entity type</typeparam>
void Update<T>(T entity) where T : class, IEntity;
/// Remove entity from data storage
/// <param name="entity">Entity to remove</param>
/// <typeparam name="T">Entity type</typeparam>
void Remove<T>(T entity) where T : class, IEntity;
}

View File

@@ -0,0 +1,22 @@
using Domains;
using Domains.Users;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.Users.Configuration;
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> entity)
{
entity.Property(u => u.Email)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.EmailLength);
entity.HasIndex(u => u.Email).IsUnique();
entity.Property(u => u.Password)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.PasswordLength);
}
}

View File

@@ -0,0 +1,21 @@
using ApplicationLayer.Services.AuthServices.NeededServices;
using Domains.Users;
using Infrastructure.Database.Generic;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Database.Users.Repositories;
/// <inheritdoc cref="IUsersRepository"/>
public class UsersRepository(IGenericReader reader, IGenericWriter writer)
: GenericRepository<User>(reader, writer), IUsersRepository
{
async Task<User?> IUsersRepository.FindByEmailAsync(string email, CancellationToken cancellationToken)
{
return await LoadDomain().SingleOrDefaultAsync(u => u.Email == email, cancellationToken);
}
async Task<List<User>> IUsersRepository.GetAllOfRoleAsync(Role role, CancellationToken cancellationToken)
{
return await LoadDomain().Where(u => u.Role == role).ToListAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,15 @@
using Domains;
using Domains.VisaApplicationDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.VisaApplications.Configuration;
public static class PastVisaConfiguration<T> where T : class, IEntity
{
public static void Configure(OwnedNavigationBuilder<T, PastVisa> entity)
{
entity.Property(p => p.Name)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.VisaNameLength);
}
}

View File

@@ -0,0 +1,15 @@
using Domains;
using Domains.VisaApplicationDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.VisaApplications.Configuration;
public static class PastVisitConfiguration<T> where T : class, IEntity
{
public static void Configure(OwnedNavigationBuilder<T, PastVisit> entity)
{
entity.Property(pv => pv.DestinationCountry)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CountryNameLength);
}
}

View File

@@ -0,0 +1,15 @@
using Domains;
using Domains.VisaApplicationDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.VisaApplications.Configuration;
public static class PermissionToDestCountryConfiguration<T> where T : class, IEntity
{
public static void Configure(OwnedNavigationBuilder<T, PermissionToDestCountry> entity)
{
entity.Property(p => p.Issuer)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.IssuerNameLength);
}
}

View File

@@ -0,0 +1,15 @@
using Domains;
using Domains.VisaApplicationDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.VisaApplications.Configuration;
public static class ReentryPermitConfiguration<T> where T : class, IEntity
{
public static void Configure(OwnedNavigationBuilder<T, ReentryPermit> entity)
{
entity.Property(p => p.Number)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.ReentryPermitNumberLength);
}
}

View File

@@ -0,0 +1,27 @@
using Domains;
using Domains.ApplicantDomain;
using Domains.VisaApplicationDomain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.VisaApplications.Configuration;
public class VisaApplicationConfiguration : IEntityTypeConfiguration<VisaApplication>
{
public void Configure(EntityTypeBuilder<VisaApplication> entity)
{
entity.OwnsOne(va => va.ReentryPermit, ReentryPermitConfiguration<VisaApplication>.Configure);
entity.OwnsOne(va => va.PermissionToDestCountry, PermissionToDestCountryConfiguration<VisaApplication>.Configure);
entity.OwnsMany(va => va.PastVisits, PastVisitConfiguration<VisaApplication>.Configure);
entity.OwnsMany(va => va.PastVisas, PastVisaConfiguration<VisaApplication>.Configure);
entity.Property(va => va.DestinationCountry)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CountryNameLength);
entity.HasOne<Applicant>()
.WithMany()
.HasForeignKey(va => va.ApplicantId)
.IsRequired();
}
}

View File

@@ -0,0 +1,6 @@
using ApplicationLayer.GeneralExceptions;
namespace Infrastructure.Database.VisaApplications.Repositories.Exceptions;
public class ApplicationNotFoundByApplicantAndApplicationIdException(Guid applicationId)
: EntityNotFoundException($"Application with id {applicationId} not found for authenticated user");

View File

@@ -0,0 +1,36 @@
using ApplicationLayer.Services.VisaApplications.NeededServices;
using Domains.VisaApplicationDomain;
using Infrastructure.Database.Generic;
using Infrastructure.Database.VisaApplications.Repositories.Exceptions;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Database.VisaApplications.Repositories;
public sealed class VisaApplicationsRepository(IGenericReader reader, IGenericWriter writer)
: GenericRepository<VisaApplication>(reader, writer), IVisaApplicationsRepository
{
protected override IQueryable<VisaApplication> LoadDomain()
=> base.LoadDomain()
.Include(va => va.PastVisas)
.Include(va => va.PastVisits);
async Task<List<VisaApplication>> IVisaApplicationsRepository.GetOfApplicantAsync(Guid applicantId, CancellationToken cancellationToken)
=> await LoadDomain().Where(va => va.ApplicantId == applicantId).ToListAsync(cancellationToken);
async Task<VisaApplication> IVisaApplicationsRepository.GetByApplicantAndApplicationIdAsync(
Guid applicantId,
Guid applicationId,
CancellationToken cancellationToken)
{
var result = await LoadDomain()
.SingleOrDefaultAsync(va => va.Id == applicationId && va.ApplicantId == applicantId, cancellationToken);
return result ?? throw new ApplicationNotFoundByApplicantAndApplicationIdException(applicationId);
}
async Task<List<VisaApplication>> IVisaApplicationsRepository.GetPendingApplicationsAsync(CancellationToken cancellationToken)
{
var result = LoadDomain().Where(va => va.Status == ApplicationStatus.Pending);
return await result.ToListAsync(cancellationToken);
}
}