Skip to content

Commit f14560d

Browse files
WIP
1 parent a578a0d commit f14560d

16 files changed

Lines changed: 731 additions & 0 deletions

Importer/App.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.IO.Abstractions;
2+
using System.Text.Json;
3+
using Microsoft.Extensions.Hosting;
4+
5+
namespace FLRC.Leaderboards.Importer;
6+
7+
public sealed class App(IFileSystem fs, Importer importer, Action<string> log) : IHostedService
8+
{
9+
public async Task StartAsync(CancellationToken cancellationToken)
10+
{
11+
log("Starting...");
12+
var data = await fs.File.ReadAllTextAsync("TrailCircuit.json", cancellationToken);
13+
var courses = JsonSerializer.Deserialize<CourseImportConfig[]>(data)!;
14+
await importer.Run(courses);
15+
log("Done!");
16+
}
17+
18+
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
19+
}

Importer/Challenge.json

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
[
2+
{
3+
"Name": "2025 Participants",
4+
"Type": "WebScorer",
5+
"ExternalID": 386059
6+
},
7+
{
8+
"Name": "Sweet 1600",
9+
"Type": "WebScorer",
10+
"ExternalID": 386605
11+
},
12+
{
13+
"Name": "Mulholland Waterfalls",
14+
"Type": "WebScorer",
15+
"ExternalID": 386607
16+
},
17+
{
18+
"Name": "Lakefront Loops 5K",
19+
"Type": "WebScorer",
20+
"ExternalID": 386606
21+
},
22+
{
23+
"Name": "Cayuga Cliffs",
24+
"Type": "WebScorer",
25+
"ExternalID": 386608
26+
},
27+
{
28+
"Name": "Fall Creek Trails",
29+
"Type": "WebScorer",
30+
"ExternalID": 386609
31+
},
32+
{
33+
"Name": "Town & Gown Up & Down",
34+
"Type": "WebScorer",
35+
"ExternalID": 386610
36+
},
37+
{
38+
"Name": "Abbott Ascent",
39+
"Type": "WebScorer",
40+
"ExternalID": 386611
41+
},
42+
{
43+
"Name": "Black Diamond Cass to Gorge",
44+
"Type": "WebScorer",
45+
"ExternalID": 386612
46+
},
47+
{
48+
"Name": "Triple Hump",
49+
"Type": "WebScorer",
50+
"ExternalID": 386613
51+
},
52+
{
53+
"Name": "North Country Half",
54+
"Type": "WebScorer",
55+
"ExternalID": 386614
56+
},
57+
{
58+
"Name": "2024 Participants",
59+
"Type": "WebScorer",
60+
"ExternalID": 346590
61+
},
62+
{
63+
"Name": "Sweet 1600",
64+
"Type": "WebScorer",
65+
"ExternalID": 348512
66+
},
67+
{
68+
"Name": "Duck Trails",
69+
"Type": "WebScorer",
70+
"ExternalID": 348502
71+
},
72+
{
73+
"Name": "Lakefront Loops 5K",
74+
"Type": "WebScorer",
75+
"ExternalID": 348503
76+
},
77+
{
78+
"Name": "Lindsay-Parsons",
79+
"Type": "WebScorer",
80+
"ExternalID": 348504
81+
},
82+
{
83+
"Name": "FH Fox",
84+
"Type": "WebScorer",
85+
"ExternalID": 348505
86+
},
87+
{
88+
"Name": "Valley Views",
89+
"Type": "WebScorer",
90+
"ExternalID": 348506
91+
},
92+
{
93+
"Name": "Treman Trailipop",
94+
"Type": "WebScorer",
95+
"ExternalID": 348507
96+
},
97+
{
98+
"Name": "Hammond Hill Hoctathon",
99+
"Type": "WebScorer",
100+
"ExternalID": 348508
101+
},
102+
{
103+
"Name": "Run Rabbit Run",
104+
"Type": "WebScorer",
105+
"ExternalID": 348509
106+
},
107+
{
108+
"Name": "Freeville Fly-In",
109+
"Type": "WebScorer",
110+
"ExternalID": 348510
111+
},
112+
{
113+
"Name": "2023 Participants",
114+
"Type": "WebScorer",
115+
"ExternalID": 308700
116+
},
117+
{
118+
"Name": "Sweet 1600",
119+
"Type": "WebScorer",
120+
"ExternalID": 311086
121+
},
122+
{
123+
"Name": "Ellis Hollow Creek Crossings",
124+
"Type": "WebScorer",
125+
"ExternalID": 310931
126+
},
127+
{
128+
"Name": "Lakefront Loops 5K",
129+
"Type": "WebScorer",
130+
"ExternalID": 310933
131+
},
132+
{
133+
"Name": "Lime Hollow",
134+
"Type": "WebScorer",
135+
"ExternalID": 310934
136+
},
137+
{
138+
"Name": "Cornell Scenic Circuit",
139+
"Type": "WebScorer",
140+
"ExternalID": 310936
141+
},
142+
{
143+
"Name": "Six Mile Creek",
144+
"Type": "WebScorer",
145+
"ExternalID": 310935
146+
},
147+
{
148+
"Name": "Black Diamond Park to Park",
149+
"Type": "WebScorer",
150+
"ExternalID": 310938
151+
},
152+
{
153+
"Name": "Blueberry Patch",
154+
"Type": "WebScorer",
155+
"ExternalID": 310937
156+
},
157+
{
158+
"Name": "Ludlowville Loop",
159+
"Type": "WebScorer",
160+
"ExternalID": 310939
161+
},
162+
{
163+
"Name": "Dryden Lake Lollipop",
164+
"Type": "WebScorer",
165+
"ExternalID": 310940
166+
},
167+
{
168+
"Name": "2022 Participants",
169+
"Type": "WebScorer",
170+
"ExternalID": 271794
171+
},
172+
{
173+
"Name": "Sweet 1600",
174+
"Type": "WebScorer",
175+
"ExternalID": 273395
176+
},
177+
{
178+
"Name": "Lansing Center Trail",
179+
"Type": "WebScorer",
180+
"ExternalID": 273396
181+
},
182+
{
183+
"Name": "Beebe Lake",
184+
"Type": "WebScorer",
185+
"ExternalID": 273397
186+
},
187+
{
188+
"Name": "Taughannock Rim & Falls",
189+
"Type": "WebScorer",
190+
"ExternalID": 273398
191+
},
192+
{
193+
"Name": "Long Loomis",
194+
"Type": "WebScorer",
195+
"ExternalID": 273399
196+
},
197+
{
198+
"Name": "Inlet Shore Trail",
199+
"Type": "WebScorer",
200+
"ExternalID": 273400
201+
},
202+
{
203+
"Name": "East Hill Dryden Rail Trail",
204+
"Type": "WebScorer",
205+
"ExternalID": 273401
206+
},
207+
{
208+
"Name": "Jim Schug Trail",
209+
"Type": "WebScorer",
210+
"ExternalID": 273402
211+
},
212+
{
213+
"Name": "Brookton Hill & Dale",
214+
"Type": "WebScorer",
215+
"ExternalID": 273403
216+
},
217+
{
218+
"Name": "Lick Brook & Treman FLT",
219+
"Type": "WebScorer",
220+
"ExternalID": 273404
221+
},
222+
{
223+
"Name": "2021 Participants",
224+
"Type": "WebScorer",
225+
"ExternalID": 230581
226+
},
227+
{
228+
"Name": "East Hill Rec Way",
229+
"Type": "WebScorer",
230+
"ExternalID": 233198
231+
},
232+
{
233+
"Name": "Cornell Botanic Gardens",
234+
"Type": "WebScorer",
235+
"ExternalID": 235658
236+
},
237+
{
238+
"Name": "Waterfront Trail",
239+
"Type": "WebScorer",
240+
"ExternalID": 235665
241+
},
242+
{
243+
"Name": "Tortoise & Hare",
244+
"Type": "WebScorer",
245+
"ExternalID": 237535
246+
},
247+
{
248+
"Name": "Danby Down & Dirty",
249+
"Type": "WebScorer",
250+
"ExternalID": 237533
251+
},
252+
{
253+
"Name": "South Hill Rec Way",
254+
"Type": "WebScorer",
255+
"ExternalID": 236137
256+
},
257+
{
258+
"Name": "Thom B. Trail Run",
259+
"Type": "WebScorer",
260+
"ExternalID": 237534
261+
},
262+
{
263+
"Name": "Forest Frolic",
264+
"Type": "WebScorer",
265+
"ExternalID": 237877
266+
},
267+
{
268+
"Name": "Black Diamond Trail",
269+
"Type": "WebScorer",
270+
"ExternalID": 236033
271+
},
272+
{
273+
"Name": "Pseudo Skunk Cabbage",
274+
"Type": "WebScorer",
275+
"ExternalID": 233928
276+
}
277+
]

