Skip to content

Commit 30f6145

Browse files
authored
feat: automatic event ingest from sessionize/meetup (#103)
* feat: add event sync planning layer * feat: sync sessionize events into umbraco * fix: use display logic for current events * chore: refresh event sync test fixtures * chore: run event sync every two hours * chore: set default meetup group id * feat: import sessionize speaker images * refactor: fetch upcoming meetup events only * chore: inline meetup fallback * refactor: inline meetup event fetch * fix: skip past events during sync * refactor: simplify sessionize event planning * refactor: resolve presenter type key by alias * refactor: split external api namespaces * feat: match sessionize presenters to members * refactor: search member presenters by name * chore regenerate umbraco models * fix: match meetup titles with event prefixes * fix: publish presentation explicitly after event to fix race * chore: add option for enabling event sync * fix: issues with tests
1 parent e9f8630 commit 30f6145

28 files changed

Lines changed: 1823 additions & 22 deletions
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using SgfDevs.Dev;
3+
using Xunit;
4+
5+
namespace SgfDevs.Tests;
6+
7+
public class EventDisplayServiceTests
8+
{
9+
private readonly EventDisplayService _service = new();
10+
11+
[Fact]
12+
public void IsCurrentOrUpcoming_ReturnsTrueBeforeGracePeriodEnds()
13+
{
14+
var startsAtLocal = new DateTime(2026, 5, 6, 18, 30, 0);
15+
16+
var result = _service.IsCurrentOrUpcoming(startsAtLocal, new DateTime(2026, 5, 6, 19, 29, 59));
17+
18+
Assert.True(result);
19+
}
20+
21+
[Fact]
22+
public void IsCurrentOrUpcoming_ReturnsFalseOnceGracePeriodEnds()
23+
{
24+
var startsAtLocal = new DateTime(2026, 5, 6, 18, 30, 0);
25+
26+
var result = _service.IsCurrentOrUpcoming(startsAtLocal, new DateTime(2026, 5, 6, 19, 30, 0));
27+
28+
Assert.False(result);
29+
}
30+
31+
[Fact]
32+
public void GetCurrentOrNextEvent_SkipsExpiredEvents()
33+
{
34+
var now = new DateTime(2026, 5, 6, 20, 0, 0);
35+
var events = new[]
36+
{
37+
new DateTime(2026, 5, 6, 18, 30, 0),
38+
new DateTime(2026, 6, 3, 18, 30, 0)
39+
};
40+
41+
var result = _service.GetCurrentOrNextEvent(events, static item => item, now);
42+
43+
Assert.Equal(new DateTime(2026, 6, 3, 18, 30, 0), result);
44+
}
45+
46+
[Fact]
47+
public void GetCurrentAndUpcomingEvents_ReturnsOnlyVisibleWindow()
48+
{
49+
var now = new DateTime(2026, 5, 6, 19, 0, 0);
50+
var events = new[]
51+
{
52+
new DateTime(2026, 4, 1, 18, 30, 0),
53+
new DateTime(2026, 5, 6, 18, 30, 0),
54+
new DateTime(2026, 6, 3, 18, 30, 0)
55+
};
56+
57+
var result = _service.GetCurrentAndUpcomingEvents(events, static item => item, now);
58+
59+
Assert.Equal(
60+
[new DateTime(2026, 5, 6, 18, 30, 0), new DateTime(2026, 6, 3, 18, 30, 0)],
61+
result);
62+
}
63+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using SgfDevs.Dev.EventSync;
3+
using SgfDevs.Dev.EventSync.Sessionize;
4+
using Xunit;
5+
6+
namespace SgfDevs.Tests;
7+
8+
public class EventSyncImportFilterTests
9+
{
10+
private readonly EventSyncImportFilter _filter = new();
11+
12+
[Fact]
13+
public void GetUpcomingEvents_ExcludesPastEvents()
14+
{
15+
var nowLocal = new DateTime(2026, 6, 3, 18, 0, 0);
16+
var plans = new[]
17+
{
18+
new ImportedEventPlan("Past Dev Night", new DateTime(2026, 5, 6, 18, 30, 0), []),
19+
new ImportedEventPlan("Future Dev Night", new DateTime(2026, 7, 1, 18, 30, 0), [])
20+
};
21+
22+
var result = _filter.GetUpcomingEvents(plans, nowLocal);
23+
24+
var plan = Assert.Single(result);
25+
Assert.Equal("Future Dev Night", plan.Name);
26+
}
27+
28+
[Fact]
29+
public void GetUpcomingEvents_KeepsEventsStartingNow()
30+
{
31+
var nowLocal = new DateTime(2026, 6, 3, 18, 30, 0);
32+
var plans = new[]
33+
{
34+
new ImportedEventPlan("Current Dev Night", nowLocal, []),
35+
new ImportedEventPlan("Next Dev Night", new DateTime(2026, 7, 1, 18, 30, 0), [])
36+
};
37+
38+
var result = _filter.GetUpcomingEvents(plans, nowLocal);
39+
40+
Assert.Equal(2, result.Count);
41+
Assert.Equal("Current Dev Night", result[0].Name);
42+
}
43+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System.Collections.Generic;
2+
using System.Text.Json;
3+
using SgfDevs.Dev.EventSync;
4+
using SgfDevs.Dev.EventSync.Sessionize;
5+
using Xunit;
6+
7+
namespace SgfDevs.Tests;
8+
9+
public class ImportedPresenterBlockBuilderTests
10+
{
11+
private readonly ImportedPresenterBlockBuilder _builder = new(
12+
new Guid("1bdea08d-8393-4e70-85a9-2ca27bef54f1"),
13+
new Guid("5ff3a2c3-9dc3-4131-8f07-99c2c0a38be5"));
14+
15+
[Fact]
16+
public void Build_ReturnsEmptyStringWhenNoPresentersExist()
17+
{
18+
var result = _builder.Build([]);
19+
20+
Assert.Equal(string.Empty, result);
21+
}
22+
23+
[Fact]
24+
public void Build_CreatesBlockListPayloadForNonMemberPresenters()
25+
{
26+
var result = _builder.Build(
27+
[
28+
new ImportedPresenterPlan("speaker-1", "Bertram Gilfoyle", null, MatchedMemberKey: new Guid("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")),
29+
new ImportedPresenterPlan("speaker-2", "Dinesh Chugtai", null)
30+
]);
31+
32+
using var document = JsonDocument.Parse(result);
33+
var root = document.RootElement;
34+
35+
Assert.Equal(2, root.GetProperty("contentData").GetArrayLength());
36+
Assert.Equal(2, root.GetProperty("expose").GetArrayLength());
37+
Assert.Equal(2, root.GetProperty("Layout").GetProperty("Umbraco.BlockList").GetArrayLength());
38+
39+
var firstValues = root.GetProperty("contentData")[0].GetProperty("values");
40+
Assert.Equal("member", firstValues[0].GetProperty("alias").GetString());
41+
Assert.Equal("umb://member/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", firstValues[0].GetProperty("value").GetString());
42+
43+
var secondValues = root.GetProperty("contentData")[1].GetProperty("values");
44+
Assert.Equal("presenterName", secondValues[0].GetProperty("alias").GetString());
45+
Assert.Equal("Dinesh Chugtai", secondValues[0].GetProperty("value").GetString());
46+
Assert.Equal(string.Empty, secondValues[1].GetProperty("value").GetString());
47+
}
48+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using SgfDevs.Dev.EventSync;
4+
using SgfDevs.Dev.EventSync.Sessionize;
5+
using Xunit;
6+
7+
namespace SgfDevs.Tests;
8+
9+
public class PresenterMemberMatcherTests
10+
{
11+
[Fact]
12+
public void MatchPresenters_AssignsMemberKeyWhenExactlyOneMemberMatches()
13+
{
14+
var memberKey = Guid.NewGuid();
15+
var matcher = CreateMatcher(new Dictionary<string, IReadOnlyList<Guid>>
16+
{
17+
["bertram gilfoyle"] = [memberKey]
18+
});
19+
20+
var result = Assert.Single(matcher.MatchPresenters([new ImportedPresenterPlan("speaker-1", "Bertram Gilfoyle", null)]));
21+
22+
Assert.Equal(memberKey, result.MatchedMemberKey);
23+
}
24+
25+
[Fact]
26+
public void MatchPresenters_LeavesPresenterUnchangedWhenNoMembersMatch()
27+
{
28+
var matcher = CreateMatcher(new Dictionary<string, IReadOnlyList<Guid>>());
29+
30+
var result = Assert.Single(matcher.MatchPresenters([new ImportedPresenterPlan("speaker-1", "Bertram Gilfoyle", null)]));
31+
32+
Assert.Null(result.MatchedMemberKey);
33+
}
34+
35+
[Fact]
36+
public void MatchPresenters_LeavesPresenterUnchangedWhenMultipleMembersMatch()
37+
{
38+
var matcher = CreateMatcher(new Dictionary<string, IReadOnlyList<Guid>>
39+
{
40+
["bertram gilfoyle"] = [Guid.NewGuid(), Guid.NewGuid()]
41+
});
42+
43+
var result = Assert.Single(matcher.MatchPresenters([new ImportedPresenterPlan("speaker-1", "Bertram Gilfoyle", null)]));
44+
45+
Assert.Null(result.MatchedMemberKey);
46+
}
47+
48+
[Fact]
49+
public void MatchPresenters_DeduplicatesSameMemberReturnedByMultipleSearchTerms()
50+
{
51+
var memberKey = Guid.NewGuid();
52+
var matcher = CreateMatcher(new Dictionary<string, IReadOnlyList<Guid>>
53+
{
54+
["bertram gilfoyle"] = [memberKey],
55+
["Bertram Gilfoyle"] = [memberKey]
56+
});
57+
58+
var result = Assert.Single(matcher.MatchPresenters([new ImportedPresenterPlan("speaker-1", "Bertram Gilfoyle", null)]));
59+
60+
Assert.Equal(memberKey, result.MatchedMemberKey);
61+
}
62+
63+
private static PresenterMemberMatcher CreateMatcher(IReadOnlyDictionary<string, IReadOnlyList<Guid>> resultsBySearchTerm)
64+
{
65+
return new PresenterMemberMatcher(searchTerm =>
66+
resultsBySearchTerm.TryGetValue(searchTerm, out var result)
67+
? result
68+
: []);
69+
}
70+
71+
}

0 commit comments

Comments
 (0)