finalizare 1.0

This commit is contained in:
andrei-mihnea-cerbu
2024-05-21 12:10:53 +03:00
parent f7795f7519
commit 1cc1d34003
11268 changed files with 2102399 additions and 10909 deletions
@@ -0,0 +1,43 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Infrastructure.Services.Authentication;
public class ApiKeyAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IConfiguration configuration)
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock)
{
private const string ApiKeyHeaderName = "ApiKey";
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKeyHeaderValues))
return Task.FromResult(AuthenticateResult.NoResult());
var providedApiKey = apiKeyHeaderValues.FirstOrDefault();
if (string.IsNullOrWhiteSpace(providedApiKey)) return Task.FromResult(AuthenticateResult.NoResult());
var configuredApiKey = configuration.GetValue<string>("ApiKey");
if (configuredApiKey.Equals(providedApiKey))
{
var claims = new[] { new Claim(ClaimTypes.Name, "ApiKeyUser") };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("Invalid API Key provided."));
}
}
@@ -5,22 +5,16 @@ using Application.Services.Jwt;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
public class JwtService : IJwtService
namespace Infrastructure.Services.Authentication;
public class JwtService(IConfiguration configuration) : IJwtService
{
private readonly string _audience;
private readonly double _expiryMinutes;
private readonly string _issuer;
private readonly string _secretKey;
private readonly string? _audience = configuration["Jwt:Audience"];
private readonly double _expiryMinutes = double.Parse(configuration["Jwt:ExpirationTime"]);
private readonly string? _issuer = configuration["Jwt:Issuer"];
private readonly string? _secretKey = configuration["Jwt:SecretKey"];
public JwtService(IConfiguration configuration)
{
_secretKey = configuration["Jwt:SecretKey"];
_issuer = configuration["Jwt:Issuer"];
_audience = configuration["Jwt:Audience"];
_expiryMinutes = double.Parse(configuration["Jwt:ExpirationTime"]);
}
public string GenerateJwtToken(string email)
public string GenerateJwtToken(Guid userId, string roleName, string userName)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_secretKey);
@@ -28,7 +22,9 @@ public class JwtService : IJwtService
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Email, email)
new Claim(ClaimTypes.NameIdentifier, userId.ToString()), // User ID as GUID
new Claim(ClaimTypes.Role, roleName), // User role as a readable string
new Claim(ClaimTypes.Name, userName) // User's name
}),
Expires = DateTime.UtcNow.AddMinutes(_expiryMinutes),
Issuer = _issuer,
@@ -41,6 +37,7 @@ public class JwtService : IJwtService
return tokenHandler.WriteToken(token);
}
public bool ValidateJwtToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
@@ -74,12 +71,20 @@ public class JwtService : IJwtService
var principal = ValidateTokenAndGetPrincipal(token);
if (principal == null) throw new SecurityTokenException("Invalid token.");
var emailClaim = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
if (emailClaim == null) throw new SecurityTokenException("Token does not contain an email claim.");
var idClaim = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (idClaim == null || !Guid.TryParse(idClaim.Value, out var id))
throw new SecurityTokenException("Token does not contain a valid ID claim.");
return GenerateJwtToken(emailClaim.Value);
var roleClaim = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role);
var role = roleClaim != null ? roleClaim.Value : "Anonymous";
var nameClaim = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name);
var name = nameClaim != null ? nameClaim.Value : "Unknown";
return GenerateJwtToken(id, role, name);
}
private ClaimsPrincipal ValidateTokenAndGetPrincipal(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
@@ -1,26 +1,19 @@
using Application.Services.Email;
using System.Net;
using System.Net.Mail;
using Application.Services.Email;
using Microsoft.Extensions.Options;
namespace Infrastructure.Services.Email;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
public class EmailService : IEmailService
public class EmailService(IOptions<SmtpSettings> smtpSettings) : IEmailService
{
private readonly SmtpSettings _smtpSettings;
public EmailService(IOptions<SmtpSettings> smtpSettings)
{
_smtpSettings = smtpSettings.Value;
}
private readonly SmtpSettings _smtpSettings = smtpSettings.Value;
public async Task SendEmailAsync(string to, string subject, string body)
{
using (var client = new SmtpClient(_smtpSettings.Host, _smtpSettings.Port))
{
client.EnableSsl = _smtpSettings.EnableSSL;
client.EnableSsl = _smtpSettings.EnableSsl;
client.Credentials = new NetworkCredential(_smtpSettings.UserName, _smtpSettings.Password);
var mailMessage = new MailMessage
@@ -36,7 +29,7 @@ public class EmailService : IEmailService
}
}
public string GenerateCredentialsEmailBody(string email, string password)
public string GenerateCredentialsEmailBody(string name, string email, string password)
{
var template = @"
<html>
@@ -49,21 +42,32 @@ public class EmailService : IEmailService
</body>
</html>";
return template.Replace("{email}", email).Replace("{password}", password);
return template.Replace("{name}", name).Replace("{email}", email).Replace("{password}", password);
}
public string GenerateResetCredentialsEmailBody(string email, string password)
public string GenerateResetCredentialsEmailBody(string name, string email, string password)
{
var template = @"
<html>
<body>
<h1>Password Reset Successful</h1>
<p>Hello {name},
<p>Your password has been successfully reset. You can now log in to your HealthcareManager account using your new password.</p>
<p>If you did not request a password reset, please contact our support team immediately.</p>
<p>For security reasons, it's recommended to keep your password confidential and to change it regularly.</p>
</body>
</html>";
return template.Replace("{email}", email).Replace("{password}", password);
return template.Replace("{name}", name).Replace("{email}", email).Replace("{password}", password);
}
public string GetSuccessfulRegistrationSubject()
{
return "Welcome to HealthcareManager!";
}
public string GetSuccessfulPasswordResetSubject()
{
return "Password reset successfully!";
}
}
@@ -6,14 +6,12 @@ namespace Infrastructure.Services.HashingAlgorithms;
public class HashingAlgorithms : IHashingAlgorithms
{
public string? SHA256Algorithm(string? password)
public string? Sha256Algorithm(string? password)
{
if (password == null) return null;
using (var sha256 = SHA256.Create())
{
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
return Convert.ToBase64String(bytes);
}
using var sha256 = SHA256.Create();
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
return Convert.ToBase64String(bytes);
}
}
@@ -1,46 +1,36 @@
using Application.Endpoints.Appointments;
using Application.Services.Database.MongoDB;
using Core.Entities;
using Application.Services.Database.MongoDB;
using Domain.Entities;
using MongoDB.Driver;
using Application.Endpoints.Appointments;
namespace Infrastructure.Services.MongoDB;
public class AppointmentsMongoDbService : MongoDbService, IAppointmentsMongoDbService
public class AppointmentsMongoDbService(string connectionString, string databaseName, string collectionName)
: MongoDbService(connectionString, databaseName, collectionName), IAppointmentsMongoDbService
{
public AppointmentsMongoDbService(string connectionString, string databaseName, string collectionName)
: base(connectionString, databaseName, collectionName)
public async Task<bool> IsAppointmentUnique(AppointmentInformation request, CancellationToken token)
{
}
var criteria = new List<(string FieldName, string Value)>
{
("DoctorId", request.DoctorId.ToString()),
("PatientId", request.PatientId.ToString())
};
public async Task<bool> IsAppointmentUnique(AppointmentManagementDto dto)
var appointments = await FindAsync<Appointment>(criteria, token);
return appointments.All(a => !a.AppointmentsList.Any(dt => dt == request.Appointment));
}
public async Task<bool> DoesAppointmentExists(AppointmentInformation request, CancellationToken token)
{
var appointment = dto.Appointment.ToUniversalTime();
var appointment = request.Appointment.ToUniversalTime();
var criteria = new List<(string FieldName, string Value)>
{
("DoctorId", dto.DoctorId.ToString()),
("PatientId", dto.PatientId.ToString())
("DoctorId", request.DoctorId.ToString()),
("PatientId", request.PatientId.ToString())
};
var appointments = await FindAsync<Appointment>(criteria);
foreach (var app in appointments)
if (app.AppointmentsList.Any(a => a.Date == appointment.Date))
return false;
return true;
}
public async Task<bool> DoesAppointmentExists(AppointmentManagementDto dto)
{
var appointment = dto.Appointment.ToUniversalTime();
var criteria = new List<(string FieldName, string Value)>
{
("DoctorId", dto.DoctorId.ToString()),
("PatientId", dto.PatientId.ToString())
};
var appointments = await FindAsync<Appointment>(criteria);
var appointments = await FindAsync<Appointment>(criteria, token);
foreach (var app in appointments)
if (app.AppointmentsList.Any(a => a.Date == appointment.Date))
@@ -48,4 +38,27 @@ public class AppointmentsMongoDbService : MongoDbService, IAppointmentsMongoDbSe
return false;
}
public async Task<List<Appointment>> FindPastAppointments(DateTime currentDate, CancellationToken token)
{
var filterBuilder = Builders<Appointment>.Filter;
var filter = filterBuilder.Lt("AppointmentsList", currentDate);
return await GetCollection<Appointment>().Find(filter).ToListAsync(token);
}
public async Task UpdateAppointment(Appointment appointment, CancellationToken token)
{
await ModifyAsync("_id", appointment.Id, appointment, token);
}
public async Task DeleteAppointment(string appointmentId, CancellationToken token)
{
await DeleteByIdAsync<Appointment>(appointmentId, token);
}
public async Task<List<Appointment>> GetAllAppointments(CancellationToken token)
{
return await GetCollection<Appointment>().Find(Builders<Appointment>.Filter.Empty).ToListAsync(token);
}
}
@@ -2,12 +2,7 @@
namespace Infrastructure.Services.MongoDB;
public class ChatMongoDbService : MongoDbService, IChatMongoDbService
public class ChatMongoDbService(string connectionString, string databaseName, string collectionName)
: MongoDbService(connectionString, databaseName, collectionName), IChatMongoDbService
{
public ChatMongoDbService(string connectionString, string databaseName, string collectionName)
: base(connectionString, databaseName, collectionName)
{
}
// Implement additional methods specific to Chat database if needed
}
@@ -2,12 +2,7 @@
namespace Infrastructure.Services.MongoDB;
public class MedicalHistoryMongoDbService : MongoDbService, IMedicalHistoryMongoDbService
public class MedicalHistoryMongoDbService(string connectionString, string databaseName, string collectionName)
: MongoDbService(connectionString, databaseName, collectionName), IMedicalHistoryMongoDbService
{
public MedicalHistoryMongoDbService(string connectionString, string databaseName, string collectionName)
: base(connectionString, databaseName, collectionName)
{
}
// Implement additional methods specific to Medical History database if needed
}
@@ -9,23 +9,18 @@ public class MongoDbService
private readonly IMongoClient _database;
private readonly string _databaseName;
public MongoDbService(string connectionString, string databaseName, string collectionName)
protected MongoDbService(string connectionString, string databaseName, string collectionName)
{
_databaseName = databaseName;
_collectionName = collectionName;
Console.WriteLine($"Connection string: {connectionString}");
Console.WriteLine($"Database Name: {databaseName}");
Console.WriteLine($"Collection Name: {collectionName}");
var settings = MongoClientSettings.FromConnectionString(connectionString);
settings.ServerApi = new ServerApi(ServerApiVersion.V1);
_database = new MongoClient(settings);
try
{
var result = _database.GetDatabase("admin").RunCommand<BsonDocument>(new BsonDocument("ping", 1));
Console.WriteLine("Pinged your deployment. You successfully connected to MongoDB!");
_database.GetDatabase("admin").RunCommand<BsonDocument>(new BsonDocument("ping", 1));
}
catch (Exception ex)
{
@@ -33,12 +28,19 @@ public class MongoDbService
}
}
public MongoDbService(IMongoClient database, string collectionName, string databaseName)
{
_database = database;
_collectionName = collectionName;
_databaseName = databaseName;
}
public IMongoCollection<T> GetCollection<T>()
{
return _database.GetDatabase(_databaseName).GetCollection<T>(_collectionName);
}
public async Task<List<T>> FindAsync<T>(List<(string FieldName, string Value)> criteria)
public async Task<List<T>> FindAsync<T>(List<(string FieldName, string Value)> criteria, CancellationToken token)
{
var collection = _database.GetDatabase(_databaseName).GetCollection<T>(_collectionName);
@@ -47,33 +49,34 @@ public class MongoDbService
var combinedFilter = Builders<T>.Filter.And(filters);
return await collection.Find(combinedFilter).ToListAsync();
return await collection.Find(combinedFilter).ToListAsync(token);
}
public async Task AddAsync<T>(T document)
[Obsolete("Obsolete")]
public async Task AddAsync<T>(T document, CancellationToken token)
{
var collection = _database.GetDatabase(_databaseName).GetCollection<T>(_collectionName);
await collection.InsertOneAsync(document);
await collection.InsertOneAsync(document, token);
}
public async Task ModifyAsync<T>(string keyField, string keyValue, T document)
public async Task ModifyAsync<T>(string keyField, string keyValue, T document, CancellationToken token)
{
var collection = _database.GetDatabase(_databaseName).GetCollection<T>(_collectionName);
var filter = Builders<T>.Filter.Eq(keyField, keyValue);
await collection.ReplaceOneAsync(filter, document, new ReplaceOptions { IsUpsert = true });
await collection.ReplaceOneAsync(filter, document, new ReplaceOptions { IsUpsert = true }, token);
}
public async Task DeleteAsync<T>(string keyField, string keyValue)
public async Task DeleteAsync<T>(string keyField, string keyValue, CancellationToken token)
{
var collection = _database.GetDatabase(_databaseName).GetCollection<T>(_collectionName);
var filter = Builders<T>.Filter.Eq(keyField, keyValue);
await collection.DeleteOneAsync(filter);
await collection.DeleteOneAsync(filter, token);
}
public async Task DeleteByIdAsync<T>(string id)
public async Task DeleteByIdAsync<T>(string id, CancellationToken token)
{
var collection = _database.GetDatabase(_databaseName).GetCollection<T>(_collectionName);
var filter = Builders<T>.Filter.Eq("_id", id);
await collection.DeleteOneAsync(filter);
await collection.DeleteOneAsync(filter, token);
}
}
@@ -0,0 +1,19 @@
using Application.Services.Database.PostgreSQL;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Services.PostgreSQL;
public class AdminRepository(HealthcareManagerDatabase context)
: BasePostgreSqlRepository<Admin>(context), IAdminRepository
{
public async Task<Admin?> FindByEmailAsync(string email, CancellationToken token)
{
return await Context.Admins.FirstOrDefaultAsync(d => d.Email == email, token);
}
public async Task<bool> CredentialsMatch(string email, string password, CancellationToken token)
{
return await Context.Admins.AnyAsync(u => u.Email == email && u.Password == password, token);
}
}
@@ -1,43 +1,38 @@
using Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Services.PostgreSQL;
public class BasePostgreSQLRepository<T> where T : class
public class BasePostgreSqlRepository<T>(HealthcareManagerDatabase context)
where T : class
{
protected readonly HealthcareManagerDatabase _context;
protected readonly HealthcareManagerDatabase Context = context;
public BasePostgreSQLRepository(HealthcareManagerDatabase context)
public async Task<T?> GetByIdAsync(Guid id, CancellationToken token)
{
_context = context;
return await Context.Set<T>().FindAsync(id, token);
}
public async Task<T?> GetByIdAsync(Guid id)
public async Task<IEnumerable<T>> GetAllAsync(CancellationToken token)
{
return await _context.Set<T>().FindAsync(id);
return await Context.Set<T>().ToListAsync(token);
}
public async Task<IEnumerable<T>> GetAllAsync()
public async Task AddAsync(T entity, CancellationToken token)
{
return await _context.Set<T>().ToListAsync();
await Context.Set<T>().AddAsync(entity, token);
await Context.SaveChangesAsync(token);
}
public async Task AddAsync(T entity)
public async Task UpdateAsync(T entity, CancellationToken token)
{
await _context.Set<T>().AddAsync(entity);
await _context.SaveChangesAsync();
Context.Set<T>().Attach(entity);
Context.Entry(entity).State = EntityState.Modified;
await Context.SaveChangesAsync(token);
}
public async Task UpdateAsync(T entity)
public async Task DeleteAsync(T entity, CancellationToken token)
{
_context.Set<T>().Attach(entity);
_context.Entry(entity).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(T entity)
{
_context.Set<T>().Remove(entity);
await _context.SaveChangesAsync();
Context.Set<T>().Remove(entity);
await Context.SaveChangesAsync(token);
}
}
@@ -1,20 +1,19 @@
using Application.Services.Database.PostgreSQL;
using Core.Entities;
using Infrastructure.Data;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Services.PostgreSQL;
public class DoctorRepository(HealthcareManagerDatabase context)
: BasePostgreSQLRepository<Doctor>(context), IDoctorRepository
: BasePostgreSqlRepository<Doctor>(context), IDoctorRepository
{
public async Task<Doctor?> FindByEmailAsync(string email)
public async Task<Doctor?> FindByEmailAsync(string email, CancellationToken token)
{
return await _context.Doctors.FirstOrDefaultAsync(d => d.Email == email);
return await Context.Doctors.FirstOrDefaultAsync(d => d.Email == email, token);
}
public async Task<bool> CredentialsMatch(string email, string password)
public async Task<bool> CredentialsMatch(string email, string password, CancellationToken token)
{
return await _context.Doctors.AnyAsync(u => u.Email == email && u.Password == password);
return await Context.Doctors.AnyAsync(u => u.Email == email && u.Password == password, token);
}
}
@@ -1,18 +1,14 @@
using Application.Services.Database.PostgreSQL;
using Core.Entities;
using Infrastructure.Data;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Services.PostgreSQL;
public class MedicalHistoryRepository : BasePostgreSQLRepository<MedicalHistory>, IMedicalHistoryRepository
public class MedicalHistoryRepository(HealthcareManagerDatabase context)
: BasePostgreSqlRepository<MedicalHistory>(context), IMedicalHistoryRepository
{
public MedicalHistoryRepository(HealthcareManagerDatabase context) : base(context)
public async Task<MedicalHistory?> GetByPatientIdAsync(Guid patientId, CancellationToken token)
{
}
public async Task<MedicalHistory?> GetByUserIdAsync(Guid userId)
{
return await _context.MedicalHistories.FirstOrDefaultAsync(d => d.UserId == userId);
return await Context.MedicalHistories.FirstOrDefaultAsync(d => d.UserId == patientId, token);
}
}
@@ -1,20 +1,19 @@
using Application.Services.Database.PostgreSQL;
using Core.Entities;
using Infrastructure.Data;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Services.PostgreSQL;
public class PatientRepository(HealthcareManagerDatabase context)
: BasePostgreSQLRepository<Patient>(context), IPatientRepository
: BasePostgreSqlRepository<Patient>(context), IPatientRepository
{
public async Task<Patient?> FindByEmailAsync(string email)
public async Task<Patient?> FindByEmailAsync(string email, CancellationToken token)
{
return await _context.Patients.FirstOrDefaultAsync(d => d.Email == email);
return await Context.Patients.FirstOrDefaultAsync(d => d.Email == email, token);
}
public async Task<bool> CredentialsMatch(string email, string password)
public async Task<bool> CredentialsMatch(string email, string password, CancellationToken token)
{
return await _context.Patients.AnyAsync(u => u.Email == email && u.Password == password);
return await Context.Patients.AnyAsync(u => u.Email == email && u.Password == password, token);
}
}