1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
|
using Microsoft.EntityFrameworkCore;
using PagerParser.Bart;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Security.Cryptography;
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 Guid Hashcode {
get {
int?[] hashes = [
Timestamp.GetHashCode(),
Member.BartMemberId.GetHashCode(),
Status.GetHashCode(),
IsDefault.GetHashCode(),
ModifiedOn.GetHashCode(),
ModifiedBy?.BartMemberId.GetHashCode(),
CreatedOn.GetHashCode(),
CreatedBy?.BartMemberId.GetHashCode()
];
var bytes = hashes
.Where(x => x is not null)
.Cast<int>()
.SelectMany(BitConverter.GetBytes)
.ToArray();
using var md5 = MD5.Create();
return new Guid(md5.ComputeHash(bytes));
}
set => _ = value;
}
}
public class BartService : IHostedService {
private const string AvailabilityEndpointUrl =
@"https://bartapp.net/webapp/webservice/WebsiteService.svc/GetUserAvailabilityList";
private IConfiguration config;
private ILogger logger;
private IServiceProvider serviceProvider;
private string? loginToken;
private HttpClient httpClient;
public BartService(
IConfiguration config,
ILogger<BartService> logger,
IServiceProvider serviceProvider) {
this.config = config;
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 = DateTime.Now.ToString("yyyy-MM-dd"),
LoginToken = loginToken,
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...");
loginToken = config.GetValue<string>("PagerParser:Bart:Token");
httpClient = new();
await FetchAsync(DateTime.Now);
}
public Task StopAsync(CancellationToken cancellationToken) {
logger.LogInformation("BART service stopping...");
loginToken = null;
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));
}
}
}
}
|