diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Browse/BrowseServiceTestsBase.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Browse/BrowseServiceTestsBase.cs index f4f1ae1d..a59c5f93 100644 --- a/Tests/TutorLizard.BusinessLogic.Tests/Services/Browse/BrowseServiceTestsBase.cs +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Browse/BrowseServiceTestsBase.cs @@ -1,4 +1,5 @@ using AutoFixture; +using Microsoft.Extensions.Logging; using Moq; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Models; @@ -12,10 +13,13 @@ public abstract class BrowseServiceTestsBase : TestsWithInMemoryDbBase protected Fixture Fixture = new(); protected Mock> MockAdRepository = new(); protected Mock> MockScheduleItemRepository = new(); + protected Mock> MockLogger = new(); protected BrowseServiceTestsBase() : base() { - BrowseService = new(MockAdRepository.Object, MockScheduleItemRepository.Object); + BrowseService = new(MockAdRepository.Object, + MockScheduleItemRepository.Object, + MockLogger.Object); } protected void SetupMockGetAllAds(List ads) diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs index 98408524..cdb80dfe 100644 --- a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs @@ -1,4 +1,5 @@ using AutoFixture; +using Microsoft.Extensions.Logging; using Moq; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Models; @@ -14,14 +15,15 @@ public class StudentServiceTestBase : TestsWithInMemoryDbBase protected Mock> MockAdRequestRepository = new(); protected Mock> MockScheduleItemRepository = new(); protected Mock> MockScheduleItemRequestRepository = new(); - + protected Mock> MockLogger = new(); protected StudentServiceTestBase() : base() { StudentService = new StudentService(MockAdRepository.Object, - MockAdRequestRepository.Object, - MockScheduleItemRepository.Object, - MockScheduleItemRequestRepository.Object); + MockAdRequestRepository.Object, + MockScheduleItemRepository.Object, + MockScheduleItemRequestRepository.Object, + MockLogger.Object); } protected void SetupMockGetScheduleItemById(ScheduleItem? scheduleItem) @@ -38,18 +40,5 @@ protected void SetupMockGetAllScheduleItems(List scheduleItems) .Setup(x => x.GetAll()) .Returns(scheduleItemsInDb); } - - protected IQueryable AddEntitiesToInMemoryDb(List entities) - where TEntity : class - { - DbContext - .Set() - .AddRange(entities); - DbContext.SaveChanges(); - - return DbContext - .Set() - .AsQueryable(); - } } } diff --git a/TutorLizard.BusinessLogic/Data/Repositories/DataBase/DbRepository.cs b/TutorLizard.BusinessLogic/Data/Repositories/DataBase/DbRepository.cs index 274f4ce2..ffeab5cb 100644 --- a/TutorLizard.BusinessLogic/Data/Repositories/DataBase/DbRepository.cs +++ b/TutorLizard.BusinessLogic/Data/Repositories/DataBase/DbRepository.cs @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using TutorLizard.BusinessLogic.Extensions; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; namespace TutorLizard.BusinessLogic.Data.Repositories.DataBase; @@ -7,10 +9,13 @@ public class DbRepository : IDbRepository where UDbContext : DbContext { private readonly UDbContext _dbContext; + private readonly ILogger> _logger; - public DbRepository(UDbContext dbContext) + public DbRepository(UDbContext dbContext, + ILogger> logger) { _dbContext = dbContext; + _logger = logger; } public IQueryable GetAll() @@ -21,8 +26,20 @@ public IQueryable GetAll() public async Task GetById(VId id) { - return await _dbContext.Set() + TEntity? entity = await _dbContext + .Set() .FindAsync(id); + + if (entity is null) + { + _logger.LogEntityNotFound(nameof(GetById), id); + } + else + { + _logger.LogEntityFound(nameof(GetById), id); + } + + return entity; } public async Task Create(TEntity entity) @@ -31,6 +48,9 @@ public async Task Create(TEntity entity) .Set() .Add(entity); await _dbContext.SaveChangesAsync(); + + _logger.LogEntityCreated(nameof(Create), GetEntityId(entity)); + return entity; } @@ -38,11 +58,16 @@ public async Task Create(TEntity entity) { TEntity? toUpdate = await GetById(id); if (toUpdate is null) + { + _logger.LogEntityNotUpdated(nameof(Update), id); return null; + } updateAction.Invoke(toUpdate); await _dbContext.SaveChangesAsync(); + _logger.LogEntityUpdated(nameof(Update), id); + return toUpdate; } @@ -50,12 +75,28 @@ public async Task Create(TEntity entity) { TEntity? toDelete = await GetById(id); if (toDelete is null) + { + _logger.LogEntityNotDeleted(nameof(Delete), id); return null; + } _dbContext.Set() .Remove(toDelete); await _dbContext.SaveChangesAsync(); + _logger.LogEntityDeleted(nameof(Delete), id); + return toDelete; } + + private object? GetEntityId(TEntity entity) + { + var entry = _dbContext.Entry(entity); + var id = entry.Metadata.FindPrimaryKey()? + .Properties + .Select(p => entry.Property(p.Name).CurrentValue) + .FirstOrDefault(); + + return id; + } } diff --git a/TutorLizard.BusinessLogic/Extensions/DtoExtensions.cs b/TutorLizard.BusinessLogic/Extensions/DtoExtensions.cs index 513ca581..e968dece 100644 --- a/TutorLizard.BusinessLogic/Extensions/DtoExtensions.cs +++ b/TutorLizard.BusinessLogic/Extensions/DtoExtensions.cs @@ -14,6 +14,5 @@ public static UserDto ToDto(this User user) user.Name, user.UserType, user.Email, - user.DateCreated, - user.GoogleId); + user.DateCreated); } diff --git a/TutorLizard.BusinessLogic/Extensions/LoggingExtensions.cs b/TutorLizard.BusinessLogic/Extensions/LoggingExtensions.cs new file mode 100644 index 00000000..d0036abf --- /dev/null +++ b/TutorLizard.BusinessLogic/Extensions/LoggingExtensions.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.Logging; +using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; + +namespace TutorLizard.BusinessLogic.Extensions; +internal static class LoggingExtensions +{ + internal static IDisposable? BeginMethodCallScope(this ILogger logger, string methodName, TRequest request, bool destructureRequest = true) + { + string scopeMessageFormat = $"Service: {{Service}} Method: {{ServiceMethod}} Request: {(destructureRequest ? "{@Request}" : "{Request}")}"; + var scope = logger.BeginScope(scopeMessageFormat, typeof(TService).Name, methodName, request); + + string logMessageFormat = $"[Service method call] Service: {{Service}} Method: {{ServiceMethod}} Request: {(destructureRequest ? "{@Request}" : "{Request}")}"; + logger.LogInformation(logMessageFormat, typeof(TService).Name, methodName, request); + + return scope; + } + + internal static void LogReturningResponse(this ILogger logger, TResponse response, bool destructureResponse = true) + { + string logMessageFormat = $"[Returning response] Response: {(destructureResponse ? "{@Response}" : "{Response}")}"; + logger.LogInformation(logMessageFormat, response); + } + + internal static void LogEntityCreated(this ILogger> logger, string methodName, TId id) + where TEntity : class + { + logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} created successfully with Id: {EntityId}", methodName, typeof(TEntity).Name, id); + } + + internal static void LogEntityNotCreated(this ILogger> logger, string methodName) + where TEntity : class + { + logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} not created", methodName, typeof(TEntity).Name); + } + + internal static void LogEntityUpdated(this ILogger> logger, string methodName, TId id) + where TEntity : class + { + logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} updated successfully", methodName, typeof(TEntity).Name, id); + } + + internal static void LogEntityNotUpdated(this ILogger> logger, string methodName, TId id) + where TEntity : class + { + logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not updated", methodName, typeof(TEntity).Name, id); + } + + internal static void LogEntityDeleted(this ILogger> logger, string methodName, TId id) + where TEntity : class + { + logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} deleted successfully", methodName, typeof(TEntity).Name, id); + } + + internal static void LogEntityNotDeleted(this ILogger> logger, string methodName, TId id) + where TEntity : class + { + logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not deleted", methodName, typeof(TEntity).Name, id); + } + + internal static void LogEntityFound(this ILogger> logger, string methodName, TId id) + where TEntity : class + { + logger.LogInformation("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} found", methodName, typeof(TEntity).Name, id); + } + + internal static void LogEntityNotFound(this ILogger> logger, string methodName, TId id) + where TEntity : class + { + logger.LogWarning("[DbRepository.{DBRepositoryMethod}] Entity of type: {EntityType} with Id: {EntityId} not found", methodName, typeof(TEntity).Name, id); + } +} diff --git a/TutorLizard.BusinessLogic/Services/BrowseService.cs b/TutorLizard.BusinessLogic/Services/BrowseService.cs index 3c1f4a60..bb078d49 100644 --- a/TutorLizard.BusinessLogic/Services/BrowseService.cs +++ b/TutorLizard.BusinessLogic/Services/BrowseService.cs @@ -6,21 +6,28 @@ using TutorLizard.Shared.Models.DTOs; using TutorLizard.Shared.Models.DTOs.Requests; using TutorLizard.Shared.Models.DTOs.Responses; +using Microsoft.Extensions.Logging; +using TutorLizard.BusinessLogic.Extensions; namespace TutorLizard.BusinessLogic.Services; public class BrowseService : IBrowseService { private readonly IDbRepository _adRepository; private readonly IDbRepository _scheduleItemRepository; + private readonly ILogger _logger; public BrowseService(IDbRepository adRepository, - IDbRepository _scheduleItemRepository) + IDbRepository _scheduleItemRepository, + ILogger logger) { _adRepository = adRepository; this._scheduleItemRepository = _scheduleItemRepository; + _logger = logger; } public async Task GetBrowseAdsPage(GetBrowseAdsPageRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetBrowseAdsPage), request); + if (request.PageSize < 1 || request.PageNumber < 1) { return new() @@ -64,11 +71,14 @@ public async Task GetBrowseAdsPage(GetBrowseAdsPageReq SearchCriteria = request.SearchCriteria }; + _logger.LogReturningResponse(response); return response; } public async Task GetAdDetails(GetAdDetailsRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetAdDetails), request); + GetAdDetailsResponse? response = await _adRepository.GetAll() .Where(a => a.Id == request.AdId) .Select(a => new GetAdDetailsResponse @@ -92,11 +102,14 @@ public async Task GetBrowseAdsPage(GetBrowseAdsPageReq }) .FirstOrDefaultAsync(); + _logger.LogReturningResponse(response); return response; } public async Task GetUsersSchedule(GetUsersScheduleRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetUsersSchedule), request); + List tutorsSchedule = await _scheduleItemRepository.GetAll() .Where(i => i.Ad.TutorId == request.UserId) .Select(i => new TutorsScheduleItemSummaryDto() @@ -136,6 +149,7 @@ public async Task GetUsersSchedule(GetUsersScheduleReq StudentsSchedule = studentsSchedule }; + _logger.LogReturningResponse(response); return response; } private IQueryable ApplySearchCriteria(IQueryable ads, AdSearchCriteriaDto searchCriteria) diff --git a/TutorLizard.BusinessLogic/Services/CategoryService.cs b/TutorLizard.BusinessLogic/Services/CategoryService.cs index ae7a7ef2..862505f9 100644 --- a/TutorLizard.BusinessLogic/Services/CategoryService.cs +++ b/TutorLizard.BusinessLogic/Services/CategoryService.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using TutorLizard.BusinessLogic.Extensions; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Interfaces.Services; @@ -11,23 +12,31 @@ namespace TutorLizard.BusinessLogic.Services; public class CategoryService : ICategoryService { private readonly IDbRepository _categoryRepository; + private readonly ILogger _logger; - public CategoryService(IDbRepository categoryRepository) + public CategoryService(IDbRepository categoryRepository, + ILogger logger) { _categoryRepository = categoryRepository; + _logger = logger; } public async Task GetCategories(GetCategoriesRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetCategories), request); + List categories = await _categoryRepository .GetAll() .Select(category => category.ToDto()) .ToListAsync(); - return new GetCategoriesResponse() + GetCategoriesResponse response = new() { Success = true, Categories = categories }; + + _logger.LogReturningResponse(response); + return response; } } diff --git a/TutorLizard.BusinessLogic/Services/StudentService.cs b/TutorLizard.BusinessLogic/Services/StudentService.cs index 10b663ca..11e9f33e 100644 --- a/TutorLizard.BusinessLogic/Services/StudentService.cs +++ b/TutorLizard.BusinessLogic/Services/StudentService.cs @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using TutorLizard.BusinessLogic.Extensions; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Interfaces.Services; using TutorLizard.BusinessLogic.Models; @@ -13,20 +15,26 @@ public class StudentService : IStudentService private readonly IDbRepository _adRequestRepository; private readonly IDbRepository _scheduleItemRepository; private readonly IDbRepository _scheduleItemRequestRepository; + private readonly ILogger _logger; + public StudentService(IDbRepository adRepository, IDbRepository adRequestRepository, IDbRepository scheduleItemRepository, - IDbRepository scheduleItemRequestRepository) + IDbRepository scheduleItemRequestRepository, + ILogger logger) { _adRepository = adRepository; _adRequestRepository = adRequestRepository; _scheduleItemRepository = scheduleItemRepository; _scheduleItemRequestRepository = scheduleItemRequestRepository; + _logger = logger; } #region Schedule public async Task CreateScheduleItemRequest(CreateScheduleItemRequestRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(CreateScheduleItemRequest), request); + int studentId = request.StudentId; int scheduleItemId = request.ScheduleItemId; @@ -37,10 +45,13 @@ public async Task CreateScheduleItemRequest(C if (isOwner) { - return new CreateScheduleItemRequestResponse + _logger.LogWarning("User requesting creation of ScheduleItemRequest is owner of the Ad. ScheduleItemRequest will not be created."); + CreateScheduleItemRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } var scheduleItemRequest = new ScheduleItemRequest() @@ -54,16 +65,19 @@ public async Task CreateScheduleItemRequest(C await _scheduleItemRequestRepository.Create(scheduleItemRequest); - - return new CreateScheduleItemRequestResponse + CreateScheduleItemRequestResponse response = new() { Success = true, CreatedScheduleItemRequestId = scheduleItemRequest.Id }; + _logger.LogReturningResponse(response); + return response; } public async Task GetAvailableScheduleForAd(GetAvailableScheduleForAdRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetAvailableScheduleForAd), request); + List items = await _scheduleItemRepository.GetAll() .Where(si => si.Ad.AdRequests.Any(ar => ar.StudentId == request.StudentId && ar.IsAccepted) && si.AdId == request.AdId) .Select(si => new ScheduleItemDto() @@ -93,7 +107,7 @@ public async Task GetAvailableScheduleForAd(G IsRemote = isRemote, Items = items }; - + _logger.LogReturningResponse(response); return response; } @@ -101,6 +115,8 @@ public async Task GetAvailableScheduleForAd(G #region Ads public async Task GetStudentsAcceptedAds(GetStudentsAcceptedAdsRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetStudentsAcceptedAds), request); + var studentId = request.StudentId; var acceptedAdRequests = await _adRequestRepository.GetAll() @@ -131,20 +147,24 @@ public async Task GetStudentsAcceptedAds(GetStud }) .ToList(); - return new GetStudentsAcceptedAdsResponse + GetStudentsAcceptedAdsResponse response = new() { Ads = adListDtos, }; + _logger.LogReturningResponse(response); + return response; } public async Task GetStudentsAdRequests(GetStudentsAdRequestsRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetStudentsAdRequests), request); + var studentId = request.StudentId; var adRequests = await _adRequestRepository.GetAll() .Include(ar => ar.Ad) .ThenInclude(ad => ad.Category) - .Where(ar => ar.StudentId == studentId && ar.DateCreated != null) + .Where(ar => ar.StudentId == studentId) .ToListAsync(); var adRequestsListDtos = adRequests @@ -164,14 +184,18 @@ public async Task GetStudentsAdRequests(GetStuden }) .ToList(); - return new GetStudentsAdRequestsResponse + GetStudentsAdRequestsResponse response = new() { AdRequests = adRequestsListDtos }; + _logger.LogReturningResponse(response); + return response; } public async Task GetAdRequestStatus(GetAdRequestStatusRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetAdRequestStatus), request); + var adRequestDetails = await _adRequestRepository.GetAll() .Include(adrequest => adrequest.Ad) .Where(adrequest => adrequest.StudentId == request.StudentId @@ -181,7 +205,15 @@ public async Task GetAdRequestStatus(GetAdRequestSta .FirstOrDefaultAsync(); if (adRequestDetails is null) - return new GetAdRequestStatusResponse() { IsSuccessful = false }; + { + _logger.LogWarning("Requested Ad not found."); + GetAdRequestStatusResponse failedResponse = new() + { + IsSuccessful = false + }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; + } GetAdRequestStatusResponse response = new GetAdRequestStatusResponse() { @@ -194,43 +226,63 @@ public async Task GetAdRequestStatus(GetAdRequestSta Status = adRequestDetails.ReviewDate == null ? GetAdRequestStatusResponse.RequestStatus.Pending : GetAdRequestStatusResponse.RequestStatus.Rejected, IsSuccessful = true }; - + _logger.LogReturningResponse(response); return response; } public async Task DeleteAdRequest(DeleteAdRequestRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(DeleteAdRequest), request); + var deletedAdRequest = await _adRequestRepository.Delete(request.Id); - DeleteAdRequestResponse response = new DeleteAdRequestResponse(); - if (deletedAdRequest == null) - response.IsSuccessful = true; - else - response.IsSuccessful = false; + if (deletedAdRequest is null) + { + _logger.LogWarning("Requested Ad not found."); + DeleteAdRequestResponse failedResponse = new() + { + IsSuccessful = false + }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; + } + DeleteAdRequestResponse response = new() + { + IsSuccessful = true + }; + _logger.LogReturningResponse(response); return response; } public async Task CreateAdRequest(CreateAdRequestRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(CreateAdRequest), request); + Ad? ad = await _adRepository.GetById(request.AdId); if (ad is null) { - return new() + _logger.LogWarning("Creating AdRequest unsuccessful."); + CreateAdRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } bool userIsOwnerOfAd = ad.TutorId == request.StudentId; if (userIsOwnerOfAd) { - return new() + _logger.LogWarning("User requesting creation of AdRequest is owner of the Ad. AdRequest will not be created."); + CreateAdRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } var userAlreadySentRequest = await _adRequestRepository.GetAll() @@ -239,10 +291,13 @@ public async Task CreateAdRequest(CreateAdRequestReques if (userAlreadySentRequest) { - return new() + _logger.LogWarning("AdRequest already sent. AdRequest will not be created."); + CreateAdRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } AdRequest toCreate = new() @@ -258,18 +313,23 @@ public async Task CreateAdRequest(CreateAdRequestReques { await _adRequestRepository.Create(toCreate); } - catch + catch (Exception ex) { - return new() + _logger.LogError(ex, "Caught exception while creating AdRequest"); + CreateAdRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } - return new() + CreateAdRequestResponse response = new() { Success = true }; + _logger.LogReturningResponse(response); + return response; } } #endregion \ No newline at end of file diff --git a/TutorLizard.BusinessLogic/Services/TutorService.cs b/TutorLizard.BusinessLogic/Services/TutorService.cs index f4f12934..67070e03 100644 --- a/TutorLizard.BusinessLogic/Services/TutorService.cs +++ b/TutorLizard.BusinessLogic/Services/TutorService.cs @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using TutorLizard.BusinessLogic.Extensions; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Interfaces.Services; using TutorLizard.BusinessLogic.Models; @@ -12,27 +14,35 @@ public class TutorService : ITutorService private readonly IDbRepository _scheduleItemRepository; private readonly IDbRepository _scheduleItemRequestRepository; private readonly IDbRepository _adRepository; + private readonly ILogger _logger; private readonly IDbRepository _adRequestRepository; public TutorService(IDbRepository scheduleItemRepository, IDbRepository scheduleItemRequestRepository, IDbRepository adRequestRepository, - IDbRepository adRepository) + IDbRepository adRepository, + ILogger logger) { _scheduleItemRepository = scheduleItemRepository; _scheduleItemRequestRepository = scheduleItemRequestRepository; _adRequestRepository = adRequestRepository; _adRepository = adRepository; + _logger = logger; } public async Task IsUserTheAdOwner(IsUserTheAdOwnerRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(IsUserTheAdOwner), request); + if (request.UserId is null) { - return new IsUserTheAdOwnerResponse + _logger.LogWarning("Requested User is null."); + IsUserTheAdOwnerResponse userNotFoundResponse = new() { IsOwner = false }; + _logger.LogReturningResponse(userNotFoundResponse); + return userNotFoundResponse; } int adId = request.AdId; @@ -41,14 +51,18 @@ public async Task IsUserTheAdOwner(IsUserTheAdOwnerReq Ad? ad = await _adRepository.GetById(adId); bool isOwner = ad != null && ad.TutorId == userId; - return new IsUserTheAdOwnerResponse + IsUserTheAdOwnerResponse response = new() { IsOwner = isOwner, }; + _logger.LogReturningResponse(response); + return response; } public async Task CreateScheduleItem(CreateScheduleItemRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(CreateScheduleItem), request); + int adId = request.AdId; int userId = request.UserId; DateTime dateTime = request.DateTime; @@ -58,10 +72,13 @@ public async Task CreateScheduleItem(CreateScheduleI if (!isOwner) { - return new CreateScheduleItemResponse + _logger.LogWarning("User requesting creation of ScheduleItem is not owner of the Ad. ScheduleItem will not be created."); + CreateScheduleItemResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } var scheduleItem = new ScheduleItem() @@ -72,15 +89,19 @@ public async Task CreateScheduleItem(CreateScheduleI await _scheduleItemRepository.Create(scheduleItem); - return new CreateScheduleItemResponse + CreateScheduleItemResponse response = new() { Success = true, CreatedItemId = scheduleItem.Id }; + _logger.LogReturningResponse(response); + return response; } public async Task GetTutorsScheduleForAd(GetTutorsScheduleForAdRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetTutorsScheduleForAd), request); + List scheduleItems = await _scheduleItemRepository.GetAll() .Where(item => item.AdId == request.AdId) .Select(item => @@ -110,12 +131,14 @@ public async Task GetTutorsScheduleForAd(GetTuto AdId = request.AdId, ScheduleItems = scheduleItems }; - + _logger.LogReturningResponse(response); return response; } public async Task AcceptScheduleItemRequest(AcceptScheduleItemRequestRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(AcceptScheduleItemRequest), request); + bool isOwner = await _scheduleItemRequestRepository.GetAll() .Where(r => r.Id == request.ScheduleItemRequestId) .Select(r => r.ScheduleItem.Ad.TutorId == request.TutorId) @@ -123,10 +146,13 @@ public async Task AcceptScheduleItemRequest(A if (isOwner == false) { - return new() + _logger.LogWarning("User requesting is not owner of the Ad."); + AcceptScheduleItemRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } var updated = await _scheduleItemRequestRepository.Update(request.ScheduleItemRequestId, r => @@ -136,20 +162,27 @@ public async Task AcceptScheduleItemRequest(A if (updated is null) { - return new() + _logger.LogWarning("Requested ScheduleItemRequest to accept not found."); + AcceptScheduleItemRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } - return new() + AcceptScheduleItemRequestResponse response = new() { Success = true }; + _logger.LogReturningResponse(response); + return response; } public async Task UnacceptScheduleItemRequest(UnacceptScheduleItemRequestRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(UnacceptScheduleItemRequest), request); + bool isOwner = await _scheduleItemRequestRepository.GetAll() .Where(r => r.Id == request.ScheduleItemRequestId) .Select(r => r.ScheduleItem.Ad.TutorId == request.TutorId) @@ -157,10 +190,13 @@ public async Task UnacceptScheduleItemReque if (isOwner == false) { - return new() + _logger.LogWarning("User requesting is not owner of the Ad."); + UnacceptScheduleItemRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } var updated = await _scheduleItemRequestRepository.Update(request.ScheduleItemRequestId, r => @@ -170,20 +206,27 @@ public async Task UnacceptScheduleItemReque if (updated is null) { - return new() + _logger.LogWarning("Requested ScheduleItemRequest to unaccept not found."); + UnacceptScheduleItemRequestResponse failedResponse = new() { Success = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } - return new() + UnacceptScheduleItemRequestResponse response = new() { Success = true }; + _logger.LogReturningResponse(response); + return response; } public async Task GetTutorsAllAdRequests(GetTutorsAllAdRequestsRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetTutorsAllAdRequests), request); + List adRequestsList = await _adRequestRepository.GetAll() .Include(adrequest => adrequest.Ad) .ThenInclude(ad => ad.Category) @@ -205,13 +248,15 @@ public async Task GetTutorsAllAdRequests(GetTuto { AdRequests = adRequestsList }; - + _logger.LogReturningResponse(response); return response; } public async Task GetTutorsPendingAdRequests(GetTutorsPendingAdRequestsRequest request) { - List adRequests = await _adRequestRepository.GetAll() + using var scope = _logger.BeginMethodCallScope(nameof(GetTutorsPendingAdRequests), request); + + List adRequests = await _adRequestRepository.GetAll() .Where(adrequest => adrequest.Ad.TutorId == request.TutorId && adrequest.ReviewDate == null && @@ -235,12 +280,14 @@ public async Task GetTutorsPendingAdRequests { AdRequests = adRequests }; - + _logger.LogReturningResponse(response); return response; } public async Task UpdateAdRequest(UpdateAdRequestRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(UpdateAdRequest), request); + var adRequest = await _adRequestRepository .Update(request.AdRequestId, entity => { @@ -254,18 +301,29 @@ public async Task UpdateAdRequest(UpdateAdRequestReques .Update(request.AdRequestId, entity => entity.IsAccepted = true); } - UpdateAdRequestResponse response = new(); - if (adRequest is null) - response.IsSuccessful = false; - else - response.IsSuccessful = true; + { + _logger.LogWarning("Requested AdRequest to update not found."); + UpdateAdRequestResponse failedResponse = new() + { + IsSuccessful = false + }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; + } + UpdateAdRequestResponse response = new() + { + IsSuccessful = true + }; + _logger.LogReturningResponse(response); return response; } public async Task GetTutorsAds(GetTutorsAdsRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(GetTutorsAds), request); + var tutorId = request.TutorId; var tutorsAds = await _adRepository.GetAll() @@ -291,14 +349,18 @@ public async Task GetTutorsAds(GetTutorsAdsRequest request }) .ToList(); - return new GetTutorsAdsResponse + GetTutorsAdsResponse response = new() { AdList = adListDtos }; + _logger.LogReturningResponse(response); + return response; } public async Task CrateAd(CreateAdRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(CrateAd), request); + Ad toCreate = new() { TutorId = request.TutorId, @@ -315,18 +377,23 @@ public async Task CrateAd(CreateAdRequest request) { await _adRepository.Create(toCreate); } - catch + catch (Exception ex) { - return new() + _logger.LogError(ex, "Caught exception while creating Ad"); + CreateAdResponse failedResponse = new() { SuccessfullyCreated = false }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } - return new() + CreateAdResponse response = new() { SuccessfullyCreated = true, CreatedAdId = toCreate.Id }; + _logger.LogReturningResponse(response); + return response; } } diff --git a/TutorLizard.BusinessLogic/Services/UserService.cs b/TutorLizard.BusinessLogic/Services/UserService.cs index 206a270b..c11c449e 100644 --- a/TutorLizard.BusinessLogic/Services/UserService.cs +++ b/TutorLizard.BusinessLogic/Services/UserService.cs @@ -1,13 +1,14 @@ using Microsoft.AspNetCore.Identity; -using TutorLizard.Shared.Models.DTOs; -using TutorLizard.BusinessLogic.Models; -using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using TutorLizard.BusinessLogic.Extensions; +using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Interfaces.Services; -using Microsoft.EntityFrameworkCore; +using TutorLizard.BusinessLogic.Models; using TutorLizard.Shared.Enums; using TutorLizard.Shared.Models.DTOs.Responses; using TutorLizard.Shared.Models.DTOs.Requests; +using TutorLizard.Shared.Models.DTOs; namespace TutorLizard.BusinessLogic.Services; @@ -15,52 +16,71 @@ public class UserService : IUserService { private readonly PasswordHasher _passwordHasher = new PasswordHasher(); private readonly IDbRepository _userRepository; + private readonly ILogger _logger; - public UserService(IDbRepository userRepository) + public UserService(IDbRepository userRepository, + ILogger logger) { _userRepository = userRepository; + _logger = logger; } public async Task LogIn(string username, string password) { + using var scope = _logger.BeginMethodCallScope(nameof(LogIn), "LogIn with password request"); + var user = await _userRepository.GetAll() .FirstOrDefaultAsync(user => user.Name == username); if (user == null) { - return new LogInResult + _logger.LogWarning("User not found"); + LogInResult userNotFoundResponse = new() { ResultCode = LogInResultCode.UserNotFound }; + _logger.LogReturningResponse(userNotFoundResponse); + return userNotFoundResponse; } if (!user.IsActive) { - return new LogInResult + _logger.LogWarning("User account not active"); + LogInResult inactiveAccountResponse = new() { ResultCode = LogInResultCode.InactiveAccount }; + _logger.LogReturningResponse(inactiveAccountResponse); + return inactiveAccountResponse; } - var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password); + var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash ?? "", password); if (result == PasswordVerificationResult.Success) { - return new LogInResult + LogInResult response = new() { ResultCode = LogInResultCode.Success, User = user.ToDto() }; + _logger.LogReturningResponse(response, destructureResponse: true); + return response; } - return new LogInResult + _logger.LogWarning("Invalid password."); + LogInResult failedResponse = new() { ResultCode = LogInResultCode.InvalidPassword }; - } + _logger.LogReturningResponse(failedResponse); + return failedResponse; + } + public async Task LogInWithGoogle(string email, string googleId) { + using var scope = _logger.BeginMethodCallScope(nameof(LogInWithGoogle), "LogIn with Google request"); + var user = await _userRepository.GetAll() .FirstOrDefaultAsync(user => user.GoogleId == googleId && @@ -68,6 +88,7 @@ public async Task LogInWithGoogle(string email, string googleId) if (user == null) { + _logger.LogWarning("User not found"); return new LogInResult() { ResultCode = LogInResultCode.UserNotFound, @@ -75,18 +96,26 @@ public async Task LogInWithGoogle(string email, string googleId) }; } - return new LogInResult() + LogInResult response = new() { ResultCode = LogInResultCode.Success, User = user.ToDto() }; + _logger.LogReturningResponse(response); + return response; } - public async Task RegisterUser(string userName, UserType type, string email, string password, string activationCode) { + using var scope = _logger.BeginMethodCallScope(nameof(RegisterUser), "Register user request"); + if (await _userRepository.GetAll().AnyAsync(user => user.Name == userName)) - return false; + { + _logger.LogWarning("User with provided name already exists. User will not be created."); + bool failedResponse = false; + _logger.LogReturningResponse(failedResponse); + return failedResponse; + } User user = new() { @@ -102,11 +131,15 @@ public async Task RegisterUser(string userName, UserType type, string emai await _userRepository.Create(user); - return true; + bool response = true; + _logger.LogReturningResponse(response); + return response; } public async Task RegisterUserWithGoogle(RegisterUserWithGoogleRequest request) { + using var scope = _logger.BeginMethodCallScope(nameof(RegisterUserWithGoogle), "Register user with Google request"); + try { User? existingUser = _userRepository @@ -120,7 +153,7 @@ public async Task RegisterUserWithGoogle(Registe { Result = GoogleRegistrationResult.LinkedExistingAccount }; - + _logger.LogReturningResponse(linkedExistingResponse); return linkedExistingResponse; } @@ -144,49 +177,60 @@ public async Task RegisterUserWithGoogle(Registe return response; } - catch + catch (Exception ex) { + _logger.LogError(ex, "Caught exception while registering User using Google Auth"); RegisterUserWithGoogleResponse failedResponse = new() { Result = GoogleRegistrationResult.Failure }; - + _logger.LogReturningResponse(failedResponse); return failedResponse; } } public async Task IsTheGoogleUserRegistered(string googleId) { - return await _userRepository.GetAll().AnyAsync(user => user.GoogleId == googleId); + using var scope = _logger.BeginMethodCallScope(nameof(IsTheGoogleUserRegistered), "Is The Google User Registered request"); + + bool response = await _userRepository.GetAll().AnyAsync(user => user.GoogleId == googleId); + + _logger.LogReturningResponse(response); + return response; } public async Task ActivateUserAsync(string activationCode) { - + using var scope = _logger.BeginMethodCallScope(nameof(ActivateUserAsync), activationCode); var user = await _userRepository.GetAll() .FirstOrDefaultAsync(u => u.ActivationCode == activationCode && u.IsActive == false); - if (user != null) + if (user is null) { - user.IsActive = true; - user.ActivationCode = "ACTIVATED"; - - await _userRepository.Update(user.Id, u => - { - u.IsActive = user.IsActive; - u.ActivationCode = user.ActivationCode; - }); - - return new ActivationResultDto + _logger.LogWarning("User with provided ActivationCode not found."); + ActivationResultDto failedResponse = new() { - IsActivated = true, + IsActivated = false, ActivationCode = activationCode }; + _logger.LogReturningResponse(failedResponse); + return failedResponse; } - return new ActivationResultDto + user.IsActive = true; + user.ActivationCode = "ACTIVATED"; + + await _userRepository.Update(user.Id, u => + { + u.IsActive = user.IsActive; + u.ActivationCode = user.ActivationCode; + }); + + ActivationResultDto response = new() { - IsActivated = false, + IsActivated = true, ActivationCode = activationCode }; + _logger.LogReturningResponse(response); + return response; } } \ No newline at end of file diff --git a/TutorLizard.Shared/Models/DTOs/UserDto.cs b/TutorLizard.Shared/Models/DTOs/UserDto.cs index 0ed1a072..e8c105c5 100644 --- a/TutorLizard.Shared/Models/DTOs/UserDto.cs +++ b/TutorLizard.Shared/Models/DTOs/UserDto.cs @@ -10,15 +10,13 @@ public class UserDto public UserType UserType { get; set; } public string Email { get; set; } public DateTime DateCreated { get; set; } = DateTime.Now; - public string? GoogleId { get; set; } - public UserDto(int id, string name, UserType userType, string email, DateTime dateCreated, string? googleId) + public UserDto(int id, string name, UserType userType, string email, DateTime dateCreated) { Id = id; Name = name; UserType = userType; Email = email; DateCreated = dateCreated; - GoogleId = googleId; } }