aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2024-10-14 16:03:04 +1100
committerJake Mannens <jake@asger.xyz>2024-11-01 16:23:29 +1100
commitbc4939d6d0f59a326b7182949b1d33e77da55864 (patch)
treefec479a30e2caf24ca0877be3a7f507325f10d7d
parentb39dc97286456159a99b8afcdfdb6e0aae759495 (diff)
Initial commit
-rw-r--r--BartService.cs284
-rw-r--r--Program.cs2
2 files changed, 286 insertions, 0 deletions
diff --git a/BartService.cs b/BartService.cs
new file mode 100644
index 0000000..f54ec55
--- /dev/null
+++ b/BartService.cs
@@ -0,0 +1,284 @@
+using Microsoft.EntityFrameworkCore;
+using PagerParser.Bart;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace PagerParser {
+ public partial class PagerContext {
+ public DbSet<BartMember> BartMembers { get; set; }
+ public DbSet<BartAvailabilityRecord> BartAvailabilityRecords { get; set; }
+ }
+}
+
+namespace PagerParser.Bart {
+ public enum BartStatus {
+ NotAvailable = -1,
+ Clear = 0,
+ Available = 1,
+ AtPremises = 2,
+ EmergencyOnly = 3,
+ Delayed = 4
+ }
+
+ [Index(nameof(MemberName))]
+ public record BartMember {
+ [JsonIgnore]
+ [Key]
+ public int BartMemberId { get; set; }
+ public string MemberName { get; set; }
+ }
+
+ [Index(nameof(Hashcode))]
+ public record BartAvailabilityRecord {
+ [JsonIgnore]
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int BartAvailabilityRecordId { get; set; }
+
+ public DateTime Timestamp { get; set; }
+ public BartMember Member { get; set; }
+ public BartStatus Status { get; set; }
+ public bool IsDefault { get; set; }
+ public DateTime? ModifiedOn { get; set; }
+ public BartMember? ModifiedBy { get; set; }
+ public DateTime? CreatedOn { get; set; }
+ public BartMember? CreatedBy { get; set; }
+
+ public int Hashcode {
+ get => (
+ Timestamp,
+ Member.BartMemberId,
+ Status,
+ IsDefault,
+ ModifiedOn,
+ ModifiedBy?.BartMemberId,
+ CreatedOn,
+ CreatedBy?.BartMemberId).GetHashCode();
+ set => _ = value;
+ }
+ }
+
+ public class BartService : IHostedService {
+ private const string AvailabilityEndpointUrl =
+ @"https://bartapp.net/webapp/webservice/WebsiteService.svc/GetUserAvailabilityList";
+
+ private ILogger logger;
+ private IServiceProvider serviceProvider;
+
+ private HttpClient httpClient;
+
+ public BartService(
+ ILogger<BartService> logger,
+ IServiceProvider serviceProvider) {
+
+ this.logger = logger;
+ this.serviceProvider = serviceProvider;
+ }
+
+ public async Task FetchAsync(DateTime fetchUntil) {
+ using var scope = serviceProvider.CreateScope();
+ using var db = scope.ServiceProvider.GetRequiredService<PagerContext>();
+
+ // Prepare API request
+ var request = new BartAvailabilityRequest() {
+ RelatedDay = "2024-10-22",
+ // TODO: retrieve this from config
+ LoginToken = "<login token here>",
+ PermissionLevel = 2,
+ UserQualFilterIds = [],
+ AvailabilityGroupFilterId = "1366",
+ GroupId = "1366",
+ Length = 0
+ };
+
+ // Fetch the data
+ var response = await httpClient.PostAsJsonAsync(
+ AvailabilityEndpointUrl,
+ request,
+ new JsonSerializerOptions() {
+ PropertyNamingPolicy = null
+ });
+
+ var responseData = await response.Content
+ .ReadFromJsonAsync<BartAvailabilityResponse>();
+
+ // Parse out a list of users from the response
+ var users = responseData.GetUserAvailabilityListResult.List
+ .Select(e => new BartMember() {
+ BartMemberId = e.UserId,
+ MemberName = e.MemberName
+ })
+ .ToArray();
+
+ var existingUsers = db.BartMembers
+ .Select(m => m.BartMemberId)
+ .ToArray();
+
+ // Add new users to the DB and update the names of existing ones
+ foreach(var user in users) {
+ if(existingUsers.Contains(user.BartMemberId))
+ db.Update(user);
+ else
+ db.Add(user);
+ }
+
+ await db.SaveChangesAsync();
+
+ LinkedList<BartAvailabilityRecord> availabilityRecords = new();
+
+ foreach(var entry in responseData.GetUserAvailabilityListResult.List) {
+ for(int hour = 0; hour < 24; hour++) {
+ var timestamp = new DateTime(
+ DateOnly.FromDateTime(entry.RelatedDay),
+ new TimeOnly(hour, 0),
+ DateTimeKind.Local);
+
+ var status = (BartStatus) entry.GetType()
+ .GetProperty($"Block{hour + 1}")!
+ .GetValue(entry)!;
+
+ var record = new BartAvailabilityRecord() {
+ Timestamp = timestamp.ToUniversalTime(),
+ Member = users.First(u => u.BartMemberId == entry.UserId),
+ Status = status,
+ IsDefault = entry.AvailabilityId < 0,
+ ModifiedOn = entry.ModifiedOn,
+ ModifiedBy = users.FirstOrDefault(u => u.MemberName == entry.ModifiedBy),
+ CreatedBy = users.FirstOrDefault(u => u.MemberName == entry.CreatedBy)
+ };
+
+ // Optionally set the create/modify time if one is present in the fetched record
+ if(entry.ModifiedOn is not null)
+ record.ModifiedOn =
+ DateTime.SpecifyKind((DateTime) entry.ModifiedOn, DateTimeKind.Utc);
+ if(entry.CreatedOn is not null)
+ record.CreatedOn =
+ DateTime.SpecifyKind((DateTime) entry.CreatedOn, DateTimeKind.Utc);
+
+ availabilityRecords.AddLast(record);
+ }
+ }
+
+ var toAdd = availabilityRecords
+ .ExceptBy(db.BartAvailabilityRecords.Select(e => e.Hashcode), e => e.Hashcode);
+
+ await db.BartAvailabilityRecords.AddRangeAsync(toAdd);
+ await db.SaveChangesAsync();
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken) {
+ logger.LogInformation("BART service starting...");
+ httpClient = new();
+ await FetchAsync(DateTime.Now);
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken) {
+ logger.LogInformation("BART service stopping...");
+ httpClient.Dispose();
+ return Task.CompletedTask;
+ }
+
+ private record BartAvailabilityRequest {
+ public string RelatedDay { get; set; }
+ public string LoginToken { get; set; }
+ public int PermissionLevel { get; set; }
+ public string[] UserQualFilterIds { get; set; }
+ public string AvailabilityGroupFilterId { get; set; }
+ public string GroupId { get; set; }
+ [JsonPropertyName("length")]
+ public int Length { get; set; }
+ }
+
+ private record BartAvailabilityResponse {
+ public BartAvailabilityResponseResult GetUserAvailabilityListResult { get; set; }
+ }
+
+ private record BartAvailabilityResponseResult {
+ public List<BartAvailabilityResponseEntry> List { get; set; }
+ }
+
+ private record BartAvailabilityResponseEntry {
+ public int AvailabilityId { get; set; }
+ [JsonConverter(typeof(BartDateTimeConverter))]
+ public DateTime RelatedDay { get; set; }
+ public string MemberName { get; set; }
+ [JsonConverter(typeof(BartNullableDateTimeConverter))]
+ public DateTime? CreatedOn { get; set; }
+ public string? CreatedBy { get; set; }
+ [JsonConverter(typeof(BartNullableDateTimeConverter))]
+ public DateTime? ModifiedOn { get; set; }
+ public string? ModifiedBy { get; set; }
+ public int UserId { get; set; }
+ public int Block1 { get; set; }
+ public int Block2 { get; set; }
+ public int Block3 { get; set; }
+ public int Block4 { get; set; }
+ public int Block5 { get; set; }
+ public int Block6 { get; set; }
+ public int Block7 { get; set; }
+ public int Block8 { get; set; }
+ public int Block9 { get; set; }
+ public int Block10 { get; set; }
+ public int Block11 { get; set; }
+ public int Block12 { get; set; }
+ public int Block13 { get; set; }
+ public int Block14 { get; set; }
+ public int Block15 { get; set; }
+ public int Block16 { get; set; }
+ public int Block17 { get; set; }
+ public int Block18 { get; set; }
+ public int Block19 { get; set; }
+ public int Block20 { get; set; }
+ public int Block21 { get; set; }
+ public int Block22 { get; set; }
+ public int Block23 { get; set; }
+ public int Block24 { get; set; }
+ }
+
+ private class BartDateTimeConverter : JsonConverter<DateTime> {
+ private const string Format = "yyyy-MM-dd HH:mm:ss";
+
+ public override DateTime Read(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options) {
+
+ return DateTime.ParseExact(reader.GetString()!, Format, null);
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer,
+ DateTime value,
+ JsonSerializerOptions options) {
+
+ writer.WriteStringValue(value.ToString(Format));
+ }
+ }
+
+ private class BartNullableDateTimeConverter : JsonConverter<DateTime?> {
+ private const string Format = "yyyy-MM-dd HH:mm:ss";
+
+ public override DateTime? Read(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options) {
+
+ string? value = reader.GetString();
+ if(string.IsNullOrEmpty(value))
+ return null;
+
+ return DateTime.ParseExact(value, Format, null);
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer,
+ DateTime? value,
+ JsonSerializerOptions options) {
+
+ writer.WriteStringValue(value?.ToString(Format));
+ }
+ }
+ }
+}
diff --git a/Program.cs b/Program.cs
index cc60d92..40bd618 100644
--- a/Program.cs
+++ b/Program.cs
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
+using PagerParser.Bart;
using System.Text.Json.Serialization;
namespace PagerParser;
@@ -24,6 +25,7 @@ public class Program {
builder.Services.AddSingleton<IRootPagerHandler>(p => p.GetRequiredService<RootPagerHandler>());
builder.Services.AddSingleton<IHostedService>(p => p.GetRequiredService<RootPagerHandler>());
builder.Services.AddHostedService<PagerFetchService>();
+ builder.Services.AddHostedService<BartService>();
var app = builder.Build();