Add image generation
This commit is contained in:
@@ -12,7 +12,18 @@ public class HomeController(IMediator mediator) : ControllerBase
|
|||||||
[HttpGet(Name = "GetHome")]
|
[HttpGet(Name = "GetHome")]
|
||||||
public async Task<ActionResult<WeatherInformation>> Get()
|
public async Task<ActionResult<WeatherInformation>> Get()
|
||||||
{
|
{
|
||||||
var result = await mediator.Send(new GetWeather.Command());
|
return Ok(await mediator.Send(new Weather.Command()));
|
||||||
return Ok(result);
|
}
|
||||||
|
|
||||||
|
[HttpGet("default.png")]
|
||||||
|
public async Task<IActionResult> GetImage()
|
||||||
|
{
|
||||||
|
return File(await mediator.Send(new ImageGeneration.Command()), "image/png");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("departureboard")]
|
||||||
|
public async Task<ActionResult<List<TimeTable>>> GetDepartureBoard()
|
||||||
|
{
|
||||||
|
return Ok(await mediator.Send(new DepartureBoard.Command()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,4 +126,22 @@ public static class ContractExtensions
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<TimeTable>? ToContract(this TrafikLabsApiResponse response)
|
||||||
|
{
|
||||||
|
if (response?.Departure is null)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return response.Departure.Select(dep => new TimeTable
|
||||||
|
{
|
||||||
|
LineNumber = dep.ProductAtStop?.DisplayNumber ?? dep.ProductAtStop?.Line,
|
||||||
|
LineName = dep.ProductAtStop?.Name,
|
||||||
|
TransportType = dep.ProductAtStop?.CatOutL,
|
||||||
|
Operator = dep.ProductAtStop?.Operator,
|
||||||
|
StopName = dep.Stop,
|
||||||
|
DepartureTime = $"{dep.Date} {dep.Time}",
|
||||||
|
Direction = dep.Direction,
|
||||||
|
JourneyDetailRef = dep.JourneyDetailRef?.Ref,
|
||||||
|
Notes = dep.Notes?.Note?.Select(n => n.Value).ToList() ?? []
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
25
HomeApi/Handlers/DepartureBoard.cs
Normal file
25
HomeApi/Handlers/DepartureBoard.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using HomeApi.Integration;
|
||||||
|
using HomeApi.Models;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HomeApi.Handlers;
|
||||||
|
|
||||||
|
public static class DepartureBoard
|
||||||
|
{
|
||||||
|
public record Command : IRequest<List<TimeTable>>;
|
||||||
|
|
||||||
|
public class Handler : IRequestHandler<Command, List<TimeTable>>
|
||||||
|
{
|
||||||
|
private readonly IDepartureBoardService _departureBoardService;
|
||||||
|
|
||||||
|
public Handler(IDepartureBoardService departureBoardService)
|
||||||
|
{
|
||||||
|
_departureBoardService = departureBoardService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<TimeTable>> Handle(Command request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await _departureBoardService.GetDepartureBoard() ?? new List<TimeTable>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
HomeApi/Handlers/ImageGeneration.cs
Normal file
75
HomeApi/Handlers/ImageGeneration.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using HomeApi.Models;
|
||||||
|
using HomeApi.Models.Configuration;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using PuppeteerSharp;
|
||||||
|
using RazorLight;
|
||||||
|
|
||||||
|
namespace HomeApi.Handlers;
|
||||||
|
|
||||||
|
public static class ImageGeneration
|
||||||
|
{
|
||||||
|
public record Command : IRequest<Stream>;
|
||||||
|
|
||||||
|
public class Handler : IRequestHandler<Command, Stream>
|
||||||
|
{
|
||||||
|
private readonly ILogger<Handler> _logger;
|
||||||
|
private readonly IWebHostEnvironment _env;
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
public Handler(
|
||||||
|
IOptions<ApiConfiguration> apiConfiguration,
|
||||||
|
ILogger<Handler> logger, IWebHostEnvironment env, IMediator mediator)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_env = env;
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> Handle(Command request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var weather = await _mediator.Send(new Weather.Command(), cancellationToken);
|
||||||
|
var departureBoard = await _mediator.Send(new DepartureBoard.Command(), cancellationToken);
|
||||||
|
|
||||||
|
var model = new Image
|
||||||
|
{
|
||||||
|
Weather = weather,
|
||||||
|
TimeTable = departureBoard
|
||||||
|
};
|
||||||
|
|
||||||
|
if(weather is null)
|
||||||
|
throw new Exception("Weather data not found");
|
||||||
|
|
||||||
|
var engine = new RazorLightEngineBuilder().SetOperatingAssembly(Assembly.GetExecutingAssembly())
|
||||||
|
.UseEmbeddedResourcesProject(typeof(ImageGeneration)).UseMemoryCachingProvider().Build();
|
||||||
|
var path = Path.Combine(_env.WebRootPath, "index.cshtml");
|
||||||
|
|
||||||
|
var template = await File.ReadAllTextAsync(path);
|
||||||
|
|
||||||
|
var result = await engine.CompileRenderStringAsync("templateKey", template, model);
|
||||||
|
|
||||||
|
return await CreateImage(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Stream> CreateImage(string htmlContent)
|
||||||
|
{
|
||||||
|
var browserFetcher = new BrowserFetcher();
|
||||||
|
await browserFetcher.DownloadAsync();
|
||||||
|
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
|
||||||
|
{
|
||||||
|
Headless = true,
|
||||||
|
Args = ["--disable-gpu"]
|
||||||
|
});
|
||||||
|
var page = await browser.NewPageAsync();
|
||||||
|
await page.SetViewportAsync(new ViewPortOptions
|
||||||
|
{
|
||||||
|
Width = 800,
|
||||||
|
Height = 480
|
||||||
|
});
|
||||||
|
await page.SetContentAsync(htmlContent, new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } });
|
||||||
|
var stream = await page.ScreenshotStreamAsync(new ScreenshotOptions { Type = ScreenshotType.Png });
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Options;
|
|||||||
|
|
||||||
namespace HomeApi.Handlers;
|
namespace HomeApi.Handlers;
|
||||||
|
|
||||||
public static class GetWeather
|
public static class Weather
|
||||||
{
|
{
|
||||||
public record Command : IRequest<WeatherInformation>;
|
public record Command : IRequest<WeatherInformation>;
|
||||||
|
|
||||||
@@ -5,12 +5,15 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MediatR" Version="13.0.0" />
|
<PackageReference Include="MediatR" Version="13.0.0" />
|
||||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7"/>
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7"/>
|
||||||
|
<PackageReference Include="PuppeteerSharp" Version="20.2.0" />
|
||||||
|
<PackageReference Include="RazorLight" Version="2.3.1" />
|
||||||
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />
|
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />
|
||||||
<PackageReference Include="Scalar.AspNetCore" Version="2.5.6" />
|
<PackageReference Include="Scalar.AspNetCore" Version="2.5.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -20,5 +23,4 @@
|
|||||||
<Link>.dockerignore</Link>
|
<Link>.dockerignore</Link>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
30
HomeApi/Integration/Client/ResRobotClient.cs
Normal file
30
HomeApi/Integration/Client/ResRobotClient.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using HomeApi.Models.Response;
|
||||||
|
using Refit;
|
||||||
|
|
||||||
|
namespace HomeApi.Integration.Client;
|
||||||
|
|
||||||
|
public interface IResRobotClient
|
||||||
|
{
|
||||||
|
[Get("/v2.1/departureBoard")]
|
||||||
|
Task<TrafikLabsApiResponse> GetDepartureBoardAsync(
|
||||||
|
[AliasAs("accessId")] string accessId,
|
||||||
|
[AliasAs("id")] string stopId,
|
||||||
|
[AliasAs("direction")] string direction = null,
|
||||||
|
[AliasAs("date")] string date = null, // Format: YYYY-MM-DD
|
||||||
|
[AliasAs("time")] string time = null, // Format: HH:MM
|
||||||
|
[AliasAs("duration")] int? duration = null,
|
||||||
|
[AliasAs("maxJourneys")] int? maxJourneys = null,
|
||||||
|
[AliasAs("operators")] string operators = null, // Example: "275,287"
|
||||||
|
[AliasAs("products")] int? products = null,
|
||||||
|
[AliasAs("passlist")] int? passlist = 0,
|
||||||
|
[AliasAs("lang")] string language = "sv",
|
||||||
|
[AliasAs("format")] string format = "json"
|
||||||
|
);
|
||||||
|
|
||||||
|
[Get("/v2.1/location.name")]
|
||||||
|
Task<LocationNameResponse> GetLocationsByNameAsync(
|
||||||
|
[AliasAs("input")] string input,
|
||||||
|
[AliasAs("format")] string format = "json",
|
||||||
|
[AliasAs("accessId")] string accessId = "YOUR_API_KEY"
|
||||||
|
);
|
||||||
|
}
|
||||||
46
HomeApi/Integration/DepartureBoardService.cs
Normal file
46
HomeApi/Integration/DepartureBoardService.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using HomeApi.Extensions;
|
||||||
|
using HomeApi.Integration.Client;
|
||||||
|
using HomeApi.Models;
|
||||||
|
using HomeApi.Models.Configuration;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace HomeApi.Integration;
|
||||||
|
|
||||||
|
public interface IDepartureBoardService
|
||||||
|
{
|
||||||
|
Task<List<TimeTable>?> GetDepartureBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DepartureBoardService(IResRobotClient departureBoardApi, IOptions<ApiConfiguration> options) : IDepartureBoardService
|
||||||
|
{
|
||||||
|
private readonly ApiConfiguration _apiConfig = options.Value;
|
||||||
|
|
||||||
|
public async Task<List<TimeTable>?> GetDepartureBoard()
|
||||||
|
{
|
||||||
|
var locationResponse = await departureBoardApi.GetLocationsByNameAsync(
|
||||||
|
input: _apiConfig.DefaultStation,
|
||||||
|
format: "json",
|
||||||
|
accessId: _apiConfig.Keys.ResRobot
|
||||||
|
);
|
||||||
|
|
||||||
|
var id = locationResponse.StopLocationOrCoordLocation.FirstOrDefault()?.StopLocation?.ExtId;
|
||||||
|
|
||||||
|
if (id == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var result = await departureBoardApi.GetDepartureBoardAsync(
|
||||||
|
accessId: _apiConfig.Keys.ResRobot,
|
||||||
|
stopId: id,
|
||||||
|
direction: null,
|
||||||
|
date: DateTime.Now.ToString("yyyy-MM-dd"),
|
||||||
|
time: DateTime.Now.ToString("HH:mm"),
|
||||||
|
duration: 60,
|
||||||
|
maxJourneys: 10,
|
||||||
|
passlist: 1,
|
||||||
|
language: "sv",
|
||||||
|
format: "json"
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.ToContract();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ public class ApiConfiguration
|
|||||||
public Keys Keys { get; set; } = new();
|
public Keys Keys { get; set; } = new();
|
||||||
public BaseUrls BaseUrls { get; set; } = new();
|
public BaseUrls BaseUrls { get; set; } = new();
|
||||||
public string DefaultCity { get; set; } = "Vega stockholms lan";
|
public string DefaultCity { get; set; } = "Vega stockholms lan";
|
||||||
|
public string DefaultStation { get; set; } = "Vega station";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BaseUrls
|
public class BaseUrls
|
||||||
@@ -12,6 +13,7 @@ public class BaseUrls
|
|||||||
public string Weather { get; set; } = string.Empty;
|
public string Weather { get; set; } = string.Empty;
|
||||||
public string Nominatim { get; set; } = string.Empty;
|
public string Nominatim { get; set; } = string.Empty;
|
||||||
public string Aurora { get; set; } = string.Empty;
|
public string Aurora { get; set; } = string.Empty;
|
||||||
|
public string ResRobot { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Keys
|
public class Keys
|
||||||
@@ -19,4 +21,5 @@ public class Keys
|
|||||||
public string Weather { get; set; } = string.Empty;
|
public string Weather { get; set; } = string.Empty;
|
||||||
public string Nominatim { get; set; } = string.Empty;
|
public string Nominatim { get; set; } = string.Empty;
|
||||||
public string Aurora { get; set; } = string.Empty;
|
public string Aurora { get; set; } = string.Empty;
|
||||||
|
public string ResRobot { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
7
HomeApi/Models/ImageGeneration.cs
Normal file
7
HomeApi/Models/ImageGeneration.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace HomeApi.Models;
|
||||||
|
|
||||||
|
public class Image
|
||||||
|
{
|
||||||
|
public WeatherInformation Weather { get; set; }
|
||||||
|
public List<TimeTable> TimeTable { get; set; }
|
||||||
|
}
|
||||||
88
HomeApi/Models/Response/LocationNameResponse.cs
Normal file
88
HomeApi/Models/Response/LocationNameResponse.cs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
public class LocationNameResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("stopLocationOrCoordLocation")]
|
||||||
|
public List<StopLocationOrCoordLocation> StopLocationOrCoordLocation { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("TechnicalMessages")]
|
||||||
|
public TechnicalMessages TechnicalMessages { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("serverVersion")]
|
||||||
|
public string ServerVersion { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("dialectVersion")]
|
||||||
|
public string DialectVersion { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("requestId")]
|
||||||
|
public string RequestId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StopLocationOrCoordLocation
|
||||||
|
{
|
||||||
|
[JsonPropertyName("StopLocation")]
|
||||||
|
public StopLocation StopLocation { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StopLocation
|
||||||
|
{
|
||||||
|
[JsonPropertyName("productAtStop")]
|
||||||
|
public List<ProductAtStop> ProductAtStop { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("timezoneOffset")]
|
||||||
|
public int TimezoneOffset { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("extId")]
|
||||||
|
public string ExtId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lon")]
|
||||||
|
public double Lon { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lat")]
|
||||||
|
public double Lat { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("weight")]
|
||||||
|
public int Weight { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("products")]
|
||||||
|
public int Products { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("minimumChangeDuration")]
|
||||||
|
public string MinimumChangeDuration { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProductAtStop
|
||||||
|
{
|
||||||
|
[JsonPropertyName("icon")]
|
||||||
|
public Icon Icon { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("cls")]
|
||||||
|
public string Cls { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Icon
|
||||||
|
{
|
||||||
|
[JsonPropertyName("res")]
|
||||||
|
public string Res { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TechnicalMessages
|
||||||
|
{
|
||||||
|
[JsonPropertyName("TechnicalMessage")]
|
||||||
|
public List<TechnicalMessage> TechnicalMessage { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TechnicalMessage
|
||||||
|
{
|
||||||
|
[JsonPropertyName("value")]
|
||||||
|
public string Value { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("key")]
|
||||||
|
public string Key { get; set; }
|
||||||
|
}
|
||||||
84
HomeApi/Models/Response/TrafikLabsApiResponse.cs
Normal file
84
HomeApi/Models/Response/TrafikLabsApiResponse.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
namespace HomeApi.Models.Response;
|
||||||
|
public class TrafikLabsApiResponse
|
||||||
|
{
|
||||||
|
public List<Departure> Departure { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Departure
|
||||||
|
{
|
||||||
|
public JourneyDetailRef JourneyDetailRef { get; set; }
|
||||||
|
public string JourneyStatus { get; set; }
|
||||||
|
public ProductDetail ProductAtStop { get; set; }
|
||||||
|
public List<ProductDetail> Product { get; set; }
|
||||||
|
public Notes Notes { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string Stop { get; set; }
|
||||||
|
public string Stopid { get; set; }
|
||||||
|
public string StopExtId { get; set; }
|
||||||
|
public double Lon { get; set; }
|
||||||
|
public double Lat { get; set; }
|
||||||
|
public string Time { get; set; }
|
||||||
|
public string Date { get; set; }
|
||||||
|
public bool Reachable { get; set; }
|
||||||
|
public string Direction { get; set; }
|
||||||
|
public string DirectionFlag { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JourneyDetailRef
|
||||||
|
{
|
||||||
|
public string Ref { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProductDetail
|
||||||
|
{
|
||||||
|
public Icon Icon { get; set; }
|
||||||
|
public OperatorInfo OperatorInfo { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string InternalName { get; set; }
|
||||||
|
public string DisplayNumber { get; set; }
|
||||||
|
public string Num { get; set; }
|
||||||
|
public string Line { get; set; }
|
||||||
|
public string LineId { get; set; }
|
||||||
|
public string CatOut { get; set; }
|
||||||
|
public string CatIn { get; set; }
|
||||||
|
public string CatCode { get; set; }
|
||||||
|
public string Cls { get; set; }
|
||||||
|
public string CatOutS { get; set; }
|
||||||
|
public string CatOutL { get; set; }
|
||||||
|
public string OperatorCode { get; set; }
|
||||||
|
public string Operator { get; set; }
|
||||||
|
public string Admin { get; set; }
|
||||||
|
public string MatchId { get; set; }
|
||||||
|
public int? RouteIdxFrom { get; set; }
|
||||||
|
public int? RouteIdxTo { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Icon
|
||||||
|
{
|
||||||
|
public string Res { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OperatorInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string NameS { get; set; }
|
||||||
|
public string NameN { get; set; }
|
||||||
|
public string NameL { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Notes
|
||||||
|
{
|
||||||
|
public List<Note> Note { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Note
|
||||||
|
{
|
||||||
|
public string Value { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public int RouteIdxFrom { get; set; }
|
||||||
|
public int RouteIdxTo { get; set; }
|
||||||
|
public string TxtN { get; set; }
|
||||||
|
}
|
||||||
14
HomeApi/Models/TimeTable.cs
Normal file
14
HomeApi/Models/TimeTable.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace HomeApi.Models;
|
||||||
|
|
||||||
|
public class TimeTable
|
||||||
|
{
|
||||||
|
public string LineNumber { get; set; } // e.g. "43", "832"
|
||||||
|
public string LineName { get; set; } // e.g. "Länstrafik - Tåg 43"
|
||||||
|
public string TransportType { get; set; } // e.g. "Tåg", "Buss"
|
||||||
|
public string Operator { get; set; } // e.g. "SL"
|
||||||
|
public string StopName { get; set; } // e.g. "Vega station (Haninge kn)"
|
||||||
|
public string DepartureTime { get; set; } // e.g. 2025-07-15 01:03
|
||||||
|
public string Direction { get; set; } // e.g. "Farsta Strand station"
|
||||||
|
public string JourneyDetailRef { get; set; } // e.g. "1|39437|0|1|15072025"
|
||||||
|
public List<string> Notes { get; set; } // e.g. "Pendeltåg", "Endast 2 klass"
|
||||||
|
}
|
||||||
@@ -28,7 +28,11 @@ public static class RegisterIntegration
|
|||||||
|
|
||||||
services.AddRefitClient<IWeatherClient>()
|
services.AddRefitClient<IWeatherClient>()
|
||||||
.ConfigureBaseAddress(apiConfiguration => apiConfiguration.BaseUrls.Weather);
|
.ConfigureBaseAddress(apiConfiguration => apiConfiguration.BaseUrls.Weather);
|
||||||
|
|
||||||
|
services.AddRefitClient<IResRobotClient>()
|
||||||
|
.ConfigureBaseAddress(apiConfiguration => apiConfiguration.BaseUrls.ResRobot);
|
||||||
|
|
||||||
|
services.AddScoped<IDepartureBoardService, DepartureBoardService>();
|
||||||
services.AddScoped<IGeocodingService, GeocodingService>();
|
services.AddScoped<IGeocodingService, GeocodingService>();
|
||||||
services.AddScoped<IAuroraService, AuroraService>();
|
services.AddScoped<IAuroraService, AuroraService>();
|
||||||
services.AddScoped<IWeatherService, WeatherService>();
|
services.AddScoped<IWeatherService, WeatherService>();
|
||||||
|
|||||||
@@ -7,14 +7,16 @@
|
|||||||
},
|
},
|
||||||
"ApiConfiguration": {
|
"ApiConfiguration": {
|
||||||
"Keys": {
|
"Keys": {
|
||||||
"Weather": "KEY",
|
"Weather": "NOT COMMITED",
|
||||||
"SL": ""
|
"ResRobot": "NOT COMMITED"
|
||||||
},
|
},
|
||||||
"BaseUrls": {
|
"BaseUrls": {
|
||||||
"Nominatim": "https://nominatim.openstreetmap.org",
|
"Nominatim": "https://nominatim.openstreetmap.org",
|
||||||
"Aurora": "http://api.auroras.live",
|
"Aurora": "http://api.auroras.live",
|
||||||
"Weather": "https://api.weatherapi.com/v1"
|
"Weather": "https://api.weatherapi.com/v1",
|
||||||
|
"ResRobot": "https://api.resrobot.se"
|
||||||
},
|
},
|
||||||
"DefaultCity": "Vega stockholms lan"
|
"DefaultCity": "Vega stockholms lan",
|
||||||
|
"DefaultStation": "Vega Station"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,17 @@
|
|||||||
},
|
},
|
||||||
"ApiConfiguration": {
|
"ApiConfiguration": {
|
||||||
"Keys": {
|
"Keys": {
|
||||||
"Weather": "KEY",
|
"Weather": "NOT COMMITED",
|
||||||
"SL": ""
|
"ResRobot": "NOT COMMITED"
|
||||||
},
|
},
|
||||||
"BaseUrls": {
|
"BaseUrls": {
|
||||||
"Nominatim": "https://nominatim.openstreetmap.org",
|
"Nominatim": "https://nominatim.openstreetmap.org",
|
||||||
"Aurora": "http://api.auroras.live",
|
"Aurora": "http://api.auroras.live",
|
||||||
"Weather": "https://api.weatherapi.com/v1"
|
"Weather": "https://api.weatherapi.com/v1",
|
||||||
|
"ResRobot": "https://api.resrobot.se"
|
||||||
},
|
},
|
||||||
"DefaultCity": "Vega stockholms lan"
|
"DefaultCity": "Vega stockholms lan",
|
||||||
|
"DefaultStation": "Vega Station"
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
212
HomeApi/wwwroot/index.cshtml
Normal file
212
HomeApi/wwwroot/index.cshtml
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
@model HomeApi.Models.Image
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Weather Dashboard</title>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
width: 800px;
|
||||||
|
height: 480px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #f0f4f8;
|
||||||
|
color: #333;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
|
||||||
|
flex: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.scroll {
|
||||||
|
max-height: 140px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 2px 4px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: #e8eef3;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
img.icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="section">
|
||||||
|
<h2 style="font-size:16px;">@Model.Weather.CityName</h2>
|
||||||
|
<div class="flex-row">
|
||||||
|
Current Weather
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Temp</th>
|
||||||
|
<th>Feels</th>
|
||||||
|
<th>Clouds</th>
|
||||||
|
<th>Wind</th>
|
||||||
|
<th>Gusts</th>
|
||||||
|
<th>Updated</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>@Model.Weather.Current.Temperature °C</td>
|
||||||
|
<td>@Model.Weather.Current.Feelslike °C</td>
|
||||||
|
<td>@Model.Weather.Current.Cloud%</td>
|
||||||
|
<td>@Model.Weather.Current.WindPerMeterSecond m/s (@Model.Weather.Current.WindDirection)</td>
|
||||||
|
<td>@Model.Weather.Current.WindGustPerMeterSecond m/s</td>
|
||||||
|
<td>@Model.Weather.Current.LastUpdated</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
Current Air Quality
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>CO</th>
|
||||||
|
<th>NO₂</th>
|
||||||
|
<th>O₃</th>
|
||||||
|
<th>SO₂</th>
|
||||||
|
<th>PM2.5</th>
|
||||||
|
<th>PM10</th>
|
||||||
|
<th>EPA</th>
|
||||||
|
<th>DEFRA</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>@Model.Weather.Current.AirQuality?.Co</td>
|
||||||
|
<td>@Model.Weather.Current.AirQuality?.No2</td>
|
||||||
|
<td>@Model.Weather.Current.AirQuality?.O3</td>
|
||||||
|
<td>@Model.Weather.Current.AirQuality?.So2</td>
|
||||||
|
<td>@Model.Weather.Current.AirQuality?.Pm2_5</td>
|
||||||
|
<td>@Model.Weather.Current.AirQuality?.Pm10</td>
|
||||||
|
<td>@Model.Weather.Current.AirQuality?.Us_Epa_Index</td>
|
||||||
|
<td>@Model.Weather.Current.AirQuality?.Gb_Defra_Index</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
Aurora Probability
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Probability</th>
|
||||||
|
<th>Color</th>
|
||||||
|
<th>Highest</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>@Model.Weather.Current.AuroraProbability?.Value%</td>
|
||||||
|
<td>@Model.Weather.Current.AuroraProbability?.Colour</td>
|
||||||
|
<td>@Model.Weather.Current.AuroraProbability?.HighestProbability?.Value%</td>
|
||||||
|
<td>(@Model.Weather.Current.AuroraProbability?.HighestProbability?.Lat, @Model.Weather.Current.AuroraProbability?.HighestProbability?.Long)</td>
|
||||||
|
<td>@Model.Weather.Current.AuroraProbability?.HighestProbability?.Date.ToShortDateString()</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section scroll">
|
||||||
|
<h3 style="font-size:14px;">Forecast</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Icon</th>
|
||||||
|
<th>Min</th>
|
||||||
|
<th>Max</th>
|
||||||
|
<th>Day</th>
|
||||||
|
<th>Feels</th>
|
||||||
|
<th>Rain</th>
|
||||||
|
<th>Snow</th>
|
||||||
|
<th>Sunrise</th>
|
||||||
|
<th>Sunset</th>
|
||||||
|
<th>Moonrise</th>
|
||||||
|
<th>Moonset</th>
|
||||||
|
<th>Moon</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var f in Model.Weather.Forecast)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@f.Date</td>
|
||||||
|
<td><img class="icon" src="@f.DayIcon" alt="Icon" /></td>
|
||||||
|
<td>@f.MinTempC°C</td>
|
||||||
|
<td>@f.MaxTempC°C</td>
|
||||||
|
<td>@f.Day?.ConditionText</td>
|
||||||
|
<td>@f.Day?.AvgFeelslikeC°C</td>
|
||||||
|
<td>@f.Day?.TotalChanceOfRain%</td>
|
||||||
|
<td>@f.Day?.TotalChanceOfSnow%</td>
|
||||||
|
<td>@f.Astro.Sunrise</td>
|
||||||
|
<td>@f.Astro.Sunset</td>
|
||||||
|
<td>@f.Astro.Moonrise</td>
|
||||||
|
<td>@f.Astro.Moonset</td>
|
||||||
|
<td>@f.Astro.Moon_Phase (@f.Astro.Moon_Illumination%)</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section scroll">
|
||||||
|
<h3 style="font-size:14px;">Public Transport Departures</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Line</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Operator</th>
|
||||||
|
<th>Stop</th>
|
||||||
|
<th>Departure</th>
|
||||||
|
<th>Direction</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var t in Model.TimeTable)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@t.TransportType</td>
|
||||||
|
<td>@t.LineNumber</td>
|
||||||
|
<td>@t.LineName</td>
|
||||||
|
<td>@t.Operator</td>
|
||||||
|
<td>@t.StopName</td>
|
||||||
|
<td>@t.DepartureTime</td>
|
||||||
|
<td>@t.Direction</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user