Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
88412c3
class implementation
Lisyonok04 Oct 16, 2025
81ec9d4
adding tests
Lisyonok04 Oct 23, 2025
8c5ddbb
adding tests second take
Lisyonok04 Oct 23, 2025
329eeae
cosmetic edits
Lisyonok04 Oct 24, 2025
ced0649
some more cosmetic edits
Lisyonok04 Oct 24, 2025
35d83d7
some required fixes
Lisyonok04 Oct 28, 2025
f4b9831
my bad
Lisyonok04 Oct 28, 2025
4d7b1e7
my bad x2
Lisyonok04 Oct 28, 2025
6318b4f
editing tests.yml
Lisyonok04 Oct 28, 2025
7f6e5e3
cosmetic details
Lisyonok04 Oct 28, 2025
6ee8a2c
first steps with dto
Lisyonok04 Nov 13, 2025
6b41b49
help me. services
Lisyonok04 Nov 18, 2025
45dbecd
playing with efcore
Lisyonok04 Nov 27, 2025
6ab0ddb
playing with repca
Lisyonok04 Nov 27, 2025
349449a
near to the end
Lisyonok04 Dec 9, 2025
6425256
services are done
Lisyonok04 Dec 9, 2025
27eaf80
repositories update
Lisyonok04 Dec 10, 2025
4a56476
app and api added
Lisyonok04 Dec 10, 2025
ee3fbb0
app and api modified
Lisyonok04 Dec 10, 2025
5323d40
csproj modified
Lisyonok04 Dec 11, 2025
e145b68
some fix
Lisyonok04 Dec 14, 2025
53bfafb
some more fix
Lisyonok04 Dec 15, 2025
a6815a6
final commit
Lisyonok04 Dec 17, 2025
5215cd8
fix
Lisyonok04 Dec 17, 2025
3070a17
utf-8 and documentation fix
Lisyonok04 Dec 17, 2025
7a4b78e
save me please kafka
Lisyonok04 Dec 21, 2025
7be5733
brand new start. kafka host is done
Lisyonok04 Dec 23, 2025
ccec9e9
new kafka infrastructure
Lisyonok04 Dec 23, 2025
86d8968
the end is near
Lisyonok04 Dec 23, 2025
27d858f
something is working
Lisyonok04 Dec 23, 2025
295b5e5
i suppose, it's the end
Lisyonok04 Dec 23, 2025
79306f5
some fix
Lisyonok04 Dec 23, 2025
5138bde
no comments
Lisyonok04 Dec 24, 2025
5a00b4e
сlear port
Lisyonok04 Dec 24, 2025
0f5e92f
first fixes
Lisyonok04 Dec 25, 2025
c0d1fbd
last fixes
Lisyonok04 Dec 25, 2025
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
29 changes: 29 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: .NET Tests

on:
push:
branches: [ "main", "lab_1" ]
pull_request:
branches: [ "main", "lab_1" ]

jobs:
build_and_test:
runs-on: ubuntu-latest

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

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

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

- name: Build solution
run: dotnet build ./Airline.sln --no-restore

- name: Run tests
run: dotnet test ./Airline.Tests/Airline.Tests.csproj --no-build --verbosity detailed
23 changes: 23 additions & 0 deletions Airline.Api.Host/Airline.Api.Host.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.MongoDB.Driver" Version="13.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Airline.Application.Contracts\Airline.Application.Contracts.csproj" />
<ProjectReference Include="..\Airline.Application\Airline.Application.csproj" />
<ProjectReference Include="..\Airline.Infrastructure.EfCore\Airline.Infrastructure.EfCore.csproj" />
<ProjectReference Include="..\Airline.Infrastructure.Kafka\Airline.Infrastructure.Kafka.csproj" />
<ProjectReference Include="..\Airline.ServiceDefaults\Airline.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions Airline.Api.Host/Airline.Api.Host.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@Airline.Api.Host_HostAddress = http://localhost:5147

GET {{Airline.Api.Host_HostAddress}}/weatherforecast/
Accept: application/json

###
172 changes: 172 additions & 0 deletions Airline.Api.Host/Controllers/AnalyticController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using Airline.Application.Contracts.Flight;
using Airline.Application.Contracts.Passenger;
using Microsoft.AspNetCore.Mvc;

namespace Airline.Api.Host.Controllers;

