mirror of
https://github.com/XFox111/MuiCharts.git
synced 2026-04-22 06:51:05 +03:00
- Added /Import endpoints
- Minor refactoring and improved validation
This commit is contained in:
@@ -61,6 +61,12 @@ public class PointsController(
|
|||||||
{
|
{
|
||||||
Logger.LogInformation("Getting points with ids {Ids}", ids);
|
Logger.LogInformation("Getting points with ids {Ids}", ids);
|
||||||
|
|
||||||
|
if (ids.Length < 1)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("No point IDs provided");
|
||||||
|
return Problem([Error.Validation()]);
|
||||||
|
}
|
||||||
|
|
||||||
IQueryable<Point> query = await _repository.GetPointsRangeAsync();
|
IQueryable<Point> query = await _repository.GetPointsRangeAsync();
|
||||||
|
|
||||||
PointResponse[] points = [
|
PointResponse[] points = [
|
||||||
@@ -181,6 +187,29 @@ public class PointsController(
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Imports an array of points.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="points">The array of points to import.</param>
|
||||||
|
/// <returns>An <see cref="IActionResult"/> representing the asynchronous operation result.</returns>
|
||||||
|
[HttpPost("Import")]
|
||||||
|
[ProducesResponseType<Point[]>(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesDefaultResponseType(typeof(ProblemDetails))]
|
||||||
|
public async Task<IActionResult> ImportPointsAsync(Point[] points)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Importing points");
|
||||||
|
|
||||||
|
ErrorOr<IEnumerable<Point>> importResult = await _repository.AddPointsRangeAsync(points);
|
||||||
|
|
||||||
|
if (importResult.IsError)
|
||||||
|
return Problem(importResult.Errors);
|
||||||
|
|
||||||
|
Logger.LogInformation("Imported {Count} points", importResult.Value.Count());
|
||||||
|
|
||||||
|
return Ok(importResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a point with the specified ID.
|
/// Deletes a point with the specified ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -189,6 +218,7 @@ public class PointsController(
|
|||||||
[HttpDelete("{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||||
[ProducesDefaultResponseType(typeof(ProblemDetails))]
|
[ProducesDefaultResponseType(typeof(ProblemDetails))]
|
||||||
public async Task<IActionResult> DeletePointAsync(int id)
|
public async Task<IActionResult> DeletePointAsync(int id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -138,6 +138,29 @@ public class TracksController(
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Imports tracks.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requests">The requests containing the track details.</param>
|
||||||
|
/// <returns>An <see cref="IActionResult"/> representing the asynchronous operation result.</returns>
|
||||||
|
[HttpPost("Import")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesDefaultResponseType(typeof(ProblemDetails))]
|
||||||
|
public async Task<IActionResult> ImportTracksAsync(Track[] requests)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Importing tracks");
|
||||||
|
|
||||||
|
ErrorOr<IEnumerable<Track>> importResult = await _repository.AddTracksRangeAsync(requests);
|
||||||
|
|
||||||
|
if (importResult.IsError)
|
||||||
|
return Problem(importResult.Errors);
|
||||||
|
|
||||||
|
Logger.LogInformation("Tracks imported");
|
||||||
|
|
||||||
|
return Ok(importResult.Value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a track with the specified first point ID and second point ID.
|
/// Deletes a track with the specified first point ID and second point ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ namespace MuiCharts.Contracts.Track;
|
|||||||
/// Represents a request to upsert a track.
|
/// Represents a request to upsert a track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record UpsertTrackRequest(
|
public record UpsertTrackRequest(
|
||||||
[Range(0, int.MaxValue)] int FirstId,
|
[Range(1, int.MaxValue)] int FirstId,
|
||||||
[Range(0, int.MaxValue)] int SecondId,
|
[Range(1, int.MaxValue)] int SecondId,
|
||||||
[Range(1, int.MaxValue)] int Distance,
|
[Range(1, int.MaxValue)] int Distance,
|
||||||
Surface Surface,
|
Surface Surface,
|
||||||
MaxSpeed MaxSpeed
|
MaxSpeed MaxSpeed
|
||||||
|
|||||||
@@ -41,4 +41,11 @@ public interface IPointRepository
|
|||||||
/// <param name="id">The ID of the point to delete.</param>
|
/// <param name="id">The ID of the point to delete.</param>
|
||||||
/// <returns>A task that represents the asynchronous operation. The task result contains a flag indicating if the point was deleted successfully or an error.</returns>
|
/// <returns>A task that represents the asynchronous operation. The task result contains a flag indicating if the point was deleted successfully or an error.</returns>
|
||||||
Task<ErrorOr<Deleted>> DeletePointAsync(int id);
|
Task<ErrorOr<Deleted>> DeletePointAsync(int id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a range of points asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="points">The points to add.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the added points or an error.</returns>
|
||||||
|
Task<ErrorOr<IEnumerable<Point>>> AddPointsRangeAsync(IEnumerable<Point> points);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,4 +43,11 @@ public interface ITrackRepository
|
|||||||
/// <param name="secondId">The second ID.</param>
|
/// <param name="secondId">The second ID.</param>
|
||||||
/// <returns>A task that represents the asynchronous operation. The task result contains the deletion status if successful, or an error if unsuccessful.</returns>
|
/// <returns>A task that represents the asynchronous operation. The task result contains the deletion status if successful, or an error if unsuccessful.</returns>
|
||||||
Task<ErrorOr<Deleted>> DeleteTrackAsync(int firstId, int secondId);
|
Task<ErrorOr<Deleted>> DeleteTrackAsync(int firstId, int secondId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a range of tracks asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tracks">The tracks to add.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the added tracks if successful, or an error if unsuccessful.</returns>
|
||||||
|
Task<ErrorOr<IEnumerable<Track>>> AddTracksRangeAsync(IEnumerable<Track> tracks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,23 +26,13 @@ public class PointRepository(
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Adding or updating point {point}", point);
|
_logger.LogInformation("Adding or updating point {point}", point);
|
||||||
|
|
||||||
bool doesExist = _context.Points.Any(p => p.Id == point.Id);
|
Point result = UpsertPoint(point, out bool isNewlyCreated);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
if (doesExist)
|
if (!isNewlyCreated)
|
||||||
{
|
|
||||||
_logger.LogInformation("Point {id} exists, updating", point.Id);
|
|
||||||
_context.Points.Update(point);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
return (Point?)null;
|
return (Point?)null;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Point {id} does not exist, adding", point.Id);
|
|
||||||
EntityEntry<Point> result = _context.Points.Add(point);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
|
|
||||||
return result.Entity;
|
return result;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -69,6 +59,29 @@ public class PointRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ErrorOr<IEnumerable<Point>>> AddPointsRangeAsync(IEnumerable<Point> points)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Adding points rage");
|
||||||
|
|
||||||
|
List<Point> updatedPoints = [];
|
||||||
|
|
||||||
|
foreach (Point point in points)
|
||||||
|
updatedPoints.Add(UpsertPoint(point, out _));
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation("Added {Count} points", updatedPoints.Count);
|
||||||
|
|
||||||
|
return updatedPoints;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error adding points {points}", points);
|
||||||
|
return Error.Failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<ErrorOr<Deleted>> DeletePointAsync(int id)
|
public async Task<ErrorOr<Deleted>> DeletePointAsync(int id)
|
||||||
{
|
{
|
||||||
@@ -81,6 +94,9 @@ public class PointRepository(
|
|||||||
if (point == null)
|
if (point == null)
|
||||||
return Error.NotFound();
|
return Error.NotFound();
|
||||||
|
|
||||||
|
if (_context.Tracks.Any(t => t.FirstId == id || t.SecondId == id))
|
||||||
|
return Error.Conflict(description: "Point is used in a track. Delete track first");
|
||||||
|
|
||||||
_context.Points.Remove(point);
|
_context.Points.Remove(point);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
@@ -119,4 +135,18 @@ public class PointRepository(
|
|||||||
{
|
{
|
||||||
return Task.FromResult(_context.Points.AsQueryable());
|
return Task.FromResult(_context.Points.AsQueryable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Point UpsertPoint(Point point, out bool isNewlyCreated)
|
||||||
|
{
|
||||||
|
bool doesExist = _context.Points.Any(p => p.Id == point.Id);
|
||||||
|
isNewlyCreated = !doesExist;
|
||||||
|
Point entity;
|
||||||
|
|
||||||
|
if (doesExist)
|
||||||
|
entity = _context.Points.Update(point).Entity;
|
||||||
|
else
|
||||||
|
entity = _context.Points.Add(point).Entity;
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,23 +32,13 @@ public class TrackRepository(
|
|||||||
return Error.Validation(description: "One or both specified points do not exist.");
|
return Error.Validation(description: "One or both specified points do not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool doesExist = _context.Tracks.Any(t => t.FirstId == track.FirstId && t.SecondId == track.SecondId);
|
Track entity = UpsertTrack(track, out bool isNewlyCreated);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
if (doesExist)
|
if (!isNewlyCreated)
|
||||||
{
|
|
||||||
_logger.LogInformation("Track with first ID {FirstId} and second ID {SecondId} exists, updating", track.FirstId, track.SecondId);
|
|
||||||
_context.Tracks.Update(track);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
return (Track?)null;
|
return (Track?)null;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Track with first ID {FirstId} and second ID {SecondId} does not exist, adding", track.FirstId, track.SecondId);
|
|
||||||
EntityEntry<Track> result = _context.Tracks.Add(track);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
|
|
||||||
return result.Entity;
|
return entity;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -88,6 +78,29 @@ public class TrackRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ErrorOr<IEnumerable<Track>>> AddTracksRangeAsync(IEnumerable<Track> tracks)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Adding tracks range");
|
||||||
|
|
||||||
|
List<Track> updatedTracks = [];
|
||||||
|
|
||||||
|
foreach (Track track in tracks)
|
||||||
|
updatedTracks.Add(UpsertTrack(track, out _));
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation("Added {Count} tracks", updatedTracks.Count);
|
||||||
|
|
||||||
|
return updatedTracks;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error adding tracks {tracks}", tracks);
|
||||||
|
return Error.Failure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ErrorOr<Deleted>> DeleteTrackAsync(int firstId, int secondId)
|
public async Task<ErrorOr<Deleted>> DeleteTrackAsync(int firstId, int secondId)
|
||||||
{
|
{
|
||||||
@@ -145,6 +158,21 @@ public class TrackRepository(
|
|||||||
return Task.FromResult(_context.Tracks.AsQueryable());
|
return Task.FromResult(_context.Tracks.AsQueryable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Track UpsertTrack(Track track, out bool isNewlyCreated)
|
||||||
|
{
|
||||||
|
bool doesExist = _context.Tracks.Any(t => t.FirstId == track.FirstId && t.SecondId == track.SecondId);
|
||||||
|
isNewlyCreated = !doesExist;
|
||||||
|
|
||||||
|
Track entity;
|
||||||
|
|
||||||
|
if (doesExist)
|
||||||
|
entity = _context.Tracks.Update(track).Entity;
|
||||||
|
else
|
||||||
|
entity = _context.Tracks.Add(track).Entity;
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsValidTrack(Track track)
|
private bool IsValidTrack(Track track)
|
||||||
{
|
{
|
||||||
return _context.Points.Any(p => p.Id == track.FirstId) &&
|
return _context.Points.Any(p => p.Id == track.FirstId) &&
|
||||||
|
|||||||
Reference in New Issue
Block a user