Format .NET projects with csharpier
This commit is contained in:
@@ -3,4 +3,4 @@ namespace Electricity.Api;
|
|||||||
internal static class Constants
|
internal static class Constants
|
||||||
{
|
{
|
||||||
public const string isoDateFormat = "yyyy-MM-dd";
|
public const string isoDateFormat = "yyyy-MM-dd";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ public class ElectricityLogController : ControllerBase
|
|||||||
|
|
||||||
public ElectricityLogController(
|
public ElectricityLogController(
|
||||||
ElectricityService electricityService,
|
ElectricityService electricityService,
|
||||||
ILogger<ElectricityLogController> logger)
|
ILogger<ElectricityLogController> logger
|
||||||
|
)
|
||||||
{
|
{
|
||||||
this.electricityService = electricityService;
|
this.electricityService = electricityService;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -25,7 +26,10 @@ public class ElectricityLogController : ControllerBase
|
|||||||
[Route("/day")]
|
[Route("/day")]
|
||||||
public async Task<ActionResult<Minute[]>> Get([FromQuery] string? date)
|
public async Task<ActionResult<Minute[]>> Get([FromQuery] string? date)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(date) || !DateOnly.TryParseExact(date, Constants.isoDateFormat, out _))
|
if (
|
||||||
|
string.IsNullOrWhiteSpace(date)
|
||||||
|
|| !DateOnly.TryParseExact(date, Constants.isoDateFormat, out _)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return BadDateParameter(nameof(date));
|
return BadDateParameter(nameof(date));
|
||||||
}
|
}
|
||||||
@@ -37,24 +41,34 @@ public class ElectricityLogController : ControllerBase
|
|||||||
[Route("/days")]
|
[Route("/days")]
|
||||||
public async Task<ActionResult<Day[]>> Get([FromQuery] string? start, [FromQuery] string? stop)
|
public async Task<ActionResult<Day[]>> Get([FromQuery] string? start, [FromQuery] string? stop)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(start) || !DateOnly.TryParseExact(start, Constants.isoDateFormat, out _))
|
if (
|
||||||
|
string.IsNullOrWhiteSpace(start)
|
||||||
|
|| !DateOnly.TryParseExact(start, Constants.isoDateFormat, out _)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return BadDateParameter(nameof(start));
|
return BadDateParameter(nameof(start));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(stop) || !DateOnly.TryParseExact(stop, Constants.isoDateFormat, out _))
|
if (
|
||||||
|
string.IsNullOrWhiteSpace(stop)
|
||||||
|
|| !DateOnly.TryParseExact(stop, Constants.isoDateFormat, out _)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return BadDateParameter(nameof(stop));
|
return BadDateParameter(nameof(stop));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Compare(start, stop) > 0)
|
if (string.Compare(start, stop) > 0)
|
||||||
{
|
{
|
||||||
return BadRequest($"The date argument of {nameof(stop)} must be greater or equal to the argument value of {nameof(start)}");
|
return BadRequest(
|
||||||
|
$"The date argument of {nameof(stop)} must be greater or equal to the argument value of {nameof(start)}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await electricityService.GetSummariesFor(start, stop);
|
return await electricityService.GetSummariesFor(start, stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BadRequestObjectResult BadDateParameter(string parameterName) =>
|
private BadRequestObjectResult BadDateParameter(string parameterName) =>
|
||||||
BadRequest($"The search parameter {parameterName} is missing or is not a valid ISO date ({Constants.isoDateFormat}).");
|
BadRequest(
|
||||||
|
$"The search parameter {parameterName} is missing or is not a valid ISO date ({Constants.isoDateFormat})."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,10 @@ namespace Electricity.Api
|
|||||||
{
|
{
|
||||||
public partial class DatabaseContext : DbContext
|
public partial class DatabaseContext : DbContext
|
||||||
{
|
{
|
||||||
public DatabaseContext()
|
public DatabaseContext() { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseContext(DbContextOptions<DatabaseContext> options)
|
public DatabaseContext(DbContextOptions<DatabaseContext> options)
|
||||||
: base(options)
|
: base(options) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual DbSet<ElectricityLog> ElectricityLogs { get; set; } = null!;
|
public virtual DbSet<ElectricityLog> ElectricityLogs { get; set; } = null!;
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,13 @@
|
|||||||
public long DayTarifEnabled { get; set; }
|
public long DayTarifEnabled { get; set; }
|
||||||
public double GasConsumptionInCubicMeters { get; set; }
|
public double GasConsumptionInCubicMeters { get; set; }
|
||||||
|
|
||||||
public DateTime GetDateTime() => DateTime
|
public DateTime GetDateTime() =>
|
||||||
.Parse(
|
DateTime
|
||||||
$"{Date}T{TimeUtc}Z",
|
.Parse(
|
||||||
null,
|
$"{Date}T{TimeUtc}Z",
|
||||||
System.Globalization.DateTimeStyles.AssumeUniversal)
|
null,
|
||||||
.ToUniversalTime();
|
System.Globalization.DateTimeStyles.AssumeUniversal
|
||||||
|
)
|
||||||
|
.ToUniversalTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ public static class DateTimeExtensions
|
|||||||
{
|
{
|
||||||
public static uint ToEpoch(this DateTime dateTime) =>
|
public static uint ToEpoch(this DateTime dateTime) =>
|
||||||
(uint)Math.Round((dateTime - DateTime.UnixEpoch).TotalSeconds);
|
(uint)Math.Round((dateTime - DateTime.UnixEpoch).TotalSeconds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ public record Day(string Date, double TotalPowerUse, double TotalPowerReturn, do
|
|||||||
{
|
{
|
||||||
public static Day Empty(DateOnly date)
|
public static Day Empty(DateOnly date)
|
||||||
{
|
{
|
||||||
return new Day(
|
return new Day(date.ToString(Constants.isoDateFormat), 0, 0, 0);
|
||||||
date.ToString(Constants.isoDateFormat),
|
|
||||||
0, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Day FromEntity(Entities.ElectricityLog entity)
|
public static Day FromEntity(Entities.ElectricityLog entity)
|
||||||
@@ -20,4 +18,4 @@ public record Day(string Date, double TotalPowerUse, double TotalPowerReturn, do
|
|||||||
TotalGasUse: entity.GasConsumptionInCubicMeters
|
TotalGasUse: entity.GasConsumptionInCubicMeters
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,23 @@ using Electricity.Api.Extensions;
|
|||||||
|
|
||||||
namespace Electricity.Api.Models;
|
namespace Electricity.Api.Models;
|
||||||
|
|
||||||
public record Minute(uint DateTime, double CurrentPowerUsage, double TotalPowerUse, double TotalPowerReturn, double TotalGasUse)
|
public record Minute(
|
||||||
|
uint DateTime,
|
||||||
|
double CurrentPowerUsage,
|
||||||
|
double TotalPowerUse,
|
||||||
|
double TotalPowerReturn,
|
||||||
|
double TotalGasUse
|
||||||
|
)
|
||||||
{
|
{
|
||||||
public static Minute Empty(DateOnly date)
|
public static Minute Empty(DateOnly date)
|
||||||
{
|
{
|
||||||
return new Minute(
|
return new Minute(
|
||||||
date.ToDateTime(new TimeOnly(0, 0), DateTimeKind.Utc).ToEpoch(),
|
date.ToDateTime(new TimeOnly(0, 0), DateTimeKind.Utc).ToEpoch(),
|
||||||
0, 0, 0, 0);
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Minute FromEntity(Entities.ElectricityLog entity)
|
public static Minute FromEntity(Entities.ElectricityLog entity)
|
||||||
@@ -21,4 +31,4 @@ public record Minute(uint DateTime, double CurrentPowerUsage, double TotalPowerU
|
|||||||
TotalGasUse: entity.GasConsumptionInCubicMeters
|
TotalGasUse: entity.GasConsumptionInCubicMeters
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ internal class Program
|
|||||||
{
|
{
|
||||||
private static int Main(string[] args)
|
private static int Main(string[] args)
|
||||||
{
|
{
|
||||||
var rootCommand = new RootCommand("ElectricityServer is a small REST API to access the electricity log database");
|
var rootCommand = new RootCommand(
|
||||||
|
"ElectricityServer is a small REST API to access the electricity log database"
|
||||||
|
);
|
||||||
var connectionStringArgument = new Option<string>(
|
var connectionStringArgument = new Option<string>(
|
||||||
name: "--connection-string",
|
name: "--connection-string",
|
||||||
description: "Filepath to the Sqlite3 database file (*.db)");
|
description: "Filepath to the Sqlite3 database file (*.db)"
|
||||||
|
);
|
||||||
rootCommand.AddOption(connectionStringArgument);
|
rootCommand.AddOption(connectionStringArgument);
|
||||||
|
|
||||||
var result = rootCommand.Parse(args);
|
var result = rootCommand.Parse(args);
|
||||||
@@ -27,15 +30,19 @@ internal class Program
|
|||||||
var sqlite3DatabaseFilePath = result.GetValueForOption(connectionStringArgument);
|
var sqlite3DatabaseFilePath = result.GetValueForOption(connectionStringArgument);
|
||||||
if (!File.Exists(sqlite3DatabaseFilePath))
|
if (!File.Exists(sqlite3DatabaseFilePath))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Sqlite3 database <{sqlite3DatabaseFilePath}> does not exist or is inaccessible");
|
Console.WriteLine(
|
||||||
|
$"Sqlite3 database <{sqlite3DatabaseFilePath}> does not exist or is inaccessible"
|
||||||
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Services.AddDbContext<DatabaseContext>((builder) =>
|
builder.Services.AddDbContext<DatabaseContext>(
|
||||||
{
|
(builder) =>
|
||||||
builder.UseSqlite($"Data Source={sqlite3DatabaseFilePath}");
|
{
|
||||||
});
|
builder.UseSqlite($"Data Source={sqlite3DatabaseFilePath}");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
@@ -44,14 +51,19 @@ internal class Program
|
|||||||
policy =>
|
policy =>
|
||||||
{
|
{
|
||||||
policy.WithOrigins("http://localhost:8080");
|
policy.WithOrigins("http://localhost:8080");
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder
|
||||||
builder.Services.AddControllers()
|
.Services.AddControllers()
|
||||||
.AddJsonOptions(config =>
|
.AddJsonOptions(config =>
|
||||||
{
|
{
|
||||||
config.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
|
config.JsonSerializerOptions.PropertyNamingPolicy = System
|
||||||
|
.Text
|
||||||
|
.Json
|
||||||
|
.JsonNamingPolicy
|
||||||
|
.CamelCase;
|
||||||
config.JsonSerializerOptions.WriteIndented = true;
|
config.JsonSerializerOptions.WriteIndented = true;
|
||||||
});
|
});
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
@@ -73,4 +85,4 @@ internal class Program
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ public class ElectricityService
|
|||||||
|
|
||||||
public async Task<Minute[]> GetDetailsFor(string date)
|
public async Task<Minute[]> GetDetailsFor(string date)
|
||||||
{
|
{
|
||||||
return (await databaseContext.ElectricityLogs
|
return (
|
||||||
.Where(l => l.Date == date)
|
await databaseContext
|
||||||
.OrderBy(l => l.TimeUtc)
|
.ElectricityLogs.Where(l => l.Date == date)
|
||||||
.ToArrayAsync())
|
.OrderBy(l => l.TimeUtc)
|
||||||
|
.ToArrayAsync()
|
||||||
|
)
|
||||||
.Select(Minute.FromEntity)
|
.Select(Minute.FromEntity)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
@@ -39,8 +41,8 @@ public class ElectricityService
|
|||||||
private async Task<Day> GetSummaryFor(DateOnly date)
|
private async Task<Day> GetSummaryFor(DateOnly date)
|
||||||
{
|
{
|
||||||
var databaseDate = date.ToString(Constants.isoDateFormat);
|
var databaseDate = date.ToString(Constants.isoDateFormat);
|
||||||
var baseQuery = databaseContext.ElectricityLogs
|
var baseQuery = databaseContext
|
||||||
.Where(l => l.Date == databaseDate)
|
.ElectricityLogs.Where(l => l.Date == databaseDate)
|
||||||
.OrderBy(l => l.TimeUtc);
|
.OrderBy(l => l.TimeUtc);
|
||||||
var first = await baseQuery.FirstOrDefaultAsync();
|
var first = await baseQuery.FirstOrDefaultAsync();
|
||||||
if (first == null)
|
if (first == null)
|
||||||
@@ -63,4 +65,4 @@ public class ElectricityService
|
|||||||
lastAsModel.TotalGasUse - firstAsModel.TotalGasUse
|
lastAsModel.TotalGasUse - firstAsModel.TotalGasUse
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ internal static class CacheKeys
|
|||||||
{
|
{
|
||||||
return $"month-summary-{source}-{year}-{month}";
|
return $"month-summary-{source}-{year}-{month}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ namespace Solar.Api.Constants;
|
|||||||
internal static class Format
|
internal static class Format
|
||||||
{
|
{
|
||||||
public const string Date = "yyyy-MM-dd";
|
public const string Date = "yyyy-MM-dd";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
using System.Net.Mime;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Solar.Api.Models;
|
using Solar.Api.Models;
|
||||||
using Solar.Api.Services;
|
using Solar.Api.Services;
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
using Swashbuckle.AspNetCore.Annotations;
|
||||||
using System.Net.Mime;
|
|
||||||
|
|
||||||
namespace Solar.Api.Controllers;
|
namespace Solar.Api.Controllers;
|
||||||
|
|
||||||
@@ -14,9 +14,7 @@ public class SolarLogController : ControllerBase
|
|||||||
private readonly SolarService solarService;
|
private readonly SolarService solarService;
|
||||||
private readonly ILogger<SolarLogController> logger;
|
private readonly ILogger<SolarLogController> logger;
|
||||||
|
|
||||||
public SolarLogController(
|
public SolarLogController(SolarService solarService, ILogger<SolarLogController> logger)
|
||||||
SolarService solarService,
|
|
||||||
ILogger<SolarLogController> logger)
|
|
||||||
{
|
{
|
||||||
this.solarService = solarService;
|
this.solarService = solarService;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -25,10 +23,8 @@ public class SolarLogController : ControllerBase
|
|||||||
[HttpGet()]
|
[HttpGet()]
|
||||||
[Route("/day")]
|
[Route("/day")]
|
||||||
public async Task<ActionResult<DayResponse>> GetDayDetails(
|
public async Task<ActionResult<DayResponse>> GetDayDetails(
|
||||||
[SwaggerParameter(Required = true)]
|
[SwaggerParameter(Required = true)] [SwaggerSchema(Format = "date")] [FromQuery] string date
|
||||||
[SwaggerSchema(Format = "date")]
|
)
|
||||||
[FromQuery]
|
|
||||||
string date)
|
|
||||||
{
|
{
|
||||||
var parsedDate = TryParseDate(date);
|
var parsedDate = TryParseDate(date);
|
||||||
if (!parsedDate.HasValue || parsedDate.Value.Year < 2000 || parsedDate.Value.Year > 3000)
|
if (!parsedDate.HasValue || parsedDate.Value.Year < 2000 || parsedDate.Value.Year > 3000)
|
||||||
@@ -47,28 +43,38 @@ public class SolarLogController : ControllerBase
|
|||||||
[SwaggerParameter(Required = true)]
|
[SwaggerParameter(Required = true)]
|
||||||
[SwaggerSchema(Format = "date")]
|
[SwaggerSchema(Format = "date")]
|
||||||
[FromQuery]
|
[FromQuery]
|
||||||
string start,
|
string start,
|
||||||
[SwaggerParameter(Required = true)]
|
[SwaggerParameter(Required = true)] [SwaggerSchema(Format = "date")] [FromQuery] string stop
|
||||||
[SwaggerSchema(Format = "date")]
|
)
|
||||||
[FromQuery]
|
|
||||||
string stop)
|
|
||||||
{
|
{
|
||||||
var parsedStartDate = TryParseDate(start);
|
var parsedStartDate = TryParseDate(start);
|
||||||
if (!parsedStartDate.HasValue || parsedStartDate.Value.Year < 2000 || parsedStartDate.Value.Year > 3000)
|
if (
|
||||||
|
!parsedStartDate.HasValue
|
||||||
|
|| parsedStartDate.Value.Year < 2000
|
||||||
|
|| parsedStartDate.Value.Year > 3000
|
||||||
|
)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Invalid start date {Date} requested", start);
|
logger.LogInformation("Invalid start date {Date} requested", start);
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsedStopDate = TryParseDate(stop);
|
var parsedStopDate = TryParseDate(stop);
|
||||||
if (!parsedStopDate.HasValue || parsedStopDate.Value.Year < 2000 || parsedStopDate.Value.Year > 3000)
|
if (
|
||||||
|
!parsedStopDate.HasValue
|
||||||
|
|| parsedStopDate.Value.Year < 2000
|
||||||
|
|| parsedStopDate.Value.Year > 3000
|
||||||
|
)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Invalid stop date {Date} requested", stop);
|
logger.LogInformation("Invalid stop date {Date} requested", stop);
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
else if (parsedStopDate < parsedStartDate)
|
else if (parsedStopDate < parsedStartDate)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Stop date {StopDate} must come before start date {StartDate} requested", stop, start);
|
logger.LogInformation(
|
||||||
|
"Stop date {StopDate} must come before start date {StartDate} requested",
|
||||||
|
stop,
|
||||||
|
start
|
||||||
|
);
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,28 +87,41 @@ public class SolarLogController : ControllerBase
|
|||||||
[SwaggerParameter(Required = true)]
|
[SwaggerParameter(Required = true)]
|
||||||
[SwaggerSchema(Format = "yyyy-MM")]
|
[SwaggerSchema(Format = "yyyy-MM")]
|
||||||
[FromQuery]
|
[FromQuery]
|
||||||
string start,
|
string start,
|
||||||
[SwaggerParameter(Required = true)]
|
[SwaggerParameter(Required = true)]
|
||||||
[SwaggerSchema(Format = "yyyy-MM")]
|
[SwaggerSchema(Format = "yyyy-MM")]
|
||||||
[FromQuery]
|
[FromQuery]
|
||||||
string stop)
|
string stop
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var parsedStartDate = TryParseDate($"{start}-01");
|
var parsedStartDate = TryParseDate($"{start}-01");
|
||||||
if (!parsedStartDate.HasValue || parsedStartDate.Value.Year < 2000 || parsedStartDate.Value.Year > 3000)
|
if (
|
||||||
|
!parsedStartDate.HasValue
|
||||||
|
|| parsedStartDate.Value.Year < 2000
|
||||||
|
|| parsedStartDate.Value.Year > 3000
|
||||||
|
)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Invalid start year month {YearMonth} requested", start);
|
logger.LogInformation("Invalid start year month {YearMonth} requested", start);
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsedStopDate = TryParseDate($"{stop}-01");
|
var parsedStopDate = TryParseDate($"{stop}-01");
|
||||||
if (!parsedStopDate.HasValue || parsedStopDate.Value.Year < 2000 || parsedStopDate.Value.Year > 3000)
|
if (
|
||||||
|
!parsedStopDate.HasValue
|
||||||
|
|| parsedStopDate.Value.Year < 2000
|
||||||
|
|| parsedStopDate.Value.Year > 3000
|
||||||
|
)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Invalid stop year month {YearMonth} requested", stop);
|
logger.LogInformation("Invalid stop year month {YearMonth} requested", stop);
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
else if (parsedStopDate < parsedStartDate)
|
else if (parsedStopDate < parsedStartDate)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Stop year month {StopYearMonth} must come before start year month {StartYearMonth} requested", stop, start);
|
logger.LogInformation(
|
||||||
|
"Stop year month {StopYearMonth} must come before start year month {StartYearMonth} requested",
|
||||||
|
stop,
|
||||||
|
start
|
||||||
|
);
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +129,8 @@ public class SolarLogController : ControllerBase
|
|||||||
parsedStartDate.Value.Year,
|
parsedStartDate.Value.Year,
|
||||||
parsedStartDate.Value.Month,
|
parsedStartDate.Value.Month,
|
||||||
parsedStopDate.Value.Year,
|
parsedStopDate.Value.Year,
|
||||||
parsedStopDate.Value.Month);
|
parsedStopDate.Value.Month
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DateOnly? TryParseDate(string value)
|
private static DateOnly? TryParseDate(string value)
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ public class DateOnlyConverter : JsonConverter<DateOnly>
|
|||||||
public override DateOnly Read(
|
public override DateOnly Read(
|
||||||
ref Utf8JsonReader reader,
|
ref Utf8JsonReader reader,
|
||||||
Type typeToConvert,
|
Type typeToConvert,
|
||||||
JsonSerializerOptions options)
|
JsonSerializerOptions options
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var value = reader.GetString();
|
var value = reader.GetString();
|
||||||
return DateOnly.ParseExact(value!, serializationFormat);
|
return DateOnly.ParseExact(value!, serializationFormat);
|
||||||
@@ -19,6 +20,6 @@ public class DateOnlyConverter : JsonConverter<DateOnly>
|
|||||||
public override void Write(
|
public override void Write(
|
||||||
Utf8JsonWriter writer,
|
Utf8JsonWriter writer,
|
||||||
DateOnly value,
|
DateOnly value,
|
||||||
JsonSerializerOptions options)
|
JsonSerializerOptions options
|
||||||
=> writer.WriteStringValue(value.ToString(serializationFormat));
|
) => writer.WriteStringValue(value.ToString(serializationFormat));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ public class TimeOnlyConverter : JsonConverter<TimeOnly>
|
|||||||
public override TimeOnly Read(
|
public override TimeOnly Read(
|
||||||
ref Utf8JsonReader reader,
|
ref Utf8JsonReader reader,
|
||||||
Type typeToConvert,
|
Type typeToConvert,
|
||||||
JsonSerializerOptions options)
|
JsonSerializerOptions options
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var value = reader.GetString();
|
var value = reader.GetString();
|
||||||
return TimeOnly.ParseExact(value!, serializationFormat);
|
return TimeOnly.ParseExact(value!, serializationFormat);
|
||||||
@@ -19,6 +20,6 @@ public class TimeOnlyConverter : JsonConverter<TimeOnly>
|
|||||||
public override void Write(
|
public override void Write(
|
||||||
Utf8JsonWriter writer,
|
Utf8JsonWriter writer,
|
||||||
TimeOnly value,
|
TimeOnly value,
|
||||||
JsonSerializerOptions options)
|
JsonSerializerOptions options
|
||||||
=> writer.WriteStringValue(value.ToString(serializationFormat));
|
) => writer.WriteStringValue(value.ToString(serializationFormat));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,10 @@ namespace Solar.Api
|
|||||||
{
|
{
|
||||||
public partial class DatabaseContext : DbContext
|
public partial class DatabaseContext : DbContext
|
||||||
{
|
{
|
||||||
public DatabaseContext()
|
public DatabaseContext() { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseContext(DbContextOptions<DatabaseContext> options)
|
public DatabaseContext(DbContextOptions<DatabaseContext> options)
|
||||||
: base(options)
|
: base(options) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual DbSet<EnvoyLog> EnvoyLogs { get; set; } = null!;
|
public virtual DbSet<EnvoyLog> EnvoyLogs { get; set; } = null!;
|
||||||
public virtual DbSet<ZeverLog> ZeverLogs { get; set; } = null!;
|
public virtual DbSet<ZeverLog> ZeverLogs { get; set; } = null!;
|
||||||
@@ -46,11 +42,9 @@ namespace Solar.Api
|
|||||||
{
|
{
|
||||||
entity.ToTable("ZeverSummary");
|
entity.ToTable("ZeverSummary");
|
||||||
|
|
||||||
entity.HasIndex(e => e.Date, "IX_ZeverSummary_Date")
|
entity.HasIndex(e => e.Date, "IX_ZeverSummary_Date").IsUnique();
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
entity.HasIndex(e => e.Date, "idx_ZeverSummary_Date")
|
entity.HasIndex(e => e.Date, "idx_ZeverSummary_Date").IsUnique();
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
entity.Property(e => e.TotalWatts).HasColumnType("BIGINT");
|
entity.Property(e => e.TotalWatts).HasColumnType("BIGINT");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
namespace Solar.Api.Models;
|
namespace Solar.Api.Models;
|
||||||
|
|
||||||
public record DayResponse(ZeverDayLog[] ZeverLogs, EnvoyDayLog[] EnvoyLogs);
|
public record DayResponse(ZeverDayLog[] ZeverLogs, EnvoyDayLog[] EnvoyLogs);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Solar.Api.Models;
|
namespace Solar.Api.Models;
|
||||||
|
|
||||||
public record DaySummaryLog(DateOnly Date, int ZeverTotalWatts, int EnvoyTotalWatts);
|
public record DaySummaryLog(DateOnly Date, int ZeverTotalWatts, int EnvoyTotalWatts);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Solar.Api.Models;
|
namespace Solar.Api.Models;
|
||||||
|
|
||||||
public record DaysResponse(DaySummaryLog[] DayLogs);
|
public record DaysResponse(DaySummaryLog[] DayLogs);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Solar.Api.Models;
|
namespace Solar.Api.Models;
|
||||||
|
|
||||||
public record EnvoyDayLog(TimeOnly TimeUtc, int CurrentWatts, int TotalWatts);
|
public record EnvoyDayLog(TimeOnly TimeUtc, int CurrentWatts, int TotalWatts);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Solar.Api.Models;
|
namespace Solar.Api.Models;
|
||||||
|
|
||||||
public record MonthLog(int Year, int Month, int ZeverTotalWatts, int EnvoyTotalWatts);
|
public record MonthLog(int Year, int Month, int ZeverTotalWatts, int EnvoyTotalWatts);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Solar.Api.Models;
|
namespace Solar.Api.Models;
|
||||||
|
|
||||||
public record MonthSummariesResponse(MonthLog[] MonthLogs);
|
public record MonthSummariesResponse(MonthLog[] MonthLogs);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Solar.Api.Models;
|
namespace Solar.Api.Models;
|
||||||
|
|
||||||
public record ZeverDayLog(TimeOnly TimeUtc, int CurrentWatts, int TotalWatts);
|
public record ZeverDayLog(TimeOnly TimeUtc, int CurrentWatts, int TotalWatts);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
using System.Text.Json;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Solar.Api.Converters;
|
using Solar.Api.Converters;
|
||||||
using Solar.Api.Services;
|
using Solar.Api.Services;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Solar.Api;
|
namespace Solar.Api;
|
||||||
|
|
||||||
@@ -31,31 +31,38 @@ public partial class Program
|
|||||||
policy =>
|
policy =>
|
||||||
{
|
{
|
||||||
policy.WithOrigins("http://localhost:8080");
|
policy.WithOrigins("http://localhost:8080");
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddControllers().AddJsonOptions(config =>
|
builder
|
||||||
{
|
.Services.AddControllers()
|
||||||
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
.AddJsonOptions(config =>
|
||||||
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
{
|
||||||
config.JsonSerializerOptions.Converters.Add(new DateOnlyConverter());
|
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||||
config.JsonSerializerOptions.Converters.Add(new TimeOnlyConverter());
|
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||||
});
|
config.JsonSerializerOptions.Converters.Add(new DateOnlyConverter());
|
||||||
|
config.JsonSerializerOptions.Converters.Add(new TimeOnlyConverter());
|
||||||
|
});
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen(config =>
|
builder.Services.AddSwaggerGen(config =>
|
||||||
{
|
{
|
||||||
config.MapType<DateOnly>(() => new OpenApiSchema
|
config.MapType<DateOnly>(() =>
|
||||||
{
|
new OpenApiSchema
|
||||||
Type = "string",
|
{
|
||||||
Format = "date",
|
Type = "string",
|
||||||
Example = OpenApiAnyFactory.CreateFromJson("\"2022-12-31\"")
|
Format = "date",
|
||||||
});
|
Example = OpenApiAnyFactory.CreateFromJson("\"2022-12-31\""),
|
||||||
config.MapType<TimeOnly>(() => new OpenApiSchema
|
}
|
||||||
{
|
);
|
||||||
Type = "string",
|
config.MapType<TimeOnly>(() =>
|
||||||
Format = "time",
|
new OpenApiSchema
|
||||||
Example = OpenApiAnyFactory.CreateFromJson("\"13:45:42.0000000\"")
|
{
|
||||||
});
|
Type = "string",
|
||||||
|
Format = "time",
|
||||||
|
Example = OpenApiAnyFactory.CreateFromJson("\"13:45:42.0000000\""),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
config.SupportNonNullableReferenceTypes();
|
config.SupportNonNullableReferenceTypes();
|
||||||
config.EnableAnnotations();
|
config.EnableAnnotations();
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ public class SolarService
|
|||||||
public SolarService(
|
public SolarService(
|
||||||
DatabaseContext databaseContext,
|
DatabaseContext databaseContext,
|
||||||
IMemoryCache memoryCache,
|
IMemoryCache memoryCache,
|
||||||
ILogger<SolarService> logger)
|
ILogger<SolarService> logger
|
||||||
|
)
|
||||||
{
|
{
|
||||||
this.databaseContext = databaseContext;
|
this.databaseContext = databaseContext;
|
||||||
this.memoryCache = memoryCache;
|
this.memoryCache = memoryCache;
|
||||||
@@ -26,13 +27,11 @@ public class SolarService
|
|||||||
public async Task<DayResponse> GetDayDetails(DateOnly date)
|
public async Task<DayResponse> GetDayDetails(DateOnly date)
|
||||||
{
|
{
|
||||||
var zeverRecords = await databaseContext
|
var zeverRecords = await databaseContext
|
||||||
.ZeverLogs
|
.ZeverLogs.Where(zl => zl.Date == date)
|
||||||
.Where(zl => zl.Date == date)
|
|
||||||
.OrderBy(zl => zl.TimeUtc)
|
.OrderBy(zl => zl.TimeUtc)
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
var envoyRecords = await databaseContext
|
var envoyRecords = await databaseContext
|
||||||
.EnvoyLogs
|
.EnvoyLogs.Where(er => er.Date == date)
|
||||||
.Where(er => er.Date == date)
|
|
||||||
.OrderBy(er => er.TimeUtc)
|
.OrderBy(er => er.TimeUtc)
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
@@ -40,17 +39,12 @@ public class SolarService
|
|||||||
|
|
||||||
return new DayResponse(
|
return new DayResponse(
|
||||||
zeverRecords
|
zeverRecords
|
||||||
.Select(zr => new ZeverDayLog(
|
.Select(zr => new ZeverDayLog(zr.TimeUtc, (int)zr.CurrentWatts, (int)zr.TotalWatts))
|
||||||
zr.TimeUtc,
|
|
||||||
(int)zr.CurrentWatts,
|
|
||||||
(int)zr.TotalWatts))
|
|
||||||
.ToArray(),
|
.ToArray(),
|
||||||
envoyRecords
|
envoyRecords
|
||||||
.Select(er => new EnvoyDayLog(
|
.Select(er => new EnvoyDayLog(er.TimeUtc, (int)er.CurrentWatts, (int)er.TotalWatts))
|
||||||
er.TimeUtc,
|
.ToArray()
|
||||||
(int)er.CurrentWatts,
|
);
|
||||||
(int)er.TotalWatts))
|
|
||||||
.ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void NormalizeEnvoyLogs(EnvoyLog[] entities)
|
private static void NormalizeEnvoyLogs(EnvoyLog[] entities)
|
||||||
@@ -70,21 +64,18 @@ public class SolarService
|
|||||||
public async Task<DaysResponse> GetDaySummaries(DateOnly start, DateOnly stop)
|
public async Task<DaysResponse> GetDaySummaries(DateOnly start, DateOnly stop)
|
||||||
{
|
{
|
||||||
var zeverRecords = await databaseContext
|
var zeverRecords = await databaseContext
|
||||||
.ZeverSummaries
|
.ZeverSummaries.Where(zl => zl.Date >= start && zl.Date <= stop)
|
||||||
.Where(zl => zl.Date >= start && zl.Date <= stop)
|
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
List<DaySummaryLog> logs = new();
|
List<DaySummaryLog> logs = new();
|
||||||
var current = start;
|
var current = start;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var zeverResult = zeverRecords.FirstOrDefault(zr => zr.Date == current)?.TotalWatts ?? 0;
|
var zeverResult =
|
||||||
|
zeverRecords.FirstOrDefault(zr => zr.Date == current)?.TotalWatts ?? 0;
|
||||||
var envoyResult = await GetEnvoyDayTotalWatts(current);
|
var envoyResult = await GetEnvoyDayTotalWatts(current);
|
||||||
|
|
||||||
logs.Add(new DaySummaryLog(
|
logs.Add(new DaySummaryLog(current, (int)zeverResult, envoyResult));
|
||||||
current,
|
|
||||||
(int)zeverResult,
|
|
||||||
envoyResult));
|
|
||||||
|
|
||||||
current = current.AddDays(1);
|
current = current.AddDays(1);
|
||||||
} while (current <= stop);
|
} while (current <= stop);
|
||||||
@@ -94,12 +85,12 @@ public class SolarService
|
|||||||
|
|
||||||
private async Task<int> GetEnvoyDayTotalWatts(DateOnly date)
|
private async Task<int> GetEnvoyDayTotalWatts(DateOnly date)
|
||||||
{
|
{
|
||||||
var min = await databaseContext.EnvoyLogs
|
var min = await databaseContext
|
||||||
.Where(el => el.Date == date)
|
.EnvoyLogs.Where(el => el.Date == date)
|
||||||
.OrderBy(el => el.TotalWatts)
|
.OrderBy(el => el.TotalWatts)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
var max = await databaseContext.EnvoyLogs
|
var max = await databaseContext
|
||||||
.Where(el => el.Date == date)
|
.EnvoyLogs.Where(el => el.Date == date)
|
||||||
.OrderByDescending(el => el.TotalWatts)
|
.OrderByDescending(el => el.TotalWatts)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
@@ -111,14 +102,29 @@ public class SolarService
|
|||||||
return (int)(max.TotalWatts - min.TotalWatts);
|
return (int)(max.TotalWatts - min.TotalWatts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MonthSummariesResponse> GetMonthSummaries(int fromYear, int fromMonth, int toYear, int toMonth)
|
public async Task<MonthSummariesResponse> GetMonthSummaries(
|
||||||
|
int fromYear,
|
||||||
|
int fromMonth,
|
||||||
|
int toYear,
|
||||||
|
int toMonth
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var results = new List<MonthLog>();
|
var results = new List<MonthLog>();
|
||||||
var current = (Year: fromYear, Month: fromMonth);
|
var current = (Year: fromYear, Month: fromMonth);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var zeverResult = await GetOrAddToCache("zever", current.Year, current.Month, GetZeverMonthTotalWatts);
|
var zeverResult = await GetOrAddToCache(
|
||||||
var envoyYear = await GetOrAddToCache("envoy", current.Year, current.Month, GetEnvoyMonthTotalWatts);
|
"zever",
|
||||||
|
current.Year,
|
||||||
|
current.Month,
|
||||||
|
GetZeverMonthTotalWatts
|
||||||
|
);
|
||||||
|
var envoyYear = await GetOrAddToCache(
|
||||||
|
"envoy",
|
||||||
|
current.Year,
|
||||||
|
current.Month,
|
||||||
|
GetEnvoyMonthTotalWatts
|
||||||
|
);
|
||||||
results.Add(new MonthLog(current.Year, current.Month, zeverResult, envoyYear));
|
results.Add(new MonthLog(current.Year, current.Month, zeverResult, envoyYear));
|
||||||
|
|
||||||
if (current.Month == 12)
|
if (current.Month == 12)
|
||||||
@@ -135,19 +141,20 @@ public class SolarService
|
|||||||
|
|
||||||
private async Task<int> GetZeverMonthTotalWatts(int year, int month)
|
private async Task<int> GetZeverMonthTotalWatts(int year, int month)
|
||||||
{
|
{
|
||||||
return (int)await databaseContext.ZeverSummaries
|
return (int)
|
||||||
.Where(zs => zs.Date.Year == year && zs.Date.Month == month)
|
await databaseContext
|
||||||
.SumAsync(zs => zs.TotalWatts);
|
.ZeverSummaries.Where(zs => zs.Date.Year == year && zs.Date.Month == month)
|
||||||
|
.SumAsync(zs => zs.TotalWatts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GetEnvoyMonthTotalWatts(int year, int month)
|
private async Task<int> GetEnvoyMonthTotalWatts(int year, int month)
|
||||||
{
|
{
|
||||||
var min = await databaseContext.EnvoyLogs
|
var min = await databaseContext
|
||||||
.Where(el => el.Date.Year == year && el.Date.Month == month)
|
.EnvoyLogs.Where(el => el.Date.Year == year && el.Date.Month == month)
|
||||||
.OrderBy(el => el.TotalWatts)
|
.OrderBy(el => el.TotalWatts)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
var max = await databaseContext.EnvoyLogs
|
var max = await databaseContext
|
||||||
.Where(el => el.Date.Year == year && el.Date.Month == month)
|
.EnvoyLogs.Where(el => el.Date.Year == year && el.Date.Month == month)
|
||||||
.OrderByDescending(el => el.TotalWatts)
|
.OrderByDescending(el => el.TotalWatts)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
@@ -163,7 +170,8 @@ public class SolarService
|
|||||||
string source,
|
string source,
|
||||||
int year,
|
int year,
|
||||||
int month,
|
int month,
|
||||||
Func<int, int, Task<int>> fetchMethod)
|
Func<int, int, Task<int>> fetchMethod
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return await memoryCache.GetOrCreateAsync(
|
return await memoryCache.GetOrCreateAsync(
|
||||||
CacheKeys.GetMonthSummaryCacheKey(source, year, month),
|
CacheKeys.GetMonthSummaryCacheKey(source, year, month),
|
||||||
@@ -174,6 +182,7 @@ public class SolarService
|
|||||||
cacheEntry.SlidingExpiration = TimeSpan.FromHours(12);
|
cacheEntry.SlidingExpiration = TimeSpan.FromHours(12);
|
||||||
}
|
}
|
||||||
return await fetchMethod(year, month);
|
return await fetchMethod(year, month);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user