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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Run .NET Tests

on:
push:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Настолько часто запускать ашон нет смысла

pull_request:

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"

- name: Restore dependencies
run: dotnet restore Library/Library.sln

- name: Build
run: dotnet build Library/Library.sln --configuration Release --no-restore

- name: Run tests
run: dotnet test Library/Library.sln --configuration Release --no-build
141 changes: 141 additions & 0 deletions Library/Library.Api.Host/Controllers/AnalyticsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using Library.Application.Contracts;
using Library.Application.Contracts.Analytics;
using Library.Application.Contracts.Books;
using Library.Application.Contracts.Readers;
using Microsoft.AspNetCore.Mvc;

namespace Library.Api.Host.Controllers;

/// <summary>
/// Контроллер для выполнения аналитических запросов по библиотеке
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class AnalyticsController(
IAnalyticsService analyticsService,
ILogger<AnalyticsController> logger) : ControllerBase
{
/// <summary>
/// Возвращает информацию о выданных книгах, упорядоченных по названию
/// </summary>
/// <returns>Список DTO для получения книг</returns>
[HttpGet("issued-books")]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
public async Task<ActionResult<IList<BookDto>>> GetIssuedBooksOrderedByTitle()
{
logger.LogInformation("{method} method of {controller} is called", nameof(GetIssuedBooksOrderedByTitle), GetType().Name);
try
{
var res = await analyticsService.GetIssuedBooksOrderedByTitle();
logger.LogInformation("{method} method of {controller} executed successfully", nameof(GetIssuedBooksOrderedByTitle), GetType().Name);
return Ok(res);
}
catch (Exception ex)
{
logger.LogError("An exception happened during {method} method of {controller}: {@exception}", nameof(GetIssuedBooksOrderedByTitle), GetType().Name, ex);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь и далее по коду - у LogError есть перегрузка, которая принимает Exception в качестве первого параметра

return StatusCode(500, $"{ex.Message}\n\r{ex.InnerException?.Message}");
}
}

/// <summary>
/// Возвращает информацию о топ 5 читателей, прочитавших больше всего книг за заданный период
/// </summary>
/// <param name="periodStart">Начало периода в UTC</param>
/// <param name="periodEnd">Конец периода в UTC</param>
/// <returns>Список DTO для получения статистики по читателям</returns>
[HttpGet("top-readers")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<ActionResult<IList<ReaderIssuesStatDto>>> GetTop5ReadersByIssuesCount([FromQuery] DateTime periodStart, [FromQuery] DateTime periodEnd)
{
logger.LogInformation(
"{method} method of {controller} is called with {start},{end} parameters",
nameof(GetTop5ReadersByIssuesCount), GetType().Name, periodStart, periodEnd);

if (periodEnd < periodStart)
return BadRequest("periodEnd cannot be less than PeriodStart");

try
{
var res = await analyticsService.GetTop5ReadersByIssuesCount(periodStart, periodEnd);
logger.LogInformation("{method} method of {controller} executed successfully", nameof(GetTop5ReadersByIssuesCount), GetType().Name);
return Ok(res);
}
catch (Exception ex)
{
logger.LogError("An exception happened during {method} method of {controller}: {@exception}", nameof(GetTop5ReadersByIssuesCount), GetType().Name, ex);
return StatusCode(500, $"{ex.Message}\n\r{ex.InnerException?.Message}");
}
}

/// <summary>
/// Возвращает информацию о читателях, бравших книги на наибольший период времени, упорядоченных по ФИО
/// </summary>
/// <returns>Список DTO для получения читателей</returns>
[HttpGet("readers-max-loan-days")]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
public async Task<ActionResult<IList<ReaderDto>>> GetReadersByMaxLoanDaysOrderedByFullName()
{
logger.LogInformation("{method} method of {controller} is called", nameof(GetReadersByMaxLoanDaysOrderedByFullName), GetType().Name);
try
{
var res = await analyticsService.GetReadersByMaxLoanDaysOrderedByFullName();
logger.LogInformation("{method} method of {controller} executed successfully", nameof(GetReadersByMaxLoanDaysOrderedByFullName), GetType().Name);
return Ok(res);
}
catch (Exception ex)
{
logger.LogError("An exception happened during {method} method of {controller}: {@exception}", nameof(GetReadersByMaxLoanDaysOrderedByFullName), GetType().Name, ex);
return StatusCode(500, $"{ex.Message}\n\r{ex.InnerException?.Message}");
}
}

/// <summary>
/// Возвращает топ 5 наиболее популярных издательств за последний год
/// </summary>
/// <returns>Список DTO для получения статистики по издательствам</returns>
[HttpGet("top-publishers-last-year")]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
public async Task<ActionResult<IList<PublisherIssuesStatDto>>> GetTop5PublishersByIssuesCountLastYear()
{
logger.LogInformation("{method} method of {controller} is called", nameof(GetTop5PublishersByIssuesCountLastYear), GetType().Name);
try
{
var res = await analyticsService.GetTop5PublishersByIssuesCountLastYear(DateTime.UtcNow);
logger.LogInformation("{method} method of {controller} executed successfully", nameof(GetTop5PublishersByIssuesCountLastYear), GetType().Name);
return Ok(res);
}
catch (Exception ex)
{
logger.LogError("An exception happened during {method} method of {controller}: {@exception}", nameof(GetTop5PublishersByIssuesCountLastYear), GetType().Name, ex);
return StatusCode(500, $"{ex.Message}\n\r{ex.InnerException?.Message}");
}
}

/// <summary>
/// Возвращает топ 5 наименее популярных книг за последний год
/// </summary>
/// <returns>Список DTO для получения статистики по книгам</returns>
[HttpGet("bottom-books-last-year")]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
public async Task<ActionResult<IList<BookIssuesStatDto>>> GetBottom5BooksByIssuesCountLastYear()
{
logger.LogInformation("{method} method of {controller} is called", nameof(GetBottom5BooksByIssuesCountLastYear), GetType().Name);
try
{
var res = await analyticsService.GetBottom5BooksByIssuesCountLastYear(DateTime.UtcNow);
logger.LogInformation("{method} method of {controller} executed successfully", nameof(GetBottom5BooksByIssuesCountLastYear), GetType().Name);
return Ok(res);
}
catch (Exception ex)
{
logger.LogError("An exception happened during {method} method of {controller}: {@exception}", nameof(GetBottom5BooksByIssuesCountLastYear), GetType().Name, ex);
return StatusCode(500, $"{ex.Message}\n\r{ex.InnerException?.Message}");
}
}
}
108 changes: 108 additions & 0 deletions Library/Library.Api.Host/Controllers/BookController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using Library.Application.Contracts.Books;
using Library.Application.Contracts.BookIssues;
using Library.Application.Contracts.EditionTypes;
using Library.Application.Contracts.Publishers;
using Microsoft.AspNetCore.Mvc;

namespace Library.Api.Host.Controllers;

/// <summary>
/// Контроллер для работы с книгами
/// </summary>
Comment on lines +9 to +11
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь и далее по коду - в саммари служб отсутствует описание параметров

[Route("api/[controller]")]
[ApiController]
public class BookController(
IBookService bookService,
ILogger<BookController> logger)
: CrudControllerBase<BookDto, BookCreateUpdateDto, int>(bookService, logger)
{
/// <summary>
/// Возвращает записи о выдачах книги
/// </summary>
/// <param name="id">Идентификатор книги</param>
/// <returns>Список DTO для получения выдач книг</returns>
[HttpGet("{id}/Issues")]
[ProducesResponseType(typeof(IList<BookIssueDto>), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public async Task<ActionResult<IList<BookIssueDto>>> GetIssues(int id)
{
logger.LogInformation("{method} method of {controller} is called with {id} parameter", nameof(GetIssues), GetType().Name, id);
try
{
var res = await bookService.GetIssues(id);
logger.LogInformation("{method} method of {controller} executed successfully", nameof(GetIssues), GetType().Name);
return Ok(res);
}
catch (KeyNotFoundException ex)
{
logger.LogWarning("A not found exception happened during {method} method of {controller}: {@exception}", nameof(GetIssues), GetType().Name, ex);
return NotFound(ex.Message);
}
catch (Exception ex)
{
logger.LogError("An exception happened during {method} method of {controller}: {@exception}", nameof(GetIssues), GetType().Name, ex);
return StatusCode(500, $"{ex.Message}\n\r{ex.InnerException?.Message}");
}
}

/// <summary>
/// Возвращает вид издания книги
/// </summary>
/// <param name="id">Идентификатор книги</param>
/// <returns>DTO для получения вида издания</returns>
[HttpGet("{id}/EditionType")]
[ProducesResponseType(typeof(EditionTypeDto), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public async Task<ActionResult<EditionTypeDto>> GetEditionType(int id)
{
logger.LogInformation("{method} method of {controller} is called with {id} parameter", nameof(GetEditionType), GetType().Name, id);
try
{
var res = await bookService.GetEditionType(id);
logger.LogInformation("{method} method of {controller} executed successfully", nameof(GetEditionType), GetType().Name);
return Ok(res);
}
catch (KeyNotFoundException ex)
{
logger.LogWarning("A not found exception happened during {method} method of {controller}: {@exception}", nameof(GetEditionType), GetType().Name, ex);
return NotFound(ex.Message);
}
catch (Exception ex)
{
logger.LogError("An exception happened during {method} method of {controller}: {@exception}", nameof(GetEditionType), GetType().Name, ex);
return StatusCode(500, $"{ex.Message}\n\r{ex.InnerException?.Message}");
}
}

/// <summary>
/// Возвращает издательство книги
/// </summary>
/// <param name="id">Идентификатор книги</param>
/// <returns>DTO для получения издательства</returns>
[HttpGet("{id}/Publisher")]
[ProducesResponseType(typeof(PublisherDto), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public async Task<ActionResult<PublisherDto>> GetPublisher(int id)
{
logger.LogInformation("{method} method of {controller} is called with {id} parameter", nameof(GetPublisher), GetType().Name, id);
try
{
var res = await bookService.GetPublisher(id);
logger.LogInformation("{method} method of {controller} executed successfully", nameof(GetPublisher), GetType().Name);
return Ok(res);
}
catch (KeyNotFoundException ex)
{
logger.LogWarning("A not found exception happened during {method} method of {controller}: {@exception}", nameof(GetPublisher), GetType().Name, ex);
return NotFound(ex.Message);
}
catch (Exception ex)
{
logger.LogError("An exception happened during {method} method of {controller}: {@exception}", nameof(GetPublisher), GetType().Name, ex);
return StatusCode(500, $"{ex.Message}\n\r{ex.InnerException?.Message}");
}
}
}
15 changes: 15 additions & 0 deletions Library/Library.Api.Host/Controllers/BookIssueController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Library.Application.Contracts;
using Library.Application.Contracts.BookIssues;
using Microsoft.AspNetCore.Mvc;

namespace Library.Api.Host.Controllers;

/// <summary>
/// Контроллер для работы с выдачами книг
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class BookIssueController(
IApplicationService<BookIssueDto, BookIssueCreateUpdateDto, int> appService,
ILogger<BookIssueController> logger)
: CrudControllerBase<BookIssueDto, BookIssueCreateUpdateDto, int>(appService, logger);
Loading