Rewrite electricity server to .NET web api

This commit is contained in:
2022-07-01 21:15:31 +02:00
parent b8387dfa1d
commit 642170172a
22 changed files with 354 additions and 509 deletions

2
src/Electricity.Api/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
obj/
bin/

View File

@@ -0,0 +1,6 @@
namespace Electricity.Api;
internal static class Constants
{
public const string isoDateFormat = "yyyy-MM-dd";
}

View File

@@ -0,0 +1,45 @@
using System.Net.Mime;
using Electricity.Api.Models;
using Electricity.Api.Services;
using Microsoft.AspNetCore.Mvc;
namespace Electricity.Api.Controllers;
[ApiController]
[Consumes(MediaTypeNames.Application.Json)]
[Produces(MediaTypeNames.Application.Json)]
public class ElectricityLogController : ControllerBase
{
private readonly ElectricityService electricityService;
private readonly ILogger<ElectricityLogController> logger;
public ElectricityLogController(
ElectricityService electricityService,
ILogger<ElectricityLogController> logger)
{
this.electricityService = electricityService;
this.logger = logger;
}
[HttpGet()]
[Route("/day")]
public async Task<ActionResult<Day[]>> Get([FromQuery] string? start, [FromQuery] string? stop)
{
if (string.IsNullOrWhiteSpace(start) || !DateOnly.TryParseExact(start, Constants.isoDateFormat, out _))
{
return BadRequest();
}
if (string.IsNullOrWhiteSpace(stop))
{
return await electricityService.GetDetailsFor(start);
}
if (!DateOnly.TryParseExact(stop, Constants.isoDateFormat, out _) || string.Compare(start, stop) > 0)
{
return BadRequest();
}
return await electricityService.GetSummariesFor(start, stop);
}
}

View File

@@ -0,0 +1,45 @@
using Electricity.Api.Entities;
using Microsoft.EntityFrameworkCore;
namespace Electricity.Api
{
public partial class DatabaseContext : DbContext
{
public DatabaseContext()
{
}
public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options)
{
}
public virtual DbSet<ElectricityLog> ElectricityLogs { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlite("Data Source=test.db");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ElectricityLog>(entity =>
{
entity.HasNoKey();
entity.ToTable("ElectricityLog");
entity.HasIndex(e => e.Date, "idx_Date");
entity.HasIndex(e => e.TimeUtc, "idx_TimeUtc");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
namespace Electricity.Api.Entities
{
public partial class ElectricityLog
{
public string Date { get; set; } = null!;
public string TimeUtc { get; set; } = null!;
public double CurrentPowerUsage { get; set; }
public double TotalPowerConsumptionDay { get; set; }
public double TotalPowerConsumptionNight { get; set; }
public double CurrentPowerReturn { get; set; }
public double TotalPowerReturnDay { get; set; }
public double TotalPowerReturnNight { get; set; }
public long DayTarifEnabled { get; set; }
public double GasConsumptionInCubicMeters { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
namespace Electricity.Api.Models;
public record Day(uint DateTime, double TotalPowerUse, double TotalPowerReturn, double TotalGasUse)
{
public static Day Empty(DateOnly date)
{
return new Day(
ConvertToEpoch(date.ToDateTime(new TimeOnly(0, 0), DateTimeKind.Utc)),
0, 0, 0);
}
public static Day FromEntity(Entities.ElectricityLog entity)
{
return new Day(
DateTime: ConvertToEpoch(ParseEntityDateTime(entity.Date, entity.TimeUtc)),
TotalPowerUse: entity.TotalPowerConsumptionDay + entity.TotalPowerConsumptionNight,
TotalPowerReturn: entity.TotalPowerReturnDay + entity.TotalPowerReturnNight,
TotalGasUse: entity.GasConsumptionInCubicMeters
);
}
private static DateTime ParseEntityDateTime(string date, string timeUtc)
{
return System.DateTime
.Parse(
$"{date}T{timeUtc}Z",
null,
System.Globalization.DateTimeStyles.AssumeUniversal)
.ToUniversalTime();
}
private static uint ConvertToEpoch(DateTime dateTime)
{
return (uint)Math.Round((dateTime - System.DateTime.UnixEpoch).TotalSeconds);
}
};

View File

@@ -0,0 +1,59 @@
using System.CommandLine;
using Electricity.Api;
using Electricity.Api.Services;
using Microsoft.EntityFrameworkCore;
var rootCommand = new RootCommand("ElectricityServer is a small REST API to access the electricity log database");
var connectionStringArgument = new Option<string>(
name: "--connection-string",
description: "Filepath to the Sqlite3 database file (*.db)");
rootCommand.AddOption(connectionStringArgument);
var result = rootCommand.Parse(args);
if (result.Errors.Any())
{
foreach (var error in result.Errors)
{
Console.WriteLine(error.Message);
}
return 1;
}
var sqlite3DatabaseFilePath = result.GetValueForOption(connectionStringArgument);
if (!File.Exists(sqlite3DatabaseFilePath))
{
Console.WriteLine($"Sqlite3 database <{sqlite3DatabaseFilePath}> does not exist or is inaccessible");
return 1;
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DatabaseContext>((DbContextOptionsBuilder builder) =>
{
builder.UseSqlite($"Data Source={sqlite3DatabaseFilePath}");
});
builder.Services.AddControllers()
.AddJsonOptions(config =>
{
config.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
config.JsonSerializerOptions.WriteIndented = true;
});
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<ElectricityService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
return 0;

View File

@@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:44706",
"sslPort": 44335
}
},
"profiles": {
"Electricity.Api": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7290;http://localhost:5093",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,66 @@
using Electricity.Api.Models;
using Microsoft.EntityFrameworkCore;
namespace Electricity.Api.Services;
public class ElectricityService
{
private readonly DatabaseContext databaseContext;
public ElectricityService(DatabaseContext databaseContext)
{
this.databaseContext = databaseContext;
}
public async Task<Day[]> GetDetailsFor(string date)
{
return (await databaseContext.ElectricityLogs
.Where(l => l.Date == date)
.OrderBy(l => l.TimeUtc)
.ToArrayAsync())
.Select(Day.FromEntity)
.ToArray();
}
public async Task<Day[]> GetSummariesFor(string from, string to)
{
var list = new List<Day>();
var current = DateOnly.ParseExact(from, Constants.isoDateFormat);
var limit = DateOnly.ParseExact(to, Constants.isoDateFormat);
do
{
list.Add(await GetSummaryFor(current));
current = current.AddDays(1);
} while (current <= limit);
return list.ToArray();
}
private async Task<Day> GetSummaryFor(DateOnly date)
{
var databaseDate = date.ToString(Constants.isoDateFormat);
var baseQuery = databaseContext.ElectricityLogs
.Where(l => l.Date == databaseDate)
.OrderBy(l => l.TimeUtc);
var first = await baseQuery.FirstOrDefaultAsync();
if (first == null)
{
return Day.Empty(date);
}
var last = await baseQuery.LastAsync();
var firstAsModel = Day.FromEntity(first);
if (last.TimeUtc == first.TimeUtc)
{
return Day.Empty(date);
}
var lastAsModel = Day.FromEntity(last);
return new Day(
lastAsModel.DateTime,
lastAsModel.TotalPowerUse - firstAsModel.TotalPowerUse,
lastAsModel.TotalPowerReturn - firstAsModel.TotalPowerReturn,
lastAsModel.TotalGasUse - firstAsModel.TotalGasUse
);
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

3
src/Electricity.Api/publish.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
dotnet publish -c Release -r linux-arm --no-self-contained