Importer/CourseImportConfig.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace FLRC.Leaderboards.Importer;
2+
3+
public sealed record CourseImportConfig
4+
{
5+
public required string Name { get; init; }
6+
public required string Type { get; init; }
7+
public required uint ExternalID { get; init; }
8+
public string? Distance { get; init; }
9+
public DateTime? Date { get; init; }
10+
}

Importer/Importer.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using FLRC.Leaderboards.Core.Races;
2+
using FLRC.Leaderboards.Web.Areas.Admin.Services;
3+
4+
namespace FLRC.Leaderboards.Importer;
5+
6+
public sealed class Importer(IImportManager importManager, IRaceService raceService, Action<string> log)
7+
{
8+
public async Task Run(CourseImportConfig[] definitions)
9+
{
10+
log("Getting all courses...");
11+
var races = await raceService.GetAllRaces();
12+
var courses = races.SelectMany(r => r.Courses).ToArray();
13+
log($"{courses.Length} courses found!");
14+
15+
foreach (var definition in definitions)
16+
{
17+
var course = courses.FirstOrDefault(c => c.Race.Name == definition.Name
18+
&& (definition.Distance is null
19+
|| new Distance(c.DistanceDisplay).Equals(new Distance(definition.Distance))
20+
|| (c.DistanceDisplay == "13.1 mi" && definition.Distance == "Half Marathon")
21+
|| (c.DistanceDisplay == "26.2 mi" && definition.Distance == "Marathon")));
22+
switch (course)
23+
{
24+
case null when definition.Name.EndsWith("Participants"):
25+
await importManager.ImportAthletes(definition.Type, definition.ExternalID);
26+
break;
27+
case null:
28+
log($"Course not found for {definition.Name}!");
29+
continue;
30+
default:
31+
await importManager.ImportResults(course, definition.Type, definition.ExternalID, definition.Date);
32+
break;
33+
}
34+
35+
log($"Imported \"{definition.Name} ({course?.DistanceDisplay})\"!");
36+
}
37+
}
38+
}

Importer/Importer.csproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<RootNamespace>FLRC.Leaderboards.Importer</RootNamespace>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<None Include="*.json" CopyToOutputDirectory="PreserveNewest"/>
13+
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.1.0"/>
14+
<ProjectReference Include="..\Web\Web.csproj"/>
15+
</ItemGroup>
16+
17+
</Project>

Importer/Program.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using FLRC.Leaderboards.Core.Config;
2+
using FLRC.Leaderboards.Importer;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Hosting;
5+
6+
var builder = Host.CreateDefaultBuilder(args);
7+
builder.ConfigureServices((_, services) =>
8+
{
9+
FLRC.Leaderboards.Web.App.ConfigureServices(services);
10+
services.AddSingleton<Action<string>>(Console.WriteLine);
11+
services.AddSingleton<Importer>();
12+
services.AddHostedService<App>();
13+
}
14+
);
15+
16+
var app = builder.Build();
17+
await app.StartAsync();
18+
await app.StopAsync();
19+
await app.WaitForShutdownAsync();

0 commit comments

Comments
 (0)