Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/.idea.Reservation_System/.idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<Folder Include="Features\AppointmentServiceLink\Interfaces\" />
<Folder Include="Features\Service\Interfaces\" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions Application/Common/Interfaces/IJwtTokenGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Domain.Aggregates.User;
using Domain.Entities;

namespace Application.Common.Interfaces;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Application.Common.Interfaces;

public interface IUSerContext
public interface IUserContext
{
Guid UserId { get;}
string? Email { get;}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Application.Features.Appointments.DTOs;
using Domain.Aggregates.Appointment;
using Domain.Entities;

namespace Application.Features.Appointments.Interfaces;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using Application.Common.Interfaces;
using Application.Features.Appointments.DTOs;
using Application.Features.Appointments.Interfaces;
using Application.Features.AppointmentServiceLink.Interfaces;
using Application.Features.Auth.Interfaces;
using Application.Features.Service.Interfaces;
using Domain.Aggregates.Appointment;
using Domain.Entities;
using Domain.Enums;
using Domain.Exceptions;
using Domain.Repository;

namespace Application.Features.Appointments.Services;

Expand All @@ -16,11 +16,11 @@
private readonly IUserRepository _userRepository;
private readonly IServiceRepository _serviceRepository;
private readonly IAppointmenServiceLinkRepository _appointmenServiceLinkRepository;
private readonly IUSerContext _userContext;
private readonly IUserContext _userContext;

public AppointmentService(IAppointmentRepository repository, IUserRepository userRepository,
IServiceRepository serviceRepository, IAppointmenServiceLinkRepository appointmenServiceLinkRepository,
IUSerContext userContext)
IUserContext userContext)
{
_appointmentRepository = repository;
_userRepository = userRepository;
Expand All @@ -40,7 +40,7 @@
throw new UnauthorizedAccessException("Not Authenticated");

var userId = _userContext.UserId;
var appointment = new Appointment(userId, dto.StartTime, dto.EndTime, dto.AppoinmentTitle);

Check failure on line 43 in Application/Features/Appointments/Services/AppointmentService.cs

View workflow job for this annotation

GitHub Actions / build

'Appointment' does not contain a constructor that takes 4 arguments

if (!await _userRepository.IsUserExistsByIdAsync(userId))
throw new NotFoundException($"User with {userId} does not exist");
Expand All @@ -54,10 +54,10 @@
if (services.Count != dto.ServiceId.Count)
return "One or more services not found";

appointment.AddServices(services);

Check failure on line 57 in Application/Features/Appointments/Services/AppointmentService.cs

View workflow job for this annotation

GitHub Actions / build

Argument 1: cannot convert from 'System.Collections.Generic.List<Domain.Aggregates.Service.Service>' to 'System.Collections.Generic.IEnumerable<System.Guid>'

await _appointmentRepository.AddAsync(appointment);

Check failure on line 59 in Application/Features/Appointments/Services/AppointmentService.cs

View workflow job for this annotation

GitHub Actions / build

'IAppointmentRepository' does not contain a definition for 'AddAsync' and no accessible extension method 'AddAsync' accepting a first argument of type 'IAppointmentRepository' could be found (are you missing a using directive or an assembly reference?)
await _appointmentRepository.SaveAsync();

Check failure on line 60 in Application/Features/Appointments/Services/AppointmentService.cs

View workflow job for this annotation

GitHub Actions / build

'IAppointmentRepository' does not contain a definition for 'SaveAsync' and no accessible extension method 'SaveAsync' accepting a first argument of type 'IAppointmentRepository' could be found (are you missing a using directive or an assembly reference?)
return $"{appointment.Title} Created";
}

Expand Down Expand Up @@ -94,7 +94,7 @@
public async Task<List<ViewAppointments>> ViewAppointments()
{
EnsureAdmin();
return await _appointmentRepository.ViewAppointments();

Check failure on line 97 in Application/Features/Appointments/Services/AppointmentService.cs

View workflow job for this annotation

GitHub Actions / build

Cannot implicitly convert type 'System.Collections.Generic.List<Domain.Aggregates.Appointment.Appointment>' to 'System.Collections.Generic.List<Application.Features.Appointments.DTOs.ViewAppointments>'
}

