From 0798c0e22ca141af03dbb79f4abdf1f0a0f3bf8f Mon Sep 17 00:00:00 2001 From: Abel Date: Tue, 23 May 2023 11:34:13 +0300 Subject: [PATCH 1/2] feature(backend):implement seat crud --- .../Controllers/SeatController.cs | 51 +++++++++++++ .../Contracts/Persistence/ISeatRepository.cs | 13 ++++ .../Contracts/Persistence/IUnitOfWork.cs | 1 + .../Seats/CQRS/Commands/CreateSeatCommand.cs | 17 +++++ .../Seats/CQRS/Commands/DeleteSeatCommand.cs | 15 ++++ .../Seats/CQRS/Commands/UpdateSeatCommand.cs | 16 ++++ .../CQRS/Handlers/CreateSeatCommandHandler.cs | 67 +++++++++++++++++ .../CQRS/Handlers/DeleteSeatCommandHandler.cs | 59 +++++++++++++++ .../CQRS/Handlers/GetAllSeatsQueryHandler.cs | 43 +++++++++++ .../CQRS/Handlers/UpdateSeatCommandHandler.cs | 73 +++++++++++++++++++ .../Seats/CQRS/Queries/GetAllSeatsQuery.cs | 16 ++++ .../Features/Seats/DTOs/CreateSeatDto.cs | 15 ++++ .../Features/Seats/DTOs/SeatDto.cs | 17 +++++ .../Features/Seats/DTOs/UpdateSeatDto.cs | 16 ++++ .../DTOs/Validators/CreateSeatDtoValidator.cs | 21 ++++++ .../DTOs/Validators/UpdateSeatDtoValidator.cs | 28 +++++++ .../Profiles/MappingProfile.cs | 7 ++ .../CineFlex/CineFlex.Domain/Seat.cs | 16 ++++ .../CineFlex.Persistence/CineFlexDbContex.cs | 2 + .../Repositories/SeatRepository.cs | 19 +++++ .../Repositories/UnitOfWork.cs | 11 ++- .../CineFlex.identity.csproj | 9 +++ .../CineFlex/CineFlex.identity/Class1.cs | 7 ++ 23 files changed, 538 insertions(+), 1 deletion(-) create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/SeatController.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/ISeatRepository.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/CreateSeatCommand.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/DeleteSeatCommand.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/UpdateSeatCommand.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/CreateSeatCommandHandler.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/DeleteSeatCommandHandler.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/GetAllSeatsQueryHandler.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/UpdateSeatCommandHandler.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Queries/GetAllSeatsQuery.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/CreateSeatDto.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/SeatDto.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/UpdateSeatDto.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/Validators/CreateSeatDtoValidator.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/Validators/UpdateSeatDtoValidator.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Domain/Seat.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/SeatRepository.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/CineFlex.identity.csproj create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/Class1.cs diff --git a/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/SeatController.cs b/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/SeatController.cs new file mode 100644 index 000000000..0f8ad8260 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/SeatController.cs @@ -0,0 +1,51 @@ +using CineFlex.Application.Features.Cinema.CQRS.Commands; +using CineFlex.Application.Features.Cinema.CQRS.Queries; +using CineFlex.Application.Features.Cinema.DTO; +using CineFlex.Application.Features.Cinema.Dtos; +using CineFlex.Application.Features.Seats.CQRS.Commands; +using CineFlex.Application.Features.Seats.CQRS.Queries; +using CineFlex.Application.Features.Seats.DTOs; +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace CineFlex.API.Controllers +{ + public class SeatController : BaseApiController + { + private readonly IMediator _mediator; + + public SeatController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet("GetAll")] + public async Task>> Get() + { + return HandleResult(await _mediator.Send(new GetAllSeatsQuery())); + } + + + [HttpPost("CreateSeat")] + public async Task Post([FromBody] CreateSeatDto createSeatDto) + { + var command = new CreateSeatCommand { SeatDto = createSeatDto }; + return HandleResult(await _mediator.Send(command)); + } + [HttpPut("UpdateSeat")] + public async Task Put([FromBody] UpdateSeatDto updateSeatDto) + { + var command = new UpdateSeatCommand { updateSeatDto = updateSeatDto }; + await _mediator.Send(command); + return NoContent(); + } + [HttpDelete("{id}")] + public async Task Delete(int id) + { + var command = new DeleteSeatCommand { Id = id }; + await _mediator.Send(command); + return NoContent(); + } + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/ISeatRepository.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/ISeatRepository.cs new file mode 100644 index 000000000..0bec6053e --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/ISeatRepository.cs @@ -0,0 +1,13 @@ +using CineFlex.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Contracts.Persistence +{ + public interface ISeatRepository : IGenericRepository + { + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IUnitOfWork.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IUnitOfWork.cs index 60a0724e4..20dc30105 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IUnitOfWork.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IUnitOfWork.cs @@ -9,6 +9,7 @@ namespace CineFlex.Application.Contracts.Persistence { public interface IUnitOfWork : IDisposable { + ISeatRepository SeatRepository { get; } IMovieRepository MovieRepository { get; } ICinemaRepository CinemaRepository { get; } Task Save(); diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/CreateSeatCommand.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/CreateSeatCommand.cs new file mode 100644 index 000000000..43f8c5a57 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/CreateSeatCommand.cs @@ -0,0 +1,17 @@ +using CineFlex.Application.Features.Seats.DTOs; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.CQRS.Commands +{ + public class CreateSeatCommand : IRequest> + { + public CreateSeatDto SeatDto { get; set; } + } + +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/DeleteSeatCommand.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/DeleteSeatCommand.cs new file mode 100644 index 000000000..400e2e968 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/DeleteSeatCommand.cs @@ -0,0 +1,15 @@ +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.CQRS.Commands +{ + public class DeleteSeatCommand : IRequest> + { + public int Id { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/UpdateSeatCommand.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/UpdateSeatCommand.cs new file mode 100644 index 000000000..f55d63ce1 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Commands/UpdateSeatCommand.cs @@ -0,0 +1,16 @@ +using CineFlex.Application.Features.Seats.DTOs; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.CQRS.Commands +{ + public class UpdateSeatCommand : IRequest> + { + public UpdateSeatDto updateSeatDto { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/CreateSeatCommandHandler.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/CreateSeatCommandHandler.cs new file mode 100644 index 000000000..48472e3b0 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/CreateSeatCommandHandler.cs @@ -0,0 +1,67 @@ +using AutoMapper; +using CineFlex.Application.Contracts.Persistence; +using CineFlex.Application.Features.Movies.CQRS.Commands; +using CineFlex.Application.Features.Movies.DTOs.Validators; +using CineFlex.Application.Features.Seats.CQRS.Commands; +using CineFlex.Application.Features.Seats.DTOs.Validators; +using CineFlex.Application.Responses; +using CineFlex.Domain; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.CQRS.Handlers +{ + public class CreateSeatCommandHandler : IRequestHandler> + { + private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; + + public CreateSeatCommandHandler(IUnitOfWork unitOfWork, IMapper mapper) + { + _unitOfWork = unitOfWork; + _mapper = mapper; + } + + public async Task> Handle(CreateSeatCommand request, CancellationToken cancellationToken) + { + var response = new BaseCommandResponse(); + var validator = new CreateSeatDtoValidator(); + var validationResult = await validator.ValidateAsync(request.SeatDto); + + + + if (validationResult.IsValid == false) + { + response.Success = false; + response.Message = " Creation Failed"; + response.Errors = validationResult.Errors.Select(e => e.ErrorMessage).ToList(); + + } + else + { + var seat = _mapper.Map(request.SeatDto); + + seat = await _unitOfWork.SeatRepository.Add(seat); + + if (await _unitOfWork.Save() > 0) + { + response.Success = true; + response.Message = "Creation Successful"; + response.Value = seat.Id; + } + else + { + response.Success = false; + response.Message = "Creation Failed"; + } + + } + + return response; + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/DeleteSeatCommandHandler.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/DeleteSeatCommandHandler.cs new file mode 100644 index 000000000..9c05e23b7 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/DeleteSeatCommandHandler.cs @@ -0,0 +1,59 @@ +using AutoMapper; +using CineFlex.Application.Contracts.Persistence; +using CineFlex.Application.Features.Movies.CQRS.Commands; +using CineFlex.Application.Features.Seats.CQRS.Commands; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.CQRS.Handlers +{ + public class DeleteSeatCommandHandler : IRequestHandler> + { + private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; + + public DeleteSeatCommandHandler(IUnitOfWork unitOfWork, IMapper mapper) + { + _unitOfWork = unitOfWork; + _mapper = mapper; + } + + public async Task> Handle(DeleteSeatCommand request, CancellationToken cancellationToken) + { + var response = new BaseCommandResponse(); + + var seat = await _unitOfWork.SeatRepository.Get(request.Id); + + if (seat is null) + { + response.Success = false; + response.Message = "Failed find a seat by that Id."; + } + else + { + + await _unitOfWork.SeatRepository.Delete(seat); + + + if (await _unitOfWork.Save() > 0) + { + response.Success = true; + response.Message = "seat deleted Successful"; + response.Value = seat.Id; + } + else + { + response.Success = false; + response.Message = "seat Deletion Failed"; + } + } + + return response; + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/GetAllSeatsQueryHandler.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/GetAllSeatsQueryHandler.cs new file mode 100644 index 000000000..bd26c36f5 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/GetAllSeatsQueryHandler.cs @@ -0,0 +1,43 @@ +using AutoMapper; +using CineFlex.Application.Contracts.Persistence; +using CineFlex.Application.Features.Movies.CQRS.Queries; +using CineFlex.Application.Features.Movies.DTOs; +using CineFlex.Application.Features.Seats.CQRS.Queries; +using CineFlex.Application.Features.Seats.DTOs; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.CQRS.Handlers +{ + public class GetAllSeatsQueryHandler : IRequestHandler>> + { + private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; + + public GetAllSeatsQueryHandler(IUnitOfWork unitOfWork, IMapper mapper) + { + _unitOfWork = unitOfWork; + _mapper = mapper; + } + + public async Task>> Handle(GetAllSeatsQuery request, CancellationToken cancellationToken) + { + + var response = new BaseCommandResponse>(); + var seats = await _unitOfWork.SeatRepository.GetAll(); + + response.Success = true; + response.Message = "Movies retrieval Successful"; + response.Value = _mapper.Map>(seats); + + return response; + } + + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/UpdateSeatCommandHandler.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/UpdateSeatCommandHandler.cs new file mode 100644 index 000000000..7eaddc7da --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Handlers/UpdateSeatCommandHandler.cs @@ -0,0 +1,73 @@ +using AutoMapper; +using CineFlex.Application.Contracts.Persistence; +using CineFlex.Application.Features.Movies.DTOs.Validators; +using CineFlex.Application.Features.Seats.CQRS.Commands; +using CineFlex.Application.Features.Seats.DTOs.Validators; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.CQRS.Handlers +{ + public class UpdateSeatCommandHandler : IRequestHandler> + { + + private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; + + public UpdateSeatCommandHandler(IUnitOfWork unitOfWork, IMapper mapper) + { + _unitOfWork = unitOfWork; + _mapper = mapper; + } + public async Task> Handle(UpdateSeatCommand request, CancellationToken cancellationToken) + { + + var response = new BaseCommandResponse(); + + + var validator = new UpdateSeatDtoValidator(); + var validationResult = await validator.ValidateAsync(request.updateSeatDto); + + if (validationResult.IsValid == false) + { + response.Success = false; + response.Message = "Update Failed"; + response.Errors = validationResult.Errors.Select(q => q.ErrorMessage).ToList(); + } + else + { + + var seat = await _unitOfWork.SeatRepository.Get(request.updateSeatDto.Id); + + if (seat == null) + { + response.Success = false; + response.Message = "Update Failed"; + return response; + } + _mapper.Map(request.updateSeatDto, seat); + + await _unitOfWork.SeatRepository.Update(seat); + if (await _unitOfWork.Save() > 0) + { + response.Success = true; + response.Message = "Updated Successful"; + response.Value = Unit.Value; + } + else + { + response.Success = false; + response.Message = "Update Failed"; + } + } + + return response; + + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Queries/GetAllSeatsQuery.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Queries/GetAllSeatsQuery.cs new file mode 100644 index 000000000..5d46e2d98 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/CQRS/Queries/GetAllSeatsQuery.cs @@ -0,0 +1,16 @@ +using CineFlex.Application.Features.Seats.DTOs; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.CQRS.Queries +{ + public class GetAllSeatsQuery : IRequest>> + { + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/CreateSeatDto.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/CreateSeatDto.cs new file mode 100644 index 000000000..07134d1e1 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/CreateSeatDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.DTOs +{ + public class CreateSeatDto + { + public int SeatNumber { get; set; } + public int RowNumber { get; set; } + public DateTime lastBooked { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/SeatDto.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/SeatDto.cs new file mode 100644 index 000000000..70664d58b --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/SeatDto.cs @@ -0,0 +1,17 @@ +using CineFlex.Application.Features.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.DTOs +{ + public class SeatDto : BaseDto + { + public int SeatNumber { get; set; } + public int RowNumber { get; set; } + public DateTime lastBooked { get; set; } + } + +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/UpdateSeatDto.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/UpdateSeatDto.cs new file mode 100644 index 000000000..4ca1b7dc8 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/UpdateSeatDto.cs @@ -0,0 +1,16 @@ +using CineFlex.Application.Features.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.DTOs +{ + public class UpdateSeatDto : BaseDto + { + public int SeatNumber { get; set; } + public int RowNumber { get; set; } + public DateTime lastBooked { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/Validators/CreateSeatDtoValidator.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/Validators/CreateSeatDtoValidator.cs new file mode 100644 index 000000000..ec592ef39 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/Validators/CreateSeatDtoValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.DTOs.Validators +{ + public class CreateSeatDtoValidator : AbstractValidator + { + public CreateSeatDtoValidator() + { + RuleFor(x => x.SeatNumber).NotEmpty().WithMessage("SeatNumber is required."); + RuleFor(x => x.RowNumber).NotEmpty().WithMessage("RowNumber is required."); + RuleFor(x => x.lastBooked) + .NotEmpty().WithMessage("lastBooked is required.") + .LessThan(DateTime.Now).WithMessage("lastBooked must be less than DateTime.Now"); + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/Validators/UpdateSeatDtoValidator.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/Validators/UpdateSeatDtoValidator.cs new file mode 100644 index 000000000..bf2e15b19 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Seats/DTOs/Validators/UpdateSeatDtoValidator.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Seats.DTOs.Validators +{ + public class UpdateSeatDtoValidator : AbstractValidator + { + public UpdateSeatDtoValidator() + { + RuleFor(x => x.SeatNumber) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull(); + + RuleFor(x => x.RowNumber) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull(); + + RuleFor(x => x.lastBooked) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull() + .LessThan(DateTime.Now).WithMessage("{PropertyName} must be less than today's date."); + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Profiles/MappingProfile.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Profiles/MappingProfile.cs index 7c00bdd4c..99b3f5b38 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Application/Profiles/MappingProfile.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Profiles/MappingProfile.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using CineFlex.Application.Features.Seats.DTOs.Validators; +using CineFlex.Application.Features.Seats.DTOs; namespace CineFlex.Application.Profiles { @@ -26,6 +28,11 @@ public MappingProfile() CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); + + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + + } } } diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Domain/Seat.cs b/Backend/backend_assessment/CineFlex/CineFlex.Domain/Seat.cs new file mode 100644 index 000000000..72eef3cef --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Domain/Seat.cs @@ -0,0 +1,16 @@ +using CineFlex.Domain.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Domain +{ + public class Seat:BaseDomainEntity + { + public int SeatNumber { get; set; } + public int RowNumber { get; set; } + public DateTime lastBooked { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/CineFlexDbContex.cs b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/CineFlexDbContex.cs index 20ccf56db..fe5068fad 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/CineFlexDbContex.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/CineFlexDbContex.cs @@ -44,5 +44,7 @@ public override Task SaveChangesAsync(CancellationToken cancellationToken = public DbSet Movies { get; set; } + public DbSet Seats { get; set; } + } } diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/SeatRepository.cs b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/SeatRepository.cs new file mode 100644 index 000000000..bccbf3eb5 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/SeatRepository.cs @@ -0,0 +1,19 @@ +using CineFlex.Application.Contracts.Persistence; +using CineFlex.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Persistence.Repositories +{ + public class SeatRepository : GenericRepository, ISeatRepository + { + private readonly CineFlexDbContex _context; + public SeatRepository(CineFlexDbContex dbContext): base(dbContext) + { + _context = dbContext; + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/UnitOfWork.cs b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/UnitOfWork.cs index 7fdd26456..23391a24e 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/UnitOfWork.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/UnitOfWork.cs @@ -13,7 +13,7 @@ public class UnitOfWork : IUnitOfWork { private readonly CineFlexDbContex _context; private IMovieRepository _MovieRepository; - + private ISeatRepository _SeatRepository; private ICinemaRepository _cinemaRepository; public UnitOfWork(CineFlexDbContex context) { @@ -39,6 +39,15 @@ public ICinemaRepository CinemaRepository } } + public ISeatRepository SeatRepository + { + get + { + if (_SeatRepository == null) + _SeatRepository = new SeatRepository(_context); + return _SeatRepository; + } + } public void Dispose() { diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/CineFlex.identity.csproj b/Backend/backend_assessment/CineFlex/CineFlex.identity/CineFlex.identity.csproj new file mode 100644 index 000000000..132c02c59 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/CineFlex.identity.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/Class1.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/Class1.cs new file mode 100644 index 000000000..64317c025 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/Class1.cs @@ -0,0 +1,7 @@ +namespace CineFlex.identity +{ + public class Class1 + { + + } +} \ No newline at end of file From ed8fd09ba63012f511ffadf3a26930b143af7de9 Mon Sep 17 00:00:00 2001 From: Abel Date: Tue, 23 May 2023 16:18:12 +0300 Subject: [PATCH 2/2] feature(backend): add authentication and commit (Abel Mekonen) --- .../CineFlex/CineFlex.API/CineFlex.API.csproj | 1 + .../Controllers/AuthController.cs | 40 ++++ .../Controllers/BookingController.cs | 34 ++++ .../Controllers/SeatController.cs | 7 + .../CineFlex/CineFlex.API/Program.cs | 2 + .../CineFlex/CineFlex.API/appsettings.json | 6 + .../CineFlex.Application.UnitTest.csproj | 1 + .../CineFlex.Application.csproj | 2 +- .../Contracts/Identity/IAuthRepository.cs | 16 ++ .../Persistence/IBookingRepository.cs | 13 ++ .../Contracts/Persistence/IUnitOfWork.cs | 1 + .../CQRS/Commands/SignInCommand.cs | 16 ++ .../CQRS/Commands/SignUpCommand.cs | 16 ++ .../CQRS/Handlers/SignInCommandHandler.cs | 54 +++++ .../CQRS/Handlers/SignUpCommandHandler.cs | 54 +++++ .../Authentication/DTOs/SignInForm.cs | 14 ++ .../Authentication/DTOs/SignInResponse.cs | 13 ++ .../Authentication/DTOs/SignUpForm.cs | 15 ++ .../Authentication/DTOs/SignUpResponse.cs | 16 ++ .../Validators/SignInFormDtoValidators.cs | 27 +++ .../DTOs/Validators/SignUpFormValidator.cs | 28 +++ .../CQRS/Commands/CreateBookingCommand.cs | 16 ++ .../Handlers/CreateBookingCommandHandler.cs | 65 ++++++ .../Features/Booking/DTOs/CreateBookingDto.cs | 17 ++ .../Validators/CreateBookingDtoValidator.cs | 96 +++++++++ .../Profiles/MappingProfile.cs | 9 +- .../CineFlex/CineFlex.Domain/BookingEntity.cs | 23 +++ .../CineFlex - Backup.Domain.csproj | 20 ++ .../CineFlex.Domain/CineFlex.Domain.csproj | 6 +- .../CineFlex/CineFlex.Domain/CinemaEntity.cs | 3 + .../CineFlex/CineFlex.Domain/Movie.cs | 3 + .../CineFlex/CineFlex.Domain/Seat.cs | 7 + .../CineFlex/CineFlex.Domain/User.cs | 10 + .../CineFlex.Persistence/CineFlexDbContex.cs | 2 + .../Repositories/BookingRepository.cs | 16 ++ .../Repositories/UnitOfWork.cs | 11 + .../CineFlex.identity.csproj | 25 ++- .../CineFlex/CineFlex.identity/Class1.cs | 7 - .../Configurations/RoleConfiguration.cs | 33 +++ .../Configurations/UserConfiguration.cs | 41 ++++ .../Configurations/UserRoleConfiguration.cs | 26 +++ .../IdentityServiceRegistration.cs | 53 +++++ .../CineFlex/CineFlex.identity/JwtSettings.cs | 16 ++ .../Repositories/AuthRepository.cs | 190 ++++++++++++++++++ .../CineFlex.identity/UserDbContext.cs | 24 +++ .../backend_assessment/CineFlex/CineFlex.sln | 17 +- 46 files changed, 1096 insertions(+), 16 deletions(-) create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/AuthController.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/BookingController.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Identity/IAuthRepository.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IBookingRepository.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Commands/SignInCommand.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Commands/SignUpCommand.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Handlers/SignInCommandHandler.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Handlers/SignUpCommandHandler.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignInForm.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignInResponse.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignUpForm.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignUpResponse.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/Validators/SignInFormDtoValidators.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/Validators/SignUpFormValidator.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/CQRS/Commands/CreateBookingCommand.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/CQRS/Handlers/CreateBookingCommandHandler.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/DTOs/CreateBookingDto.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/DTOs/Validators/CreateBookingDtoValidator.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Domain/BookingEntity.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Domain/CineFlex - Backup.Domain.csproj create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Domain/User.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/BookingRepository.cs delete mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/Class1.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/RoleConfiguration.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/UserConfiguration.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/UserRoleConfiguration.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/IdentityServiceRegistration.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/JwtSettings.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/Repositories/AuthRepository.cs create mode 100644 Backend/backend_assessment/CineFlex/CineFlex.identity/UserDbContext.cs diff --git a/Backend/backend_assessment/CineFlex/CineFlex.API/CineFlex.API.csproj b/Backend/backend_assessment/CineFlex/CineFlex.API/CineFlex.API.csproj index 8471c9b1d..ff91f31df 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.API/CineFlex.API.csproj +++ b/Backend/backend_assessment/CineFlex/CineFlex.API/CineFlex.API.csproj @@ -19,6 +19,7 @@ + diff --git a/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/AuthController.cs b/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/AuthController.cs new file mode 100644 index 000000000..1fd950249 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/AuthController.cs @@ -0,0 +1,40 @@ +using CineFlex.Application.Features.Authentication.CQRS.Commands; +using CineFlex.Application.Features.Authentication.DTOs; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace CineFlex.API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class AuthController : BaseApiController + { + private readonly IMediator _mediator; + + public AuthController(IMediator mediator) + { + _mediator = mediator; + } + + + [HttpPost("signup")] + public async Task Register(SignupFormDto signupFormDto) + { + var command = new SignUpCommand { SignupFormDto = signupFormDto }; + var response = await _mediator.Send(command); + return HandleResult(response); + } + + [HttpPost("signin")] + public async Task Login(SigninFormDto signinFormDto) + { + var command = new SigninCommand { SigninFormDto = signinFormDto }; + var response = await _mediator.Send(command); + return HandleResult(response); + } + + + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/BookingController.cs b/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/BookingController.cs new file mode 100644 index 000000000..e11fff707 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/BookingController.cs @@ -0,0 +1,34 @@ +using CineFlex.Application.Features.Authentication.CQRS.Commands; +using CineFlex.Application.Features.Authentication.DTOs; +using CineFlex.Application.Features.Booking.CQRS.Commands; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace CineFlex.API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class BookingController : BaseApiController + { + private readonly IMediator _mediator; + + public BookingController(IMediator mediator) + { + _mediator = mediator; + } + + + [HttpPost("book")] + [Authorize] + public async Task Book([FromBody] CreateBookingCommand bookCommand) + { + bookCommand.createBookingDto.UserId = User.Claims.FirstOrDefault(c => c.Type == "UserId").Value; + var response = await _mediator.Send(bookCommand); + return Ok(response); + } + + + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/SeatController.cs b/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/SeatController.cs index 0f8ad8260..09e6a942f 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/SeatController.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.API/Controllers/SeatController.cs @@ -6,6 +6,7 @@ using CineFlex.Application.Features.Seats.CQRS.Queries; using CineFlex.Application.Features.Seats.DTOs; using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace CineFlex.API.Controllers @@ -27,19 +28,25 @@ public async Task>> Get() [HttpPost("CreateSeat")] + [Authorize(Roles = "Admin")] public async Task Post([FromBody] CreateSeatDto createSeatDto) { var command = new CreateSeatCommand { SeatDto = createSeatDto }; return HandleResult(await _mediator.Send(command)); } + + [HttpPut("UpdateSeat")] + [Authorize(Roles = "Admin")] public async Task Put([FromBody] UpdateSeatDto updateSeatDto) { var command = new UpdateSeatCommand { updateSeatDto = updateSeatDto }; await _mediator.Send(command); return NoContent(); } + [HttpDelete("{id}")] + [Authorize(Roles = "Admin")] public async Task Delete(int id) { var command = new DeleteSeatCommand { Id = id }; diff --git a/Backend/backend_assessment/CineFlex/CineFlex.API/Program.cs b/Backend/backend_assessment/CineFlex/CineFlex.API/Program.cs index 4e1afd370..7c3ade1bc 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.API/Program.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.API/Program.cs @@ -2,6 +2,7 @@ using CineFlex.Persistence; using Microsoft.OpenApi.Models; using Microsoft.AspNetCore.Identity; +using CineFlex.identity; var builder = WebApplication.CreateBuilder(args); @@ -9,6 +10,7 @@ builder.Services.ConfigureApplicationServices(); builder.Services.ConfigurePersistenceServices(builder.Configuration); builder.Services.AddHttpContextAccessor(); +builder.Services.ConfigureIdentityServices(builder.Configuration); AddSwaggerDoc(builder.Services); builder.Services.AddControllers(); diff --git a/Backend/backend_assessment/CineFlex/CineFlex.API/appsettings.json b/Backend/backend_assessment/CineFlex/CineFlex.API/appsettings.json index 2b720c223..0ebd3b317 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.API/appsettings.json +++ b/Backend/backend_assessment/CineFlex/CineFlex.API/appsettings.json @@ -8,6 +8,12 @@ "Microsoft.AspNetCore": "Warning" } }, + "JwtSettings": { + "Key": "%%%adaaskldfasldkfjalskdf", + "Issuer": "BlogApp.Api", + "Audience": "BlogAppUser", + "DurationInMinutes": 600 + }, "AllowedHosts": "*", "compilationOptions": { "emitEntryPoint": true, diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application.UnitTest/CineFlex.Application.UnitTest.csproj b/Backend/backend_assessment/CineFlex/CineFlex.Application.UnitTest/CineFlex.Application.UnitTest.csproj index 391cc02f8..91d9159a2 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Application.UnitTest/CineFlex.Application.UnitTest.csproj +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application.UnitTest/CineFlex.Application.UnitTest.csproj @@ -30,6 +30,7 @@ + diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/CineFlex.Application.csproj b/Backend/backend_assessment/CineFlex/CineFlex.Application/CineFlex.Application.csproj index 117b85cd5..16cfafae1 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Application/CineFlex.Application.csproj +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/CineFlex.Application.csproj @@ -16,7 +16,7 @@ - + diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Identity/IAuthRepository.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Identity/IAuthRepository.cs new file mode 100644 index 000000000..9533c3fda --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Identity/IAuthRepository.cs @@ -0,0 +1,16 @@ +using CineFlex.Application.Features.Authentication.DTOs; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Contracts.Identity +{ + public interface IAuthRepository + { + Task SignUpAsync(SignupFormDto signUpFormDto); + Task SignInAsync(SigninFormDto signInFormDto); + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IBookingRepository.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IBookingRepository.cs new file mode 100644 index 000000000..fd8c2da52 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IBookingRepository.cs @@ -0,0 +1,13 @@ +using CineFlex.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Contracts.Persistence +{ + public interface IBookingRepository : IGenericRepository + { + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IUnitOfWork.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IUnitOfWork.cs index 20dc30105..cf965b09c 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IUnitOfWork.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Contracts/Persistence/IUnitOfWork.cs @@ -12,6 +12,7 @@ public interface IUnitOfWork : IDisposable ISeatRepository SeatRepository { get; } IMovieRepository MovieRepository { get; } ICinemaRepository CinemaRepository { get; } + IBookingRepository BookingRepository { get; } Task Save(); } diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Commands/SignInCommand.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Commands/SignInCommand.cs new file mode 100644 index 000000000..3202707fb --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Commands/SignInCommand.cs @@ -0,0 +1,16 @@ +using CineFlex.Application.Features.Authentication.DTOs; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.CQRS.Commands +{ + public class SigninCommand : IRequest> + { + public SigninFormDto SigninFormDto { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Commands/SignUpCommand.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Commands/SignUpCommand.cs new file mode 100644 index 000000000..6d5022812 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Commands/SignUpCommand.cs @@ -0,0 +1,16 @@ +using CineFlex.Application.Features.Authentication.DTOs; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.CQRS.Commands +{ + public class SignUpCommand : IRequest> + { + public SignupFormDto SignupFormDto { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Handlers/SignInCommandHandler.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Handlers/SignInCommandHandler.cs new file mode 100644 index 000000000..0f9fd562e --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Handlers/SignInCommandHandler.cs @@ -0,0 +1,54 @@ +using CineFlex.Application.Contracts.Identity; +using CineFlex.Application.Features.Authentication.CQRS.Commands; +using CineFlex.Application.Features.Authentication.DTOs; +using CineFlex.Application.Features.Authentication.DTOs.Validators; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.CQRS.Handlers +{ + public class SigninHandler : IRequestHandler> + { + private readonly IAuthRepository _authenticationRepo; + public SigninHandler(IAuthRepository authRepository) + { + _authenticationRepo = authRepository; + } + + public async Task> Handle(SigninCommand request, CancellationToken cancellationToken) + { + var validator = new SignInFormDtoValidators(); + var response = new BaseCommandResponse(); + var validationResult = await validator.ValidateAsync(request.SigninFormDto); + if (validationResult.IsValid == true) + { + try + { + var signInResponse = await _authenticationRepo.SignInAsync(request.SigninFormDto); + response.Success = true; + response.Value = signInResponse; + response.Message = "User Signed In Successfully"; + } + catch (Exception e) + { + response.Success = false; + response.Message = "User Sign In Failed"; + response.Errors = new List() { e.Message }; + } + } + else + { + response.Success = false; + response.Message = "User Sign In Failed"; + response.Errors = validationResult.Errors.Select(e => e.ErrorMessage).ToList(); + } + + return response; + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Handlers/SignUpCommandHandler.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Handlers/SignUpCommandHandler.cs new file mode 100644 index 000000000..2acd56e16 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/CQRS/Handlers/SignUpCommandHandler.cs @@ -0,0 +1,54 @@ +using CineFlex.Application.Contracts.Identity; +using CineFlex.Application.Features.Authentication.CQRS.Commands; +using CineFlex.Application.Features.Authentication.DTOs; +using CineFlex.Application.Features.Authentication.DTOs.Validators; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.CQRS.Handlers +{ + public class SignupHandler : IRequestHandler> + { + private readonly IAuthRepository _authRepository; + public SignupHandler(IAuthRepository authRepository) + { + _authRepository = authRepository; + } + + public async Task> Handle(SignUpCommand request, CancellationToken cancellationToken) + { + var validator = new SignUpFormValidator(); + var response = new BaseCommandResponse(); + var validationResult = await validator.ValidateAsync(request.SignupFormDto); + if (validationResult.IsValid == true) + { + try + { + var signUpResponse = await _authRepository.SignUpAsync(request.SignupFormDto); + response.Success = true; + response.Value = signUpResponse; + response.Message = "User Registered Successfully"; + } + catch (Exception e) + { + response.Success = false; + response.Message = "User Registration Failed"; + response.Errors = new List() { e.Message }; + } + } + else + { + response.Success = false; + response.Message = "User Registration Failed"; + response.Errors = validationResult.Errors.Select(e => e.ErrorMessage).ToList(); + } + + return response; + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignInForm.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignInForm.cs new file mode 100644 index 000000000..777e707f7 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignInForm.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.DTOs +{ + public class SigninFormDto + { + public string Email { get; set; } + public string Password { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignInResponse.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignInResponse.cs new file mode 100644 index 000000000..192fed4a4 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignInResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.DTOs +{ + public class SignInResponse : SignUpResponse + { + public string Token { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignUpForm.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignUpForm.cs new file mode 100644 index 000000000..237e8eb6c --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignUpForm.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.DTOs +{ + public class SignupFormDto : SigninFormDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignUpResponse.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignUpResponse.cs new file mode 100644 index 000000000..4ec0847b5 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/SignUpResponse.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.DTOs +{ + public class SignUpResponse + { + public string Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/Validators/SignInFormDtoValidators.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/Validators/SignInFormDtoValidators.cs new file mode 100644 index 000000000..35372cea3 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/Validators/SignInFormDtoValidators.cs @@ -0,0 +1,27 @@ +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.DTOs.Validators +{ + public class SignInFormDtoValidators : AbstractValidator + { + public SignInFormDtoValidators() + { + RuleFor(p => p.Email) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull() + .Matches(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$") + .EmailAddress().WithMessage("{PropertyName} must be a valid email address."); + + RuleFor(p => p.Password) + .NotEmpty().WithMessage("{PropertyName} is required.") + .Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,15}$") + .WithMessage("{PropertyName} must be 8 to 15 characters long and contain at least one uppercase letter, one lowercase letter, one numeric digit, and one special character.") + .NotNull(); + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/Validators/SignUpFormValidator.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/Validators/SignUpFormValidator.cs new file mode 100644 index 000000000..16fe2e645 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Authentication/DTOs/Validators/SignUpFormValidator.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Authentication.DTOs.Validators +{ + public class SignUpFormValidator : AbstractValidator + { + public SignUpFormValidator() + { + RuleFor(p => p.FirstName) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull() + .MaximumLength(50).WithMessage("{PropertyName} must not exceed {ComparisonValue} characters."); + + RuleFor(p => p.LastName) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull() + .MaximumLength(50).WithMessage("{PropertyName} must not exceed {ComparisonValue} characters."); + + Include(new SignInFormDtoValidators()); + + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/CQRS/Commands/CreateBookingCommand.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/CQRS/Commands/CreateBookingCommand.cs new file mode 100644 index 000000000..32361f256 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/CQRS/Commands/CreateBookingCommand.cs @@ -0,0 +1,16 @@ +using CineFlex.Application.Features.Booking.DTOs; +using CineFlex.Application.Responses; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Booking.CQRS.Commands +{ + public class CreateBookingCommand : IRequest> + { + public CreateBookingDto createBookingDto { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/CQRS/Handlers/CreateBookingCommandHandler.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/CQRS/Handlers/CreateBookingCommandHandler.cs new file mode 100644 index 000000000..6f0d08a25 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/CQRS/Handlers/CreateBookingCommandHandler.cs @@ -0,0 +1,65 @@ +using AutoMapper; +using CineFlex.Application.Contracts.Persistence; +using CineFlex.Application.Features.Booking.CQRS.Commands; +using CineFlex.Application.Features.Booking.DTOs.Validators; +using CineFlex.Application.Features.Cinema.CQRS.Commands; +using CineFlex.Application.Features.Cinema.DTO.Validators; +using CineFlex.Application.Responses; +using CineFlex.Domain; +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Booking.CQRS.Handlers +{ + public class CreateBookingCommandHandler : IRequestHandler> + { + private readonly IMapper _mapper; + private readonly IUnitOfWork _unitOfWork; + + public CreateBookingCommandHandler(IUnitOfWork unitOfWork, IMapper mapper) + { + _mapper = mapper; + _unitOfWork = unitOfWork; + } + + public async Task> Handle(CreateBookingCommand request, CancellationToken cancellationToken) + { + var response = new BaseCommandResponse(); + var validator = new CreateBookingDtoValidator(_unitOfWork); + var validationResult = await validator.ValidateAsync(request.createBookingDto); + if (validationResult.IsValid == false) + { + response.Success = false; + response.Message = "Creation Failed"; + response.Errors = validationResult.Errors.Select(q => q.ErrorMessage).ToList(); + + } + else + { + var booking = _mapper.Map(request.createBookingDto); + booking = await _unitOfWork.BookingRepository.Add(booking); + + + if (await _unitOfWork.Save() > 0) + { + response.Success = true; + response.Message = "Creation Successfull"; + response.Value = booking.Id; + } + else + { + response.Success = false; + response.Message = "Creation Failed"; + + } + + } + return response; + } + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/DTOs/CreateBookingDto.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/DTOs/CreateBookingDto.cs new file mode 100644 index 000000000..86b87f300 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/DTOs/CreateBookingDto.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Booking.DTOs +{ + public class CreateBookingDto + { + public string? UserId { get; set; } + public int MovieId { get; set; } + public int CinemaId { get; set; } + public List SeatIds { get; set; } + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/DTOs/Validators/CreateBookingDtoValidator.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/DTOs/Validators/CreateBookingDtoValidator.cs new file mode 100644 index 000000000..1e707f452 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Features/Booking/DTOs/Validators/CreateBookingDtoValidator.cs @@ -0,0 +1,96 @@ +using CineFlex.Application.Contracts.Persistence; +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Application.Features.Booking.DTOs.Validators +{ + public class CreateBookingDtoValidator : AbstractValidator + { + private readonly IUnitOfWork _unitOfWork; + public CreateBookingDtoValidator(IUnitOfWork unitOfWork) + { + _unitOfWork = unitOfWork; + RuleFor(b => b.MovieId) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull() + .MustAsync(MovieMustExist); + + RuleFor(b => b.SeatIds) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull() + .MustAsync(SeatMustExist); + + RuleFor(b => b.CinemaId) + .NotEmpty().WithMessage("{PropertyName} is required.") + .NotNull() + .MustAsync(CinemaMustExist); + + RuleFor(b => b.SeatIds) + .Must(seats => seats.Count() > 0).WithMessage("{PropertyName} is required.") + .NotNull(); + + RuleFor(b => b.SeatIds) + .Must(seats => seats.Count() <= 10).WithMessage("{PropertyName} must be less than or equal to 10.") + .NotNull(); + + RuleFor(b => b.SeatIds) + .Must(seats => seats.Count() == seats.Distinct().Count()).WithMessage("{PropertyName} must be unique.") + .NotNull(); + + + // check if there is no booking for the same seat in the same movie + RuleFor(b => b.SeatIds) + .MustAsync(SeatMustBeAvailable).WithMessage("{PropertyName} is not available.") + .NotNull(); + + // all seats should be from the same cinema + RuleFor(b => b.SeatIds) + .MustAsync(SeatsMustBeFromSameCinema).WithMessage("{PropertyName} must be from the same cinema.") + .NotNull(); + + + } + private async Task MovieMustExist(int id, CancellationToken arg2) + { + var movie = await _unitOfWork.MovieRepository.Get(id); + return movie != null; + } + + private async Task CinemaMustExist(int id, CancellationToken arg2) + { + var cinema = await _unitOfWork.MovieRepository.Get(id); + return cinema != null; + } + + private async Task SeatMustExist(List ids, CancellationToken arg2) + { + var seats = await _unitOfWork.SeatRepository.GetAll(); + var seatIds = seats.Select(s => s.Id).ToList(); + return ids.All(id => seatIds.Contains(id)); + } + + private async Task SeatMustBeAvailable(List ids, CancellationToken arg2) + { + var bookings = await _unitOfWork.BookingRepository.GetAll(); + var seatIds = bookings.SelectMany(b => b.SeatIds).ToList(); + return ids.All(id => !seatIds.Contains(id)); + } + + private async Task SeatsMustBeFromSameCinema(List ids, CancellationToken arg2) + { + var seats = await _unitOfWork.SeatRepository.GetAll(); + var seatIds = seats.Select(s => s.Id).ToList(); + var bookings = await _unitOfWork.BookingRepository.GetAll(); + var seatIdsInBookings = bookings.SelectMany(b => b.SeatIds).ToList(); + var allSeatIds = seatIds.Concat(seatIdsInBookings).ToList(); + var seatsInCinema = seats.Where(s => s.Cinema.Id == ids[0]).Select(s => s.Id).ToList(); + return ids.All(id => seatsInCinema.Contains(id)); + } + + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Application/Profiles/MappingProfile.cs b/Backend/backend_assessment/CineFlex/CineFlex.Application/Profiles/MappingProfile.cs index 99b3f5b38..044bda9c1 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Application/Profiles/MappingProfile.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Application/Profiles/MappingProfile.cs @@ -10,6 +10,8 @@ using System.Threading.Tasks; using CineFlex.Application.Features.Seats.DTOs.Validators; using CineFlex.Application.Features.Seats.DTOs; +using CineFlex.Application.Features.Booking.DTOs; +using CineFlex.Application.Features.Authentication.DTOs; namespace CineFlex.Application.Profiles { @@ -32,7 +34,12 @@ public MappingProfile() CreateMap().ReverseMap(); CreateMap().ReverseMap(); - + CreateMap().ReverseMap(); + + #region Authentication Mappings + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + #endregion Authentication Mappings } } } diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Domain/BookingEntity.cs b/Backend/backend_assessment/CineFlex/CineFlex.Domain/BookingEntity.cs new file mode 100644 index 000000000..8902bcffd --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Domain/BookingEntity.cs @@ -0,0 +1,23 @@ +using CineFlex.Domain.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Domain +{ + public class BookingEntity: BaseDomainEntity + { + public String UserId { get; set; } + public int MovieId { get; set; } + public int CinemaId { get; set; } + public List SeatIds { get; set; } + + // Navigation properties + public User User { get; set; } + public Movie Movie { get; set; } + public CinemaEntity Cinema { get; set; } + public List Seats { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Domain/CineFlex - Backup.Domain.csproj b/Backend/backend_assessment/CineFlex/CineFlex.Domain/CineFlex - Backup.Domain.csproj new file mode 100644 index 000000000..6f2258275 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Domain/CineFlex - Backup.Domain.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Domain/CineFlex.Domain.csproj b/Backend/backend_assessment/CineFlex/CineFlex.Domain/CineFlex.Domain.csproj index 132c02c59..42b67dd17 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Domain/CineFlex.Domain.csproj +++ b/Backend/backend_assessment/CineFlex/CineFlex.Domain/CineFlex.Domain.csproj @@ -1,9 +1,13 @@ - + net6.0 enable enable + + + + diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Domain/CinemaEntity.cs b/Backend/backend_assessment/CineFlex/CineFlex.Domain/CinemaEntity.cs index b70624cef..9ba1350c4 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Domain/CinemaEntity.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Domain/CinemaEntity.cs @@ -12,5 +12,8 @@ public class CinemaEntity:BaseDomainEntity public string Name { get; set; } public string Location { get; set; } public string ContactInformation { get; set; } + + // Navigational Property + public ICollection Bookings { get; set; } } } diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Domain/Movie.cs b/Backend/backend_assessment/CineFlex/CineFlex.Domain/Movie.cs index b6f39be96..23903f775 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Domain/Movie.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Domain/Movie.cs @@ -13,5 +13,8 @@ public class Movie: BaseDomainEntity public string Genre { get; set; } public string ReleaseYear { get; set; } + //Navigational Property + public ICollection Bookings { get; set; } + } } diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Domain/Seat.cs b/Backend/backend_assessment/CineFlex/CineFlex.Domain/Seat.cs index 72eef3cef..229cbf881 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Domain/Seat.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Domain/Seat.cs @@ -12,5 +12,12 @@ public class Seat:BaseDomainEntity public int SeatNumber { get; set; } public int RowNumber { get; set; } public DateTime lastBooked { get; set; } + public CinemaEntity Cinema { get; set; } + + // Navigational Property + public ICollection Bookings { get; set; } + + + } } diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Domain/User.cs b/Backend/backend_assessment/CineFlex/CineFlex.Domain/User.cs new file mode 100644 index 000000000..c9c95c71f --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Domain/User.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Identity; + +namespace CineFlex.Domain +{ + public class User : IdentityUser + { + public String FirstName { get; set; } + public String LastName { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/CineFlexDbContex.cs b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/CineFlexDbContex.cs index fe5068fad..1e7027579 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/CineFlexDbContex.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/CineFlexDbContex.cs @@ -46,5 +46,7 @@ public override Task SaveChangesAsync(CancellationToken cancellationToken = public DbSet Seats { get; set; } + public DbSet Bookings { get; set; } + } } diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/BookingRepository.cs b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/BookingRepository.cs new file mode 100644 index 000000000..72dab71ea --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/BookingRepository.cs @@ -0,0 +1,16 @@ +using CineFlex.Application.Contracts.Persistence; +using CineFlex.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.Persistence.Repositories +{ + public class BookingRepository: GenericRepository, IBookingRepository + { + public BookingRepository(CineFlexDbContex dbContext) : base(dbContext) { } + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/UnitOfWork.cs b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/UnitOfWork.cs index 23391a24e..ace00fb87 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/UnitOfWork.cs +++ b/Backend/backend_assessment/CineFlex/CineFlex.Persistence/Repositories/UnitOfWork.cs @@ -15,6 +15,7 @@ public class UnitOfWork : IUnitOfWork private IMovieRepository _MovieRepository; private ISeatRepository _SeatRepository; private ICinemaRepository _cinemaRepository; + private IBookingRepository _bookingRepository; public UnitOfWork(CineFlexDbContex context) { _context = context; @@ -49,6 +50,16 @@ public ISeatRepository SeatRepository } } + public IBookingRepository BookingRepository + { + get + { + if (_bookingRepository == null) + _bookingRepository = new BookingRepository(_context); + return _bookingRepository; + } + } + public void Dispose() { _context.Dispose(); diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/CineFlex.identity.csproj b/Backend/backend_assessment/CineFlex/CineFlex.identity/CineFlex.identity.csproj index 132c02c59..084144fdd 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.identity/CineFlex.identity.csproj +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/CineFlex.identity.csproj @@ -1,9 +1,32 @@ - + net6.0 enable enable + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/Class1.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/Class1.cs deleted file mode 100644 index 64317c025..000000000 --- a/Backend/backend_assessment/CineFlex/CineFlex.identity/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CineFlex.identity -{ - public class Class1 - { - - } -} \ No newline at end of file diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/RoleConfiguration.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/RoleConfiguration.cs new file mode 100644 index 000000000..27598913f --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/RoleConfiguration.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using Microsoft.AspNetCore.Identity; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.identity.Configurations +{ + public class RoleConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasData( + new IdentityRole + { + Id = "cac43a6e-f7bb-4448-baaf-1add431ccbbf", + Name = "User", + NormalizedName = "USER" + }, + new IdentityRole + { + Id = "cbc43a8e-f7bb-4445-baaf-1add431ffbbf", + Name = "Admin", + NormalizedName = "ADMIN" + } + ); + } + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/UserConfiguration.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/UserConfiguration.cs new file mode 100644 index 000000000..75cd90789 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/UserConfiguration.cs @@ -0,0 +1,41 @@ +using CineFlex.Domain; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CineFlex.identity.Configurations +{ + public class UserConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var hasher = new PasswordHasher(); + builder.HasData( + new User + { + Id = "8e445865-a24d-4543-a6c6-9443d048cdb9", + Email = "admin@localhost.com", + NormalizedEmail = "ADMIN@LOCALHOST.COM", + FirstName = "System", + LastName = "Admin", + UserName = "admin@localhost.com", + NormalizedUserName = "ADMIN@LOCALHOST.COM", + PasswordHash = hasher.HashPassword(null, "P@ssword1"), + EmailConfirmed = true + }, + new User + { + Id = "9e224968-33e4-4652-b7b7-8574d048cdb9", + Email = "user@localhost.com", + NormalizedEmail = "USER@LOCALHOST.COM", + FirstName = "System", + LastName = "User", + UserName = "user@localhost.com", + NormalizedUserName = "USER@LOCALHOST.COM", + PasswordHash = hasher.HashPassword(null, "P@ssword1"), + EmailConfirmed = true + } + ); + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/UserRoleConfiguration.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/UserRoleConfiguration.cs new file mode 100644 index 000000000..3ba545729 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/Configurations/UserRoleConfiguration.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + + +namespace CineFlex.identity.Configurations +{ + public class UserRoleConfiguration : IEntityTypeConfiguration> + { + public void Configure(EntityTypeBuilder> builder) + { + builder.HasData( + new IdentityUserRole + { + RoleId = "cbc43a8e-f7bb-4445-baaf-1add431ffbbf", + UserId = "8e445865-a24d-4543-a6c6-9443d048cdb9" + }, + new IdentityUserRole + { + RoleId = "cac43a6e-f7bb-4448-baaf-1add431ccbbf", + UserId = "9e224968-33e4-4652-b7b7-8574d048cdb9" + } + ); + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/IdentityServiceRegistration.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/IdentityServiceRegistration.cs new file mode 100644 index 000000000..be0911efb --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/IdentityServiceRegistration.cs @@ -0,0 +1,53 @@ +using CineFlex.Application.Contracts.Identity; +using CineFlex.Domain; +using CineFlex.identity.Repositories; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System.Text; + +namespace CineFlex.identity +{ + public static class IdentityServicesRegistration + { + public static IServiceCollection ConfigureIdentityServices(this IServiceCollection services, IConfiguration configuration) + { + + services.AddDbContext(opt => + opt.UseNpgsql(configuration.GetConnectionString("BlogAppConnectionString"))); + services.Configure(configuration.GetSection("JwtSettings")); + services.Configure(opt => opt.SignIn.RequireConfirmedEmail = true); + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero, + ValidIssuer = configuration.GetValue("JwtSettings:Issuer"), + ValidAudience = configuration.GetValue("JwtSettings:Audience"), + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetValue("JwtSettings:Key"))) + }; + }); + + services.AddHttpContextAccessor(); + services.AddSingleton(); + services.AddScoped(); + return services; + } + } +} \ No newline at end of file diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/JwtSettings.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/JwtSettings.cs new file mode 100644 index 000000000..808d70dce --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/JwtSettings.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.identity +{ + public class JwtSettings + { + public string Key { get; set; } + public string Issuer { get; set; } + public string Audience { get; set; } + public double DurationInMinutes { get; set; } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/Repositories/AuthRepository.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/Repositories/AuthRepository.cs new file mode 100644 index 000000000..8dfd67086 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/Repositories/AuthRepository.cs @@ -0,0 +1,190 @@ +using AutoMapper; +using CineFlex.Application.Contracts.Identity; +using CineFlex.Application.Exceptions; +using CineFlex.Application.Features.Authentication.DTOs; +using CineFlex.Domain; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.identity.Repositories +{ + public class AuthRepository : IAuthRepository + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly JwtSettings _jwtSettings; + private readonly IMapper _mapper; + private readonly IConfiguration _configuration; + private readonly UserIdentityDbContext _context; + + public AuthRepository(UserManager userManager, + SignInManager signInManager, + IMapper mapper, + IConfiguration configuration, + UserIdentityDbContext context, + IOptions jwtSettings) + { + _userManager = userManager; + _signInManager = signInManager; + _mapper = mapper; + _configuration = configuration; + _context = context; + _jwtSettings = jwtSettings.Value; + } + + // Delete user + public async Task DeleteUserAsync(string userId) + { + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + throw new NotFoundException(nameof(User), userId); + } + var result = await _userManager.DeleteAsync(user); + return result.Succeeded; + } + + public async Task SignInAsync(SigninFormDto signInFormDto) + { + + + var userExist = await _userManager.FindByEmailAsync(signInFormDto.Email); + if (userExist != null) + { + var result = await _signInManager.CheckPasswordSignInAsync(userExist, signInFormDto.Password, false); + + if (result.Succeeded) + { + var token = await GenerateToken(userExist); + + return new SignInResponse + { + Id = userExist.Id, + Email = userExist.Email, + FirstName = userExist.FirstName, + LastName = userExist.LastName, + Token = new JwtSecurityTokenHandler().WriteToken(token), + }; + } + else + throw new BadRequestException("Email confiramtion is required"); + } + throw new BadRequestException("Invalid Credential"); + + } + + + + + + public async Task SignUpAsync(SignupFormDto signUpFormDto) + { + using (var transaction = _context.Database.BeginTransaction()) + { + try + { + var userExist = await _userManager.FindByEmailAsync(signUpFormDto.Email); + if (userExist != null) + { + throw new BadRequestException("Email is already used!"); + } + + var user = _mapper.Map(signUpFormDto); + user.UserName = signUpFormDto.Email; + var result = await _userManager.CreateAsync(user, signUpFormDto.Password); + + if (result.Succeeded) + { + var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); + + var createdUser = await _userManager.FindByEmailAsync(user.Email); + var response = _mapper.Map(createdUser); + await transaction.CommitAsync(); + return response; + } + throw new BadRequestException("Unabe to creat user"); + + + } + catch (Exception e) + { + transaction.Rollback(); + throw new Exception("Something went wrong"); + } + } + } + + + + + // get token + private async Task GenerateToken(User user) + { + var userClaims = await _userManager.GetClaimsAsync(user); + var roles = await _userManager.GetRolesAsync(user); + + var roleClaims = roles.Select(q => new Claim(ClaimTypes.Role, q)).ToList(); + + var claims = new[] + { + new Claim(JwtRegisteredClaimNames.Sub, user.UserName), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(JwtRegisteredClaimNames.Email, user.Email), + new Claim("uid", user.Id) + } + .Union(userClaims) + .Union(roleClaims); + + + var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetValue("JwtSettings:Key"))); + var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256); + var jwtSecurityToken = new JwtSecurityToken( + issuer: _configuration.GetValue("JwtSettings:Issuer"), + audience: _configuration.GetValue("JwtSettings:Audience"), + claims: claims, + expires: DateTime.Now.AddMinutes(_configuration.GetValue("JwtSettings:DurationInMinutes")), + signingCredentials: signingCredentials); + return jwtSecurityToken; + } + + + // get user from token + private async Task validateToken(string token) + { + if (token == null) + return null; + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_jwtSettings.Key); + try + { + tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false, + ClockSkew = TimeSpan.Zero + }, out SecurityToken validatedToken); + + var jwtToken = (JwtSecurityToken)validatedToken; + var userId = jwtToken.Claims.First(x => x.Type == "id").Value; + return userId; + } + catch + { + return null; + } + + } + + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.identity/UserDbContext.cs b/Backend/backend_assessment/CineFlex/CineFlex.identity/UserDbContext.cs new file mode 100644 index 000000000..8db7b1057 --- /dev/null +++ b/Backend/backend_assessment/CineFlex/CineFlex.identity/UserDbContext.cs @@ -0,0 +1,24 @@ +using CineFlex.Domain; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace CineFlex.identity +{ + public class UserIdentityDbContext : IdentityDbContext + { + public UserIdentityDbContext(DbContextOptions options) + : base(options) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(typeof(UserIdentityDbContext).Assembly); + } + } +} diff --git a/Backend/backend_assessment/CineFlex/CineFlex.sln b/Backend/backend_assessment/CineFlex/CineFlex.sln index 22cdfded1..a842f1579 100644 --- a/Backend/backend_assessment/CineFlex/CineFlex.sln +++ b/Backend/backend_assessment/CineFlex/CineFlex.sln @@ -7,15 +7,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7B1B95A0-9DB EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F5977BC8-191D-48B9-8833-80091338052F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CineFlex.Domain", "CineFlex.Domain\CineFlex.Domain.csproj", "{50EEE1CA-423A-4F8C-8AEB-8F21104E48F5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CineFlex.Domain", "CineFlex.Domain\CineFlex.Domain.csproj", "{50EEE1CA-423A-4F8C-8AEB-8F21104E48F5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CineFlex.Application.UnitTest", "CineFlex.Application.UnitTest\CineFlex.Application.UnitTest.csproj", "{6C23CA30-5F1B-4733-9298-0221D22BC142}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CineFlex.Application.UnitTest", "CineFlex.Application.UnitTest\CineFlex.Application.UnitTest.csproj", "{6C23CA30-5F1B-4733-9298-0221D22BC142}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CineFlex.Application", "CineFlex.Application\CineFlex.Application.csproj", "{104F9AAF-4CC1-4578-9061-146FF1315A64}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CineFlex.Application", "CineFlex.Application\CineFlex.Application.csproj", "{104F9AAF-4CC1-4578-9061-146FF1315A64}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CineFlex.Persistence", "CineFlex.Persistence\CineFlex.Persistence.csproj", "{37F60AFF-7E17-4EC3-A20A-042C6F59F1D9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CineFlex.Persistence", "CineFlex.Persistence\CineFlex.Persistence.csproj", "{37F60AFF-7E17-4EC3-A20A-042C6F59F1D9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CineFlex.API", "CineFlex.API\CineFlex.API.csproj", "{8D82F498-631C-450B-982F-E7333EB92D7C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CineFlex.API", "CineFlex.API\CineFlex.API.csproj", "{8D82F498-631C-450B-982F-E7333EB92D7C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CineFlex.identity", "CineFlex.identity\CineFlex.identity.csproj", "{836D5F1B-FDD0-48FA-B0A2-D2C280774F2F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -43,6 +45,10 @@ Global {8D82F498-631C-450B-982F-E7333EB92D7C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D82F498-631C-450B-982F-E7333EB92D7C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D82F498-631C-450B-982F-E7333EB92D7C}.Release|Any CPU.Build.0 = Release|Any CPU + {836D5F1B-FDD0-48FA-B0A2-D2C280774F2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {836D5F1B-FDD0-48FA-B0A2-D2C280774F2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {836D5F1B-FDD0-48FA-B0A2-D2C280774F2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {836D5F1B-FDD0-48FA-B0A2-D2C280774F2F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -53,6 +59,7 @@ Global {104F9AAF-4CC1-4578-9061-146FF1315A64} = {7B1B95A0-9DBB-4C5D-82E6-3895BCF66EE8} {37F60AFF-7E17-4EC3-A20A-042C6F59F1D9} = {7B1B95A0-9DBB-4C5D-82E6-3895BCF66EE8} {8D82F498-631C-450B-982F-E7333EB92D7C} = {7B1B95A0-9DBB-4C5D-82E6-3895BCF66EE8} + {836D5F1B-FDD0-48FA-B0A2-D2C280774F2F} = {7B1B95A0-9DBB-4C5D-82E6-3895BCF66EE8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {30B8E19C-B56E-4DDD-8243-978C779E3BD4}