Rewrite electricity server to .NET web api
This commit is contained in:
2
src/Electricity.Api/.gitignore
vendored
Normal file
2
src/Electricity.Api/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
obj/
|
||||
bin/
|
||||
6
src/Electricity.Api/Constants.cs
Normal file
6
src/Electricity.Api/Constants.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Electricity.Api;
|
||||
|
||||
internal static class Constants
|
||||
{
|
||||
public const string isoDateFormat = "yyyy-MM-dd";
|
||||
}
|
||||
45
src/Electricity.Api/Controllers/ElectricityLogController.cs
Normal file
45
src/Electricity.Api/Controllers/ElectricityLogController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
45
src/Electricity.Api/DatabaseContext.cs
Normal file
45
src/Electricity.Api/DatabaseContext.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
20
src/Electricity.Api/Electricity.Api.csproj
Normal file
20
src/Electricity.Api/Electricity.Api.csproj
Normal 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>
|
||||
16
src/Electricity.Api/Entities/ElectricityLog.cs
Normal file
16
src/Electricity.Api/Entities/ElectricityLog.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
36
src/Electricity.Api/Models/Day.cs
Normal file
36
src/Electricity.Api/Models/Day.cs
Normal 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);
|
||||
}
|
||||
};
|
||||
59
src/Electricity.Api/Program.cs
Normal file
59
src/Electricity.Api/Program.cs
Normal 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;
|
||||
31
src/Electricity.Api/Properties/launchSettings.json
Normal file
31
src/Electricity.Api/Properties/launchSettings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/Electricity.Api/Services/ElectricityService.cs
Normal file
66
src/Electricity.Api/Services/ElectricityService.cs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
8
src/Electricity.Api/appsettings.Development.json
Normal file
8
src/Electricity.Api/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Electricity.Api/appsettings.json
Normal file
9
src/Electricity.Api/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
3
src/Electricity.Api/publish.sh
Executable file
3
src/Electricity.Api/publish.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
dotnet publish -c Release -r linux-arm --no-self-contained
|
||||
Reference in New Issue
Block a user