1
0
mirror of https://github.com/XFox111/MuiCharts.git synced 2026-04-22 06:51:05 +03:00

Added ASP.NET backend with SQLite

This commit is contained in:
2024-02-22 11:06:44 +00:00
parent d96b683a90
commit be8cc7ded4
39 changed files with 2109 additions and 0 deletions
@@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using MuiCharts.Domain.Models;
namespace MuiCharts.Infrastructure.Configurations;
/// <summary>
/// Represents the entity type configuration for the <see cref="Point"/> entity.
/// </summary>
public class PointEntityTypeConfiguration : IEntityTypeConfiguration<Point>
{
/// <inheritdoc/>
public void Configure(EntityTypeBuilder<Point> builder)
{
builder.HasKey(p => p.Id);
builder.Property(p => p.Name).IsRequired();
builder.Property(p => p.Height).IsRequired();
}
}
@@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace MuiCharts.Domain.Models;
/// <summary>
/// Represents the entity type configuration for the <see cref="Track"/> entity.
/// </summary>
public class TrackEntityTypeConfiguration : IEntityTypeConfiguration<Track>
{
/// <inheritdoc/>
public void Configure(EntityTypeBuilder<Track> builder)
{
builder.HasKey(t => new { t.FirstId, t.SecondId });
builder.Property(t => t.Distance).IsRequired();
builder.Property(t => t.Surface).IsRequired();
builder.Property(t => t.MaxSpeed).IsRequired();
}
}
@@ -0,0 +1,46 @@
using Microsoft.EntityFrameworkCore;
using MuiCharts.Domain.Models;
using MuiCharts.Infrastructure.Configurations;
namespace MuiCharts.Infrastructure;
/// <summary>
/// Represents the database context for MuiCharts application.
/// </summary>
public class DataContext : DbContext
{
/// <summary>
/// <see cref="Point"/> table.
/// </summary>
public DbSet<Point> Points { get; set; }
/// <summary>
/// <see cref="Track"/> table.
/// </summary>
public DbSet<Track> Tracks { get; set; }
/// <inheritdoc/>
public DataContext() : base() {}
/// <inheritdoc/>
public DataContext(DbContextOptions<DataContext> options) : base(options) {}
/// <inheritdoc/>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlite("Data Source=data.db")
.EnableSensitiveDataLogging(
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development"
);
}
/// <inheritdoc/>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new PointEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new TrackEntityTypeConfiguration());
}
}
@@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using MuiCharts.Domain.Repositories;
using MuiCharts.Infrastructure.Repositories;
namespace MuiCharts.Infrastructure;
/// <summary>
/// Provides extension methods for configuring infrastructure services.
/// </summary>
public static class InfrastructrureExtensions
{
/// <summary>
/// Adds infrastructure services to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The modified <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
services.AddDbContext<DataContext>();
services.AddScoped<IPointRepository, PointRepository>();
services.AddScoped<ITrackRepository, TrackRepository>();
return services;
}
}
@@ -0,0 +1,62 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MuiCharts.Infrastructure.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20240221200319_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
modelBuilder.Entity("MuiCharts.Domain.Models.Point", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Height")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Points");
});
modelBuilder.Entity("MuiCharts.Domain.Models.Track", b =>
{
b.Property<int>("FirstId")
.HasColumnType("INTEGER");
b.Property<int>("SecondId")
.HasColumnType("INTEGER");
b.Property<int>("Distance")
.HasColumnType("INTEGER");
b.Property<int>("MaxSpeed")
.HasColumnType("INTEGER");
b.Property<int>("Surface")
.HasColumnType("INTEGER");
b.HasKey("FirstId", "SecondId");
b.ToTable("Tracks");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,53 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MuiCharts.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Points",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: false),
Height = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Points", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Tracks",
columns: table => new
{
FirstId = table.Column<int>(type: "INTEGER", nullable: false),
SecondId = table.Column<int>(type: "INTEGER", nullable: false),
Distance = table.Column<int>(type: "INTEGER", nullable: false),
Surface = table.Column<int>(type: "INTEGER", nullable: false),
MaxSpeed = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tracks", x => new { x.FirstId, x.SecondId });
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Points");
migrationBuilder.DropTable(
name: "Tracks");
}
}
}
@@ -0,0 +1,59 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
#nullable disable
namespace MuiCharts.Infrastructure.Migrations
{
[DbContext(typeof(DataContext))]
partial class DataContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.2");
modelBuilder.Entity("MuiCharts.Domain.Models.Point", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Height")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Points");
});
modelBuilder.Entity("MuiCharts.Domain.Models.Track", b =>
{
b.Property<int>("FirstId")
.HasColumnType("INTEGER");
b.Property<int>("SecondId")
.HasColumnType("INTEGER");
b.Property<int>("Distance")
.HasColumnType("INTEGER");
b.Property<int>("MaxSpeed")
.HasColumnType("INTEGER");
b.Property<int>("Surface")
.HasColumnType("INTEGER");
b.HasKey("FirstId", "SecondId");
b.ToTable("Tracks");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MuiCharts.Domain\MuiCharts.Domain.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,122 @@
using ErrorOr;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Logging;
using MuiCharts.Domain.Models;
using MuiCharts.Domain.Repositories;
namespace MuiCharts.Infrastructure.Repositories;
/// <summary>
/// Represents a repository for points.
/// </summary>
/// <param name="context">The data context.</param>
/// <param name="logger">The logger.</param>
public class PointRepository(
DataContext context,
ILogger<PointRepository> logger
) : IPointRepository
{
private readonly DataContext _context = context;
private readonly ILogger<PointRepository> _logger = logger;
/// <inheritdoc/>
public async Task<ErrorOr<Point?>> AddOrUpdatePointAsync(Point point)
{
try
{
_logger.LogInformation("Adding or updating point {point}", point);
bool doesExist = _context.Points.Any(p => p.Id == point.Id);
if (doesExist)
{
_logger.LogInformation("Point {id} exists, updating", point.Id);
_context.Points.Update(point);
await _context.SaveChangesAsync();
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;
}
}
catch (Exception e)
{
_logger.LogError(e, "Error adding or updating point {point}", point);
return Error.Failure();
}
}
/// <inheritdoc/>
public async Task<ErrorOr<Point>> AddPointAsync(Point point)
{
try
{
_logger.LogInformation("Adding or updating point {point}", point);
EntityEntry<Point> result = await _context.Points.AddAsync(point);
await _context.SaveChangesAsync();
return result.Entity;
}
catch (Exception e)
{
_logger.LogError(e, "Error adding point {point}", point);
return Error.Failure();
}
}
/// <inheritdoc/>
public async Task<ErrorOr<Deleted>> DeletePointAsync(int id)
{
try
{
_logger.LogInformation("Deleting point {id}", id);
Point? point = await _context.Points.FindAsync(id);
if (point == null)
return Error.NotFound();
_context.Points.Remove(point);
await _context.SaveChangesAsync();
return Result.Deleted;
}
catch (Exception e)
{
_logger.LogError(e, "Error deleting point {id}", id);
return Error.Failure();
}
}
/// <inheritdoc/>
public async Task<ErrorOr<Point>> GetPointAsync(int id)
{
try
{
_logger.LogInformation("Getting point {id}", id);
Point? point = await _context.Points.FindAsync(id);
if (point == null)
return Error.NotFound();
return point;
}
catch (Exception e)
{
_logger.LogError(e, "Error getting point {id}", id);
return Error.Failure();
}
}
/// <inheritdoc/>
public Task<IQueryable<Point>> GetPointsRangeAsync()
{
return Task.FromResult(_context.Points.AsQueryable());
}
}
@@ -0,0 +1,153 @@
using ErrorOr;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Logging;
using MuiCharts.Domain.Models;
using MuiCharts.Domain.Repositories;
namespace MuiCharts.Infrastructure.Repositories;
/// <summary>
/// Represents a repository for tracks.
/// </summary>
/// <param name="context">The data context.</param>
/// <param name="logger">The logger.</param>
public class TrackRepository(
DataContext context,
ILogger<TrackRepository> logger
) : ITrackRepository
{
private readonly DataContext _context = context;
private readonly ILogger<TrackRepository> _logger = logger;
/// <inheritdoc />
public async Task<ErrorOr<Track?>> AddOrUpdateTrackAsync(Track track)
{
try
{
_logger.LogInformation("Adding or updating track {track}", track);
if (!IsValidTrack(track))
{
_logger.LogInformation("Points with first ID {FirstId} and second ID {SecondId} do not exist", track.FirstId, track.SecondId);
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);
if (doesExist)
{
_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;
}
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;
}
}
catch (Exception e)
{
_logger.LogError(e, "Error adding or updating track {track}", track);
return Error.Failure();
}
}
/// <inheritdoc />
public async Task<ErrorOr<Track>> AddTrackAsync(Track track)
{
try
{
_logger.LogInformation("Adding track with first ID {FirstId} and second ID {SecondId}", track.FirstId, track.SecondId);
if (!IsValidTrack(track))
{
_logger.LogInformation("Points with first ID {FirstId} and second ID {SecondId} do not exist", track.FirstId, track.SecondId);
return Error.Validation(description: "One or both specified points do not exist.");
}
if (_context.Tracks.Any(t => t.FirstId == track.FirstId && t.SecondId == track.SecondId))
{
_logger.LogInformation("Track with first ID {FirstId} and second ID {SecondId} already exists", track.FirstId, track.SecondId);
return Error.Conflict();
}
EntityEntry<Track> result = _context.Tracks.Add(track);
await _context.SaveChangesAsync();
return result.Entity;
}
catch (Exception e)
{
_logger.LogError(e, "Error adding track with first ID {FirstId} and second ID {SecondId}", track.FirstId, track.SecondId);
return Error.Failure();
}
}
/// <inheritdoc />
public async Task<ErrorOr<Deleted>> DeleteTrackAsync(int firstId, int secondId)
{
try
{
_logger.LogInformation("Deleting track with first ID {FirstId} and second ID {SecondId}", firstId, secondId);
Track? track = await _context.Tracks.FindAsync(firstId, secondId);
if (track is null)
{
_logger.LogInformation("Track with first ID {FirstId} and second ID {SecondId} does not exist", firstId, secondId);
return Error.NotFound();
}
_context.Tracks.Remove(track);
await _context.SaveChangesAsync();
return new Deleted();
}
catch (Exception e)
{
_logger.LogError(e, "Error deleting track with first ID {FirstId} and second ID {SecondId}", firstId, secondId);
return Error.Failure();
}
}
/// <inheritdoc />
public async Task<ErrorOr<Track>> GetTrackAsync(int firstId, int secondId)
{
try
{
_logger.LogInformation("Getting track with first ID {FirstId} and second ID {SecondId}", firstId, secondId);
Track? track = await _context.Tracks.FindAsync(firstId, secondId);
if (track is null)
{
_logger.LogInformation("Track with first ID {FirstId} and second ID {SecondId} does not exist", firstId, secondId);
return Error.NotFound();
}
return track;
}
catch (Exception e)
{
_logger.LogError(e, "Error getting track with first ID {FirstId} and second ID {SecondId}", firstId, secondId);
return Error.Failure();
}
}
/// <inheritdoc />
public Task<IQueryable<Track>> GetTracksRangeAsync()
{
return Task.FromResult(_context.Tracks.AsQueryable());
}
private bool IsValidTrack(Track track)
{
return _context.Points.Any(p => p.Id == track.FirstId) &&
_context.Points.Any(p => p.Id == track.SecondId);
}
}