Skip to content

Commit 3bc5472

Browse files
committed
Show working groups on events page
1 parent df05f5f commit 3bc5472

File tree

4 files changed

+151
-132
lines changed

4 files changed

+151
-132
lines changed

scripts/sync-working-groups/sync-working-groups.ts

Lines changed: 26 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,38 @@ const OUTPUT_FILE = new URL("./working-group-events.ndjson", import.meta.url)
1010
const DAYS_BACK = 30
1111
const DAYS_TO_KEEP = 90
1212
const DAYS_AHEAD = 30
13-
const DATETIME_REGEX =
14-
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/
1513

1614
const Instant = type({
1715
"dateTime?": "string",
18-
"date?": "string",
19-
"timeZone?": "string",
16+
"date?": "string", // full day events have just date
2017
})
18+
.pipe((value): string => {
19+
if (value.dateTime) return value.dateTime
20+
if (value.date) return `${value.date}T00:00:00Z`
21+
throw new Error("Instant is missing date/dateTime")
22+
})
23+
.to("string.date")
2124

22-
const Event = type({
25+
const calendarEventSchema = type({
2326
id: "string",
27+
"title?": "string",
2428
"status?": "string",
25-
"summary?": "string",
26-
"location?": "string",
2729
"description?": "string",
30+
"location?": "string",
2831
start: Instant,
2932
end: Instant,
3033
htmlLink: "string",
3134
updated: "string",
3235
})
3336

3437
const responseSchema = type({
35-
items: Event.array(),
38+
items: calendarEventSchema.array(),
3639
"nextSyncToken?": "string",
3740
"nextPageToken?": "string",
3841
})
3942

40-
const WorkingGroupMeetingSchema = type({
41-
id: "string",
42-
title: "string",
43-
"description?": "string",
44-
"location?": "string",
45-
start: Instant,
46-
end: Instant,
47-
htmlLink: "string",
48-
updated: "string",
49-
calendarId: "string",
50-
})
51-
52-
type CalendarEvent = typeof Event.infer
53-
export type WorkingGroupMeeting = typeof WorkingGroupMeetingSchema.infer
43+
export type WorkingGroupMeeting =
44+
typeof calendarEventSchema.inferIntrospectableOut
5445

5546
async function main() {
5647
if (!API_KEY) {
@@ -62,10 +53,7 @@ async function main() {
6253
const existingMeetings = await readExistingMeetings()
6354
console.log(`Found ${existingMeetings.length} existing event(s) in file`)
6455

65-
const lastMeeting = existingMeetings.at(-1)
66-
const lastMeetingStart =
67-
lastMeeting?.start.dateTime ??
68-
(lastMeeting?.start.date ? `${lastMeeting.start.date}T00:00:00Z` : null)
56+
const lastMeetingStart = existingMeetings.at(-1)?.start ?? null
6957
const cutoffDate = new Date(
7058
now.getTime() - DAYS_TO_KEEP * 24 * 60 * 60 * 1000,
7159
)
@@ -77,7 +65,7 @@ async function main() {
7765

7866
const timeMin =
7967
lastMeetingStart !== null && lastMeetingStart !== undefined
80-
? new Date(Math.min(Date.parse(lastMeetingStart) + 1, now.getTime()))
68+
? new Date(Math.min(Date.parse(lastMeetingStart), now.getTime()))
8169
: new Date(now.getTime() - DAYS_BACK * 24 * 60 * 60 * 1000)
8270

8371
const timeMax = new Date(now.getTime() + DAYS_AHEAD * 24 * 60 * 60 * 1000)
@@ -110,9 +98,9 @@ async function main() {
11098

11199
const payload = responseSchema.assert(body)
112100

113-
let allNewMeetings = payload.items
101+
let newMeetings = payload.items
114102
.filter(event => event.status !== "cancelled")
115-
.map(toWorkingGroupMeeting)
103+
.map(event => calendarEventSchema.out.assert(event))
116104

117105
if (payload.nextPageToken) {
118106
let pageToken: string | undefined = payload.nextPageToken
@@ -129,17 +117,16 @@ async function main() {
129117
throw new Error(`Page fetch failed: ${pageResponse.status}`)
130118
}
131119
const pagePayload = responseSchema.assert(pageBody)
132-
allNewMeetings = [
133-
...allNewMeetings,
120+
newMeetings = [
121+
...newMeetings,
134122
...pagePayload.items
135123
.filter(event => event.status !== "cancelled")
136-
.map(toWorkingGroupMeeting),
124+
.map(event => calendarEventSchema.out.assert(event)),
137125
]
138126
pageToken = pagePayload.nextPageToken
139127
}
140128
}
141129

142-
const newMeetings = allNewMeetings
143130
const newIds = new Set(newMeetings.map(meeting => meeting.id))
144131
const existingIds = new Set(existingMeetings.map(meeting => meeting.id))
145132
const newCount = Array.from(newIds).filter(id => !existingIds.has(id)).length
@@ -159,8 +146,7 @@ async function main() {
159146
const futureLimit = new Date(now.getTime() + DAYS_AHEAD * 24 * 60 * 60 * 1000)
160147
const futureLimitStr = futureLimit.toISOString().split("T")[0]
161148
const filteredMeetings = allMeetings.filter(meeting => {
162-
const start = meeting.start.dateTime ?? meeting.start.date ?? ""
163-
const startDate = start.split("T")[0]
149+
const startDate = meeting.start.split("T")[0]
164150
return startDate >= cutoffDateStr && startDate <= futureLimitStr
165151
})
166152

@@ -171,18 +157,16 @@ async function main() {
171157
`Filtered to ${filteredMeetings.length} event(s) after removing old entries`,
172158
)
173159

174-
const sortedMeetings = filteredMeetings.sort((a, b) => {
175-
const aStart = a.start.dateTime ?? a.start.date ?? ""
176-
const bStart = b.start.dateTime ?? b.start.date ?? ""
177-
return aStart.localeCompare(bStart)
178-
})
160+
const sortedMeetings = filteredMeetings.sort((a, b) =>
161+
a.start.localeCompare(b.start),
162+
)
179163

180164
const ndjson = sortedMeetings.map(event => JSON.stringify(event)).join("\n")
181165
const content = sortedMeetings.length > 0 ? `${ndjson}\n` : ""
182166
await writeFile(OUTPUT_FILE, content, "utf8")
183167

184168
console.log(
185-
`Saved ${sortedMeetings.length} event(s) to ${OUTPUT_FILE.pathname}`,
169+
`Saved ${sortedMeetings.length} event(s) (${newCount} new) to ${OUTPUT_FILE.pathname}`,
186170
)
187171
}
188172

@@ -193,7 +177,7 @@ async function readExistingMeetings(): Promise<WorkingGroupMeeting[]> {
193177
.trim()
194178
.split("\n")
195179
.filter(line => line.trim())
196-
.map(line => WorkingGroupMeetingSchema.assert(JSON.parse(line)))
180+
.map(line => calendarEventSchema.out.assert(JSON.parse(line)))
197181
} catch (error: any) {
198182
if (error.code === "ENOENT") {
199183
return []
@@ -222,38 +206,6 @@ function mergeMeetings(
222206
return Array.from(byId.values())
223207
}
224208

225-
function toWorkingGroupMeeting(event: CalendarEvent): WorkingGroupMeeting {
226-
return WorkingGroupMeetingSchema.assert({
227-
id: event.id,
228-
title: event.summary || "Untitled working group meeting",
229-
...(event.description && { description: event.description }),
230-
...(event.location && { location: event.location }),
231-
start: event.start,
232-
end: event.end,
233-
htmlLink: assertUrl(event.htmlLink, "event.htmlLink"),
234-
updated: assertDateTime(event.updated, "event.updated"),
235-
calendarId: CALENDAR_ID,
236-
})
237-
}
238-
239-
function assertDateTime(value: string, label: string) {
240-
if (!DATETIME_REGEX.test(value)) {
241-
throw new Error(
242-
`Invalid ${label}: expected YYYY-MM-DDThh:mm:ssZ or offset, received ${value}`,
243-
)
244-
}
245-
return value
246-
}
247-
248-
function assertUrl(value: string, label: string) {
249-
try {
250-
new URL(value)
251-
return value
252-
} catch {
253-
throw new Error(`Invalid ${label}: received ${value}`)
254-
}
255-
}
256-
257209
try {
258210
await main()
259211
} catch (error: unknown) {

0 commit comments

Comments
 (0)