Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9d37b2e
feat: #35: add feedback to return book
Mar 26, 2026
35df013
feat: #35: add addBookFeedback migration
Mar 26, 2026
6501c85
fix: #35: fix get employeeReader
MDI74 Mar 26, 2026
62fbb6b
format: #35: remove redundant comma
MDI74 Mar 26, 2026
428a795
format: #35: remove redundant comma
MDI74 Mar 26, 2026
3cf505f
format: #35: remove empty line
MDI74 Mar 26, 2026
cb55970
refactor: #35: rename GetBookIdByCopyIdAsync to GetByCopyIdAsync and …
MDI74 Mar 27, 2026
57eb644
feat: #35: add check for non existing book in ReturnBookHandler
MDI74 Mar 27, 2026
3a0b741
cleanup: #35: remove redundant code
MDI74 Mar 27, 2026
1729b96
feat: #35: add get book feedback endpoint
MDI74 Mar 27, 2026
fde78cd
feat: #35: update migration
MDI74 Mar 27, 2026
1d5d689
feat: #35: add check for unknown progress of reading in ReturnBookCom…
katariniss Mar 27, 2026
4d60ff7
test: #35: rename Task in TakeBookServiceTests
katariniss Mar 27, 2026
74d3a31
test: #35: add ShouldNotAddFeedbackIfBookCopyWasOvertaken test
katariniss Mar 27, 2026
e664239
fix: #35: fix conflicting tests in ReturnBookCommandTests
MDI74 Mar 27, 2026
630e5db
fix: #35: fix conflicting tests in ReturnBookCommandTests
MDI74 Mar 27, 2026
f5308ec
test: #35: add check for feedback from other tenant in GetBookFeedbac…
katariniss Mar 27, 2026
86af074
test: #35: add check for feedback for deleted book
Mar 27, 2026
7276b96
fix: #35: fix conflicting tests in GetBookFeedbackListByBookIdQueryTests
MDI74 Mar 27, 2026
daf8c69
fix: #35: fix karate tests
Mar 27, 2026
7b326b8
test: #35: add check for feedback to take-and-return-book-flow-e2e test
Mar 27, 2026
8347a1f
refactor: #35: remove AsNoTracking in GetByCopyIdAsync due to the fac…
MDI74 Mar 27, 2026
0ccfeb3
feat: #35: add AsNoTracking to GetBookFeedbackListByBookIdQuery
MDI74 Mar 27, 2026
6a559af
fix: #35: add ProgressOfReading to BookFeedbackDto and change type
Apr 1, 2026
25588bb
refactor: #35: remove feedbackCount from BooksFeedbackListResponse
Apr 1, 2026
6119377
feat: #35: change Rating to optional parameter
Apr 1, 2026
250f6f4
feat: #35: update AddBookFeedback migration
Apr 1, 2026
7127e31
fix: #35: change ProgressOfReading check to ReturnBookCommand
Apr 1, 2026
e3515cc
test: #35: add ReturnAsync_ShouldNotAddFeedbackIfBookCopyWasReturnedW…
MDI74 Apr 1, 2026
be1e108
doc: #35: fix command to README
Apr 1, 2026
1ee66d5
Merge branch 'feature/#35-add-book-feedback' of github.com:Tourmaline…
Apr 1, 2026
e2d6930
fix: #35: fix conflicting tests in ReturnBookCommandTests
MDI74 Apr 1, 2026
0743e9a
Merge branch 'feature/#35-add-book-feedback' of github.com:Tourmaline…
MDI74 Apr 1, 2026
728eb61
refactor: #35: remove unnecessary Update in ReturnBookCommand changes…
MDI74 Apr 1, 2026
a5f3aa0
refactor: #35: change condition for book feedback save
MDI74 Apr 1, 2026
6192b1b
refactor: #35: rename BookFeedbackListByBookId to BookFeedback
MDI74 Apr 2, 2026
c104149
test: #35: add GetBookFeedbackForNonExistingBook test
MDI74 Apr 2, 2026
a1a7dfc
fix: #35: fix parameter name
MDI74 Apr 2, 2026
c8e8c06
Apply suggestion from @SaraevaAnastasia
MDI74 Apr 2, 2026
c48921c
format: #35: fix format in ReturnBookRequest
MDI74 Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions Api/Controllers/BooksController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,16 @@ namespace Api.Controllers;
[Route("api/books")]
public class BooksController : Controller
{
private readonly CreateBookCommand _createBookCommand;
private readonly DeleteBookCommand _deleteBookCommand;
private readonly GetAllBooksQuery _getAllBooksQuery;
private readonly GetBookByIdQuery _getBookByIdQuery;
private readonly GetBookByCopyIdQuery _getBookByCopyIdQuery;
private readonly IGetBookByCopyIdQuery _getBookByCopyIdQuery;
private readonly GetBookCopyReadingHistoryByCopyIdQuery _getBookCopyReadingHistoryByCopyIdQuery;
private readonly GetBookHistoryByIdQuery _getBookHistoryByIdQuery;
private readonly IGetKnowledgeAreasQuery _getKnowledgeAreasQuery;
private readonly BookCopyValidatorQuery _bookCopyValidatorQuery;
private readonly SoftDeleteBookCommand _softDeleteBookCommand;
private readonly EditBookCommand _editBookCommand;
private readonly ReturnBookCommand _returnBookCommand;
private readonly TakeBookService _takeBookService;
private readonly IInnerCircleHttpClient _client;

Expand All @@ -42,16 +40,14 @@ public class BooksController : Controller
public BooksController(
GetAllBooksQuery getAllBooksQuery,
GetBookByIdQuery getBookByIdQuery,
GetBookByCopyIdQuery getBookByCopyIdQuery,
IGetBookByCopyIdQuery getBookByCopyIdQuery,
GetBookCopyReadingHistoryByCopyIdQuery getBookCopyReadingHistoryByCopyIdQuery,
GetBookHistoryByIdQuery getBookHistoryByIdQuery,
BookCopyValidatorQuery bookCopyValidatorQuery,
IGetKnowledgeAreasQuery getKnowledgeAreasQuery,
CreateBookCommand createBookCommand,
EditBookCommand editBookCommand,
DeleteBookCommand deleteBookCommand,
SoftDeleteBookCommand softDeleteBookCommand,
ReturnBookCommand returnBookCommand,
TakeBookService takeBookService,
IInnerCircleHttpClient client
)
Expand All @@ -63,11 +59,9 @@ IInnerCircleHttpClient client
_getBookHistoryByIdQuery = getBookHistoryByIdQuery;
_bookCopyValidatorQuery = bookCopyValidatorQuery;
_getKnowledgeAreasQuery = getKnowledgeAreasQuery;
_createBookCommand = createBookCommand;
_editBookCommand = editBookCommand;
_deleteBookCommand = deleteBookCommand;
_softDeleteBookCommand = softDeleteBookCommand;
_returnBookCommand = returnBookCommand;
_takeBookService = takeBookService;
_client = client;
}
Expand Down Expand Up @@ -146,9 +140,9 @@ public async Task<ActionResult<SingleBookResponse>> GetBookByIdAsync([Required][
[HttpGet("copy/{id}")]
public async Task<ActionResult<SingleBookResponse>> GetBookByCopyIdAsync([Required][FromRoute] long id, [Required][FromQuery] string secretKey)
{
var bookId = await _getBookByCopyIdQuery.GetBookIdByCopyIdAsync(id, User.GetTenantId());
var book = await _getBookByCopyIdQuery.GetByCopyIdAsync(id, User.GetTenantId());

if (bookId == 0)
if (book == null)
{
return NotFound(new
{
Expand All @@ -166,7 +160,7 @@ public async Task<ActionResult<SingleBookResponse>> GetBookByCopyIdAsync([Requir
});
}

return await GetBookResponseAsync(bookId);
return await GetBookResponseAsync(book.Id);
}

/// <summary>
Expand Down Expand Up @@ -202,6 +196,19 @@ public async Task<ActionResult<BookWithCopiesResponse>> GetBookCopiesByIdAsync([
};
}

/// <summary>
/// Get book feedback by book id
/// </summary>
[RequiresPermission(UserClaimsProvider.CanViewBooks)]
[HttpGet("feedback/{bookId}")]
public Task<GetBookFeedbackResponse> GetBookFeedbackAsync(
[Required][FromRoute] long bookId,
[FromServices] GetBookFeedbackHandler getBookFeedbackHandler
)
{
return getBookFeedbackHandler.HandleAsync(bookId, User.GetTenantId());
}

/// <summary>
/// Adds book
/// </summary>
Expand Down Expand Up @@ -262,18 +269,14 @@ public async Task<IActionResult> TakeBookAsync([Required][FromBody] TakeBookRequ
/// <param name="returnBookRequest"></param>
[RequiresPermission(UserClaimsProvider.CanViewBooks)]
[HttpPost("return")]
public async Task ReturnBookAsync([Required][FromBody] ReturnBookRequest returnBookRequest)
public async Task ReturnBookAsync(
[Required][FromBody] ReturnBookRequest returnBookRequest,
[FromServices] ReturnBookHandler returnBookHandler
)
{
var employee = await _client.GetEmployeeAsync(User.GetCorporateEmail());

var returnBookCommandParams = new ReturnBookCommandParams
{
BookCopyId = returnBookRequest.BookCopyId,
ProgressOfReading = (ProgressOfReading)Enum.Parse(typeof(ProgressOfReading), returnBookRequest.ProgressOfReading),
ActualReturnedAtUtc = DateTime.UtcNow
};

await _returnBookCommand.ReturnAsync(returnBookCommandParams, employee, User.GetTenantId());
await returnBookHandler.HandleAsync(returnBookRequest, employee, User.GetTenantId());
}

/// <summary>
Expand Down
51 changes: 51 additions & 0 deletions Api/Controllers/Handlers/GetBookFeedbackHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Api.Responses;
using Application;
using Application.Queries;

namespace Api.Controllers.Handlers;

public class GetBookFeedbackHandler
{
private readonly IInnerCircleHttpClient _client;

private readonly GetBookFeedbackQuery _getBookFeedbackQuery;

public GetBookFeedbackHandler(
IInnerCircleHttpClient client,
GetBookFeedbackQuery getBookFeedbackQuery
)
{
_client = client;
_getBookFeedbackQuery = getBookFeedbackQuery;
}

public async Task<GetBookFeedbackResponse> HandleAsync(long bookId, long tenantId)
{
var bookFeedback = await _getBookFeedbackQuery.GetAsync(bookId, tenantId);

var employeesIds = bookFeedback
.Select(x => x.EmployeeId)
.ToList();

var employeesByIds = await _client.GetEmployeesByIdsAsync(employeesIds);

return new GetBookFeedbackResponse
{
BookFeedback = bookFeedback
.Select(x =>
{
return new BookFeedbackDto
{
Id = x.Id,
EmployeeFullName = employeesByIds.FirstOrDefault(employee => employee.EmployeeId == x.EmployeeId).FullName,
LeftFeedbackAtUtc = x.LeftFeedbackAtUtc,
ProgressOfReading = x.ProgressOfReading.ToString(),
Rating = x.Rating,
Advantages = x.Advantages,
Disadvantages = x.Disadvantages
};
})
.ToList(),
};
}
}
45 changes: 45 additions & 0 deletions Api/Controllers/Handlers/ReturnBookHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Api.Requests;
using Application.Commands;
using Application.Queries;
using Core;
using Core.Entities;

namespace Api.Controllers.Handlers;

public class ReturnBookHandler
{
private readonly IGetBookByCopyIdQuery _getBookByCopyIdQuery;
private readonly ReturnBookCommand _returnBookCommand;

public ReturnBookHandler(
IGetBookByCopyIdQuery getBookByCopyIdQuery,
ReturnBookCommand returnBookCommand
)
{
_getBookByCopyIdQuery = getBookByCopyIdQuery;
_returnBookCommand = returnBookCommand;
}

public async Task HandleAsync(ReturnBookRequest returnBookRequest, Employee employee, long tenantId)
{
var book = await _getBookByCopyIdQuery.GetByCopyIdAsync(returnBookRequest.BookCopyId, tenantId);

if (book == null)
{
throw new ArgumentException($"Book copy with id {returnBookRequest.BookCopyId} not found");
}

var returnBookCommandParams = new ReturnBookCommandParams
{
BookCopyId = returnBookRequest.BookCopyId,
BookId = book.Id,
ProgressOfReading = (ProgressOfReading)Enum.Parse(typeof(ProgressOfReading), returnBookRequest.ProgressOfReading),
ActualReturnedAtUtc = DateTime.UtcNow,
Rating = returnBookRequest.Rating,
Advantages = returnBookRequest.Advantages,
Disadvantages = returnBookRequest.Disadvantages,
};

await _returnBookCommand.ReturnAsync(returnBookCommandParams, employee, tenantId);
}
}
45 changes: 45 additions & 0 deletions Api/Controllers/Handlers/ReturnBookHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Api.Controllers.Handlers;
using Api.Requests;
using Application.Queries;
using Core.Entities;
using Moq;
using Xunit;

namespace Application.Commands;

public class ReturnBookHandlerTests
{
private const long TENANT_ID = 1;

private readonly ReturnBookHandler _handler;

public ReturnBookHandlerTests()
{
var getBookByCopyIdQueryMock = new Mock<IGetBookByCopyIdQuery>();

getBookByCopyIdQueryMock
.Setup(x => x.GetByCopyIdAsync(1, TENANT_ID))
.ReturnsAsync(new Book
{
Id = 1,
TenantId = TENANT_ID
});

_handler = new ReturnBookHandler(getBookByCopyIdQueryMock.Object, null);
}

[Fact]
public async Task HandleAsyncWithNonExistedBookCopyId_ShouldThrowException()
{
var createBookRequest = new ReturnBookRequest
{
BookCopyId = 999
};

var exception = await Assert.ThrowsAsync<ArgumentException>(
async () => await _handler.HandleAsync(createBookRequest, null, TENANT_ID)
);

Assert.Equal($"Book copy with id {createBookRequest.BookCopyId} not found", exception.Message);
}
}
5 changes: 4 additions & 1 deletion Api/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ public static void AddApplication(this IServiceCollection services, IConfigurati
services.AddTransient<DeleteBookCommand>();
services.AddTransient<SoftDeleteBookCommand>();
services.AddTransient<GetBookByIdQuery>();
services.AddTransient<GetBookByCopyIdQuery>();
services.AddTransient<IGetBookByCopyIdQuery, GetBookByCopyIdQuery>();
services.AddTransient<GetBookHistoryByIdQuery>();
services.AddTransient<GetBookFeedbackHandler>();
services.AddTransient<GetBookFeedbackQuery>();
services.AddTransient<IGetKnowledgeAreasQuery, GetKnowledgeAreasQuery>();
services.AddTransient<GetAllBooksQuery>();
services.AddTransient<CreateBookHandler>();
services.AddTransient<GetBookCopyReadingHistoryByCopyIdQuery>();
services.AddTransient<BookCopyValidatorQuery>();
services.AddTransient<TakeBookCommand>();
services.AddTransient<ReturnBookCommand>();
services.AddTransient<ReturnBookHandler>();
services.AddTransient<IInnerCircleHttpClient, InnerCircleHttpClient>();
services.AddTransient<TakeBookService>();
}
Expand Down
7 changes: 7 additions & 0 deletions Api/Requests/ReturnBookRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,11 @@ public class ReturnBookRequest

[Required]
public string ProgressOfReading { get; set; }

[Range(1, 5)]
public int? Rating { get; set; }

public string? Advantages { get; set; }

public string? Disadvantages { get; set; }
}
23 changes: 23 additions & 0 deletions Api/Responses/GetBookFeedbackResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Api.Responses;

public class GetBookFeedbackResponse
{
public List<BookFeedbackDto> BookFeedback { get; set; }
}

public class BookFeedbackDto
{
public long Id { get; set; }

public string EmployeeFullName { get; set; }

public DateTime LeftFeedbackAtUtc { get; set; }

public string ProgressOfReading { get; set; }

public int? Rating { get; set; }

public string? Advantages { get; set; }

public string? Disadvantages { get; set; }
}
2 changes: 2 additions & 0 deletions Application/AppDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public AppDbContext()

public virtual DbSet<KnowledgeArea> KnowledgeAreas { get; set; }

public virtual DbSet<BookFeedback> BookFeedback { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
Expand Down
28 changes: 27 additions & 1 deletion Application/Commands/ReturnBookCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ public class ReturnBookCommandParams
{
public long BookCopyId { get; set; }

public long BookId { get; set; }

public ProgressOfReading ProgressOfReading { get; set; }

public DateTime ActualReturnedAtUtc { get; set; }

public int? Rating { get; set; }

public string? Advantages { get; set; }

public string? Disadvantages { get; set; }
}

public class ReturnBookCommand
Expand Down Expand Up @@ -38,7 +46,25 @@ long tenantId
bookCopyReadingHistory.ActualReturnedAtUtc = returnBookCommandParams.ActualReturnedAtUtc;
bookCopyReadingHistory.ProgressOfReading = returnBookCommandParams.ProgressOfReading;

_context.BooksCopiesReadingHistory.Update(bookCopyReadingHistory);
if (returnBookCommandParams.ProgressOfReading == ProgressOfReading.ReadPartially ||
returnBookCommandParams.ProgressOfReading == ProgressOfReading.ReadEntirely
)
{
var bookFeedback = new BookFeedback
{
TenantId = tenantId,
BookId = returnBookCommandParams.BookId,
EmployeeId = employee.Id,
LeftFeedbackAtUtc = DateTime.UtcNow,
ProgressOfReading = returnBookCommandParams.ProgressOfReading,
Rating = returnBookCommandParams.Rating,
Advantages = returnBookCommandParams.Advantages,
Disadvantages = returnBookCommandParams.Disadvantages
};

await _context.BookFeedback.AddAsync(bookFeedback);
}

await _context.SaveChangesAsync();
}
}
Loading
Loading