public async Task<List<ViewAppointments>> ViewMyAppointments()
Expand Down
14 changes: 0 additions & 14 deletions Application/Features/Auth/Interfaces/IUserRepository.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Application.Features.Auth.Interfaces;
using Domain.Entities;
using Domain.Exceptions;
using Domain.Repository;

namespace Application.Features.Auth.Services;

Expand Down Expand Up @@ -55,13 +56,5 @@ public async Task ForgetPasswordAsync(string email)
);

await _userRepository.SaveChangesAsync();

//Implement Email Service

// await _emailService.SendAsync(
// email,
// "Password Reset Code",
// $"Your verification code is: {code}"
// );
}
}
6 changes: 4 additions & 2 deletions Application/Features/Auth/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using Application.Common.Interfaces.Repositories;
using Application.Features.Auth.DTOs;
using Application.Features.Auth.Interfaces;
using Domain.Aggregates.User;
using Domain.Entities;
using Domain.Repository;

namespace Application.Features.Auth.Services;

Expand All @@ -14,12 +16,12 @@ public class UserService : IUserService
private readonly IPasswordHasher _passwordHasher;
private readonly IJwtTokenService _jwtTokenService;
private readonly IRefreshTokenRepository _refreshTokenRepository;
private readonly IUSerContext _userContext;
private readonly IUserContext _userContext;
private readonly IVerificationCodeGenerator _verificationCodeGenerator;