/// <summary>
/// Controller for executing analytical queries over airline operational data.
/// Provides aggregated insights such as top flights, minimal travel time, passenger baggage analysis,
/// and route-based reporting.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class AnalyticsController(
IAnalyticsService analyticsService,
ILogger<AnalyticsController> logger
) : ControllerBase
{
/// <summary>
/// Retrieves the top N flights by number of passengers.
/// </summary>
/// <param name="top">The number of top flights to return (default: 5).</param>
/// <returns>
/// A list of flight DTOs sorted by passenger count in descending order.
/// </returns>
/// <response code="200">Returns the list of top flights.</response>
/// <response code="500">If an unexpected error occurs during processing.</response>
[HttpGet("top-flights-by-passenger-count")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<FlightDto>>> GetTopFlightsByPassengerCount([FromQuery] int top = 5)
{
logger.LogInformation("GetTopFlightsByPassengerCount method called with top={Top}", top);
try
{
var flights = await analyticsService.GetTopFlightsByPassengerCountAsync(top);
logger.LogInformation("GetTopFlightsByPassengerCount method completed successfully");
return Ok(flights);
}
catch (Exception ex)
{
logger.LogError(ex, "Error in GetTopFlightsByPassengerCount method");
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}

/// <summary>
/// Retrieves flights with the minimal travel time (duration).
/// </summary>
/// <returns>
/// A list of flight DTOs with the shortest duration.
/// </returns>
/// <response code="200">Returns the list of flights with minimal travel time.</response>
/// <response code="500">If an unexpected error occurs during processing.</response>
[HttpGet("flights-with-min-travel-time")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<FlightDto>>> GetFlightsWithMinTravelTime()
{
logger.LogInformation("GetFlightsWithMinTravelTime method called");
try
{
var flights = await analyticsService.GetFlightsWithMinTravelTimeAsync();
logger.LogInformation("GetFlightsWithMinTravelTime method completed successfully");
return Ok(flights);
}
catch (Exception ex)
{
logger.LogError(ex, "Error in GetFlightsWithMinTravelTime method");
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}

/// <summary>
/// Retrieves passengers with zero checked baggage for a specific flight.
/// </summary>
/// <param name="flightId">The unique identifier of the flight.</param>
/// <returns>
/// A list of passenger DTOs who have no checked baggage on the specified flight.
/// </returns>
/// <response code="200">Returns the list of passengers with zero baggage.</response>
/// <response code="404">If the specified flight does not exist.</response>
/// <response code="500">If an unexpected error occurs during processing.</response>
[HttpGet("passengers-with-zero-baggage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<PassengerDto>>> GetPassengersWithZeroBaggage([FromQuery] int flightId)
{
logger.LogInformation("GetPassengersWithZeroBaggage method called with flightId={FlightId}", flightId);
try
{
var passengers = await analyticsService.GetPassengersWithZeroBaggageOnFlightAsync(flightId);
logger.LogInformation("GetPassengersWithZeroBaggage method completed successfully for flightId={FlightId}", flightId);
return Ok(passengers);
}
catch (KeyNotFoundException)
{
logger.LogWarning("Flight not found for flightId={FlightId}", flightId);
return NotFound();
}
catch (Exception ex)
{
logger.LogError(ex, "Error in GetPassengersWithZeroBaggage method for flightId={FlightId}", flightId);
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}

/// <summary>
/// Retrieves flights of a specific aircraft model within a given date period.
/// </summary>
/// <param name="modelId">The unique identifier of the aircraft model.</param>
/// <param name="from">Start date of the period (inclusive).</param>
/// <param name="to">End date of the period (inclusive).</param>
/// <returns>
/// A list of flight DTOs matching the model and date range.
/// </returns>
/// <response code="200">Returns the list of flights matching the criteria.</response>
/// <response code="500">If an unexpected error occurs during processing.</response>
[HttpGet("flights-by-model-in-period")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<FlightDto>>> GetFlightsByModelInPeriod(
[FromQuery] int modelId,
[FromQuery] DateTime from,
[FromQuery] DateTime to)
{
logger.LogInformation("GetFlightsByModelInPeriod method called with modelId={ModelId}, from={From}, to={To}", modelId, from, to);
try
{
var flights = await analyticsService.GetFlightsByModelInPeriodAsync(modelId, from, to);
logger.LogInformation("GetFlightsByModelInPeriod method completed successfully");
return Ok(flights);
}
catch (Exception ex)
{
logger.LogError(ex, "Error in GetFlightsByModelInPeriod method");
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}

/// <summary>
/// Retrieves flights by route (departure city → arrival city).
/// </summary>
/// <param name="departure">The city of departure.</param>
/// <param name="arrival">The city of arrival.</param>
/// <returns>
/// A list of flight DTOs matching the specified route.
/// </returns>
/// <response code="200">Returns the list of flights matching the route.</response>
/// <response code="500">If an unexpected error occurs during processing.</response>
[HttpGet("flights-by-route")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<List<FlightDto>>> GetFlightsByRoute(
[FromQuery] string departure,
[FromQuery] string arrival)
{
logger.LogInformation("GetFlightsByRoute method called with departure={Departure}, arrival={Arrival}", departure, arrival);
try
{
var flights = await analyticsService.GetFlightsByRouteAsync(departure, arrival);
logger.LogInformation("GetFlightsByRoute method completed successfully");
return Ok(flights);
}
catch (Exception ex)
{
logger.LogError(ex, "Error in GetFlightsByRoute method");
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
}
Loading