public UserService(IUserRepository repository, IJwtTokenService jwtTokenGenerator,
IRefreshTokenRepository refreshTokenRepository, IHasher hasher, IPasswordHasher passwordHasher,
IUSerContext userContext, IVerificationCodeGenerator verificationCodeGenerator)
IUserContext userContext, IVerificationCodeGenerator verificationCodeGenerator)
{
_userRepository = repository;
_jwtTokenService = jwtTokenGenerator;
Expand Down
13 changes: 0 additions & 13 deletions Application/Features/Service/Interfaces/IServiceRepository.cs

This file was deleted.

8 changes: 4 additions & 4 deletions Application/Features/Service/Services/ServiceAppService.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using Application.Common.Interfaces;
using Application.Features.Service.DTOs;
using Application.Features.Service.Interfaces;
using Domain.Enums;
using Domain.Exceptions;
using Domain.Repository;

namespace Application.Features.Service.Services;

public class ServiceAppService:IServiceAppService
{
private readonly IServiceRepository _repository;
private readonly IUSerContext _userContext;
public ServiceAppService(IServiceRepository repository, IUSerContext context)
private readonly IUserContext _userContext;
public ServiceAppService(IServiceRepository repository, IUserContext context)
{
_repository = repository;
_userContext = context;
Expand All @@ -24,8 +24,8 @@
public async Task<string> CreateServiceAsync(CreateService createService)
{
EnsureAdmin();
var service=new Domain.Entities.Service(createService.Title,createService.DurationMinutes);
var service=new Domain.Aggregates.Service.Service(createService.Title,createService.DurationMinutes);

Check failure on line 27 in Application/Features/Service/Services/ServiceAppService.cs

View workflow job for this annotation

GitHub Actions / build

Argument 2: cannot convert from 'int' to 'System.TimeSpan'
await _repository.CreatServiceAsync(service);

Check failure on line 28 in Application/Features/Service/Services/ServiceAppService.cs

View workflow job for this annotation

GitHub Actions / build

'IServiceRepository' does not contain a definition for 'CreatServiceAsync' and no accessible extension method 'CreatServiceAsync' accepting a first argument of type 'IServiceRepository' could be found (are you missing a using directive or an assembly reference?)
return $"{service.Title} created";
}

Expand All @@ -34,8 +34,8 @@
EnsureAdmin();
var appointment = await _repository.GetServiceByIdAsync(serviceId) ?? throw new NotFoundException("Service not found");

appointment.Edit(editService.DurationMinutes,editService.Title);

Check failure on line 37 in Application/Features/Service/Services/ServiceAppService.cs

View workflow job for this annotation

GitHub Actions / build

Argument 1: cannot convert from 'int' to 'System.TimeSpan'
await _repository.SaveChangesAsync();

Check failure on line 38 in Application/Features/Service/Services/ServiceAppService.cs

View workflow job for this annotation

GitHub Actions / build

'IServiceRepository' does not contain a definition for 'SaveChangesAsync' and no accessible extension method 'SaveChangesAsync' accepting a first argument of type 'IServiceRepository' could be found (are you missing a using directive or an assembly reference?)
return $"{appointment.Title} Updated";
}

Expand All @@ -48,6 +48,6 @@

public async Task<List<ViewServices>> ViewAllServicesAsync()
{
return await _repository.ViewAllServiceAsync();

Check failure on line 51 in Application/Features/Service/Services/ServiceAppService.cs

View workflow job for this annotation

GitHub Actions / build

Cannot implicitly convert type 'System.Collections.Generic.List<Domain.Aggregates.Service.Service>' to 'System.Collections.Generic.List<Application.Features.Service.DTOs.ViewServices>'
}

Expand Down
64 changes: 64 additions & 0 deletions Domain/Aggregates/Appointment/Appointment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Domain.Base;
using Domain.Enums;
using Domain.ValueObject;

namespace Domain.Aggregates.Appointment
{
public class Appointment:AggregatedRoot
{
public Guid AppointmentId { get; private set; }
public Guid UserId { get; private set; }
public string Title { get; private set; }
public AppointmentTimeRange TimeRange { get; private set; }
public AppointmentStatus Status { get; private set; }

private readonly List<Guid> _serviceIds = new();
public IReadOnlyList<Guid> ServiceIds=>_serviceIds;

public void AddServices(IEnumerable<Guid> serviceIds)
{
foreach (var id in serviceIds.Distinct())
{
if (!_serviceIds.Contains(id))
_serviceIds.Add(id);
}
}

private Appointment() { } // For EF

Check warning on line 27 in Domain/Aggregates/Appointment/Appointment.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'TimeRange' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 27 in Domain/Aggregates/Appointment/Appointment.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Title' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public Appointment(Guid userId,AppointmentTimeRange timeRange, string title)
{
if (string.IsNullOrEmpty(title))
throw new ArgumentException("Title cannot be null or empty");
AppointmentId = Guid.NewGuid();
UserId = userId;
TimeRange = timeRange??throw new ArgumentNullException(nameof(timeRange)) ;
Title = title;
Status = AppointmentStatus.Reserved;
}

public void Cancel()
{
if (Status == AppointmentStatus.Cancelled)
throw new InvalidOperationException("Already cancelled");

Status = AppointmentStatus.Cancelled;
}

public void Reschedule(AppointmentTimeRange timeRange)
{
if (Status == AppointmentStatus.Cancelled)
throw new InvalidOperationException("Cannot reschedule cancelled appointment");
TimeRange = timeRange ?? throw new ArgumentNullException(nameof(timeRange));
}
public void ChangeTitle(string title)
{
if (Status == AppointmentStatus.Cancelled)
throw new InvalidOperationException("Cannot edit cancelled appointment");

if (string.IsNullOrEmpty(title))
throw new ArgumentException("Title cannot be null or empty");
Title=title;
}
}
}
32 changes: 32 additions & 0 deletions Domain/Aggregates/Service/Service.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Domain.Base;

namespace Domain.Aggregates.Service;

public class Service:AggregatedRoot
{
public Guid Id { get; private set; }

Check warning on line 7 in Domain/Aggregates/Service/Service.cs

View workflow job for this annotation

GitHub Actions / build

'Service.Id' hides inherited member 'EntityBase.Id'. Use the new keyword if hiding was intended.
public string Title { get; private set; }
public TimeSpan DurationMinutes { get; private set; }
private Service() { }

public Service(string title, TimeSpan durationMinutes)
{
if (string.IsNullOrEmpty(title))
throw new ArgumentException("Title required");
if (durationMinutes.TotalMinutes <= 0)
throw new ArgumentException("Invalid duration");
Id = Guid.NewGuid();
Title = title;
DurationMinutes = durationMinutes;
}

public void Edit(TimeSpan durationMinutes,string title)
{
if (string.IsNullOrEmpty(title))
throw new ArgumentException("Title required");
if (durationMinutes.TotalMinutes <= 0)
throw new ArgumentException("Invalid duration");
DurationMinutes = durationMinutes;
Title = title;
}
}
95 changes: 95 additions & 0 deletions Domain/Aggregates/User/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Domain.Base;
using Domain.Entities;
using Domain.Enums;
using Domain.ValueObject;

namespace Domain.Aggregates.User;

public class User:AggregatedRoot
{
public Guid Id { get; private set; }

Check warning on line 10 in Domain/Aggregates/User/User.cs

View workflow job for this annotation

GitHub Actions / build

'User.Id' hides inherited member 'EntityBase.Id'. Use the new keyword if hiding was intended.
public string FullName { get; private set; }
public UserRole Role { get; private set; }
public Email Email { get; private set; }
public PhoneNumber PhoneNumber { get; private set; }
public Password Password { get; private set; }
public string? PasswordResetCodeHash { get; private set; }
public DateTime? PasswordResetCodeExpireAt { get; private set; }
public int PasswordResetAttemptsCount { get; private set; }
private readonly List<RefreshToken> _refreshTokens = new();
public IReadOnlyList<RefreshToken> RefreshTokens => _refreshTokens;

public void AddRefreshToken(string refreshToken, DateTime expiresAt)
{
_refreshTokens.RemoveAll(tk => tk.IsExpired);
_refreshTokens.Add(new RefreshToken(refreshToken, expiresAt));
}

public void RevokeRefreshToken(Guid tokenId)
{
var token = _refreshTokens.FirstOrDefault(tk => tk.Id == tokenId);
if (token == null) throw new InvalidOperationException("Refresh token not found");
token.Revoke();
}

private User()

Check warning on line 35 in Domain/Aggregates/User/User.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Password' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 35 in Domain/Aggregates/User/User.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'PhoneNumber' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 35 in Domain/Aggregates/User/User.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Email' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 35 in Domain/Aggregates/User/User.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'FullName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
{
}

public User(string fullName, Email email, PhoneNumber phoneNumber, Password password)
{
Id = Guid.NewGuid();
FullName = fullName;
Role = UserRole.User;
Email = email;
PhoneNumber = phoneNumber;
Password = password;
}

public User(string fullName, Email email, PhoneNumber phoneNumber, UserRole role, Password password)
{
Id = Guid.NewGuid();
FullName = fullName;
Role = role;
Email = email ?? throw new ArgumentNullException(nameof(email));
PhoneNumber = phoneNumber ?? throw new ArgumentNullException(nameof(phoneNumber));
Password = password ?? throw new ArgumentNullException(nameof(password));
}

public void UpdateProfile(string fullName, PhoneNumber phoneNumber)
{
if (string.IsNullOrWhiteSpace(fullName)) throw new ArgumentException("Full name required");
if (phoneNumber == null) throw new ArgumentException("Phone number required");
FullName = fullName;
PhoneNumber = phoneNumber;
}

public void ResetPassword(string codeHash, DateTime expiresAt)
{
if (string.IsNullOrWhiteSpace(codeHash)) throw new ArgumentException("Code hash required");
if (expiresAt <= DateTime.UtcNow) throw new ArgumentException("Expires must be in future");
PasswordResetCodeHash = codeHash;
PasswordResetCodeExpireAt = expiresAt;
PasswordResetAttemptsCount = 0;
}

private const int MaxResetAttempts = 5;

public bool CanUseResetPassword(string codeHash, DateTime now)
{
if (PasswordResetCodeExpireAt == null || now > PasswordResetCodeExpireAt)
return false;
if (PasswordResetAttemptsCount >= MaxResetAttempts)
return false;
return PasswordResetCodeHash == codeHash;
}

public void IncreasePasswordResetAttemptCount() => PasswordResetAttemptsCount++;

public void ClearPasswordResetCode()
{
PasswordResetCodeHash = null;
PasswordResetCodeExpireAt = null;
PasswordResetAttemptsCount = 0;
}
}
11 changes: 11 additions & 0 deletions Domain/Base/AggregatedRoot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Domain.Base;

public class AggregatedRoot:EntityBase
{
private readonly List<DomainEvent> _domainEvents=[];
public IReadOnlyCollection<DomainEvent> DomainEvents => _domainEvents;

protected void AddDomainEvent(DomainEvent domainEvent)=> _domainEvents.Add(domainEvent);

public void ClearDomainEvents() => _domainEvents.Clear();
}
Loading
Loading