Skip to content

Commit 111af48

Browse files
committed
Update speaker details
1 parent 0e6a30d commit 111af48

File tree

2 files changed

+86
-39
lines changed

2 files changed

+86
-39
lines changed

scripts/sync-sched/sync.ts

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import { parseArgs } from "node:util"
55
import { readFileSync, existsSync } from "node:fs"
66
import { join } from "node:path"
77

8-
import { getSchedule, getSpeakers } from "@/app/conf/_api/sched-client"
8+
import {
9+
getSchedule,
10+
getSpeakerDetails,
11+
getSpeakers,
12+
RequestContext,
13+
} from "@/app/conf/_api/sched-client"
914
import { SchedSpeaker, ScheduleSession } from "@/app/conf/_api/sched-types"
10-
import { writeFile } from "node:fs/promises"
15+
import { readFile, writeFile } from "node:fs/promises"
1116

1217
// Sched API rate limit is 30 requests per minute per token.
1318
// This scripts fires:
@@ -27,6 +32,8 @@ const options = {
2732
},
2833
}
2934

35+
const unsafeKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>
36+
3037
async function main() {
3138
try {
3239
const { values } = parseArgs({ options })
@@ -58,19 +65,6 @@ if (require.main === module) {
5865
void main()
5966
}
6067

61-
function readJsonFile<T>(filePath: string, defaultValue: T): T {
62-
if (!existsSync(filePath)) {
63-
return defaultValue
64-
}
65-
66-
try {
67-
const content = readFileSync(filePath, "utf-8")
68-
return JSON.parse(content)
69-
} catch {
70-
return defaultValue
71-
}
72-
}
73-
7468
async function sync(year: number, token: string) {
7569
const apiUrl = {
7670
2023: "https://graphqlconf23.sched.com/api",
@@ -80,21 +74,25 @@ async function sync(year: number, token: string) {
8074

8175
assert(apiUrl, `API URL for year ${year} not found`)
8276

83-
const ctx = { apiUrl, token }
77+
const ctx: RequestContext = { apiUrl, token }
8478

8579
const scriptDir = __dirname
8680
const speakersFilePath = join(scriptDir, "speakers.json")
8781
const scheduleFilePath = join(scriptDir, `schedule-${year}.json`)
8882

89-
const existingSpeakers = readJsonFile<SchedSpeaker[]>(speakersFilePath, [])
90-
const existingSchedule = readJsonFile<ScheduleSession[]>(scheduleFilePath, [])
91-
9283
console.log("Getting schedule and speakers list...")
9384

9485
const schedule = getSchedule(ctx)
9586
const speakers = getSpeakers(ctx)
96-
97-
const scheduleComparison = compare(existingSchedule, await schedule, "id")
87+
const existingSchedule = readFile(scheduleFilePath, "utf-8").then(JSON.parse)
88+
const existingSpeakers = readFile(speakersFilePath, "utf-8").then(JSON.parse)
89+
90+
const scheduleComparison = compare(
91+
await existingSpeakers,
92+
await schedule,
93+
"id",
94+
{ merge: false },
95+
)
9896
printComparison(scheduleComparison, "sessions", "id")
9997

10098
const writeSchedule = writeFile(
@@ -103,37 +101,81 @@ async function sync(year: number, token: string) {
103101
)
104102

105103
const speakerComparison = compare(
106-
existingSpeakers,
104+
await existingSpeakers,
107105
await speakers,
108106
"username",
107+
{ merge: true },
108+
)
109+
110+
await updateSpeakerDetails(
111+
ctx,
112+
speakerComparison,
113+
SPEAKER_DETAILS_REQUEST_QUOTA,
109114
)
115+
110116
printComparison(speakerComparison, "speakers", "username")
111117

112118
const updatedSpeakers = [
113119
...speakerComparison.removed,
114120
...speakerComparison.unchanged,
115121
...speakerComparison.changed.map(change => change.new),
116-
...speakerComparison.added.map(speaker => ({
117-
...speaker,
118-
["~syncedAt"]: -1,
119-
})),
122+
...speakerComparison.added,
120123
].sort((a, b) => a.username.localeCompare(b.username))
121124

122125
const writeSpeakers = writeFile(
123126
speakersFilePath,
124127
JSON.stringify(updatedSpeakers, null, 2),
125128
)
126129

127-
writeSpeakers.then(() => {
128-
console.log(
129-
`Updated speakers data: ${updatedSpeakers.length} total speakers`,
130-
)
131-
})
132-
133130
await writeSchedule
134131
await writeSpeakers
135132
}
136133

134+
async function updateSpeakerDetails(
135+
ctx: RequestContext,
136+
/** mutated in place */
137+
comparison: Comparison<SchedSpeaker>,
138+
quota: number,
139+
) {
140+
const locations = new Map<
141+
string /* username */,
142+
[key: keyof Comparison<unknown>, index: number]
143+
>()
144+
145+
for (const key of unsafeKeys(comparison)) {
146+
const items = comparison[key as keyof Comparison<SchedSpeaker>]
147+
for (let i = 0; i < items.length; i++) {
148+
let item = items[i]
149+
if (!("username" in item)) item = item.new
150+
locations.set(item.username, [key, i])
151+
}
152+
}
153+
154+
const byUpdateTime = [
155+
...comparison.unchanged,
156+
...comparison.changed.map(change => change.new),
157+
...comparison.added,
158+
].sort((a, b) => {
159+
const aTime = a["~syncedDetailsAt"] ?? 0
160+
const bTime = b["~syncedDetailsAt"] ?? 0
161+
return bTime - aTime
162+
})
163+
164+
const toUpdate = byUpdateTime.slice(0, quota)
165+
166+
const updated = await Promise.all(
167+
toUpdate.map(speaker => getSpeakerDetails(ctx, speaker.username)),
168+
)
169+
170+
for (const speaker of updated) {
171+
const location = locations.get(speaker.username)
172+
if (location) {
173+
const [key, index] = location
174+
comparison[key][index] = speaker
175+
}
176+
}
177+
}
178+
137179
function help() {
138180
return console.log("Usage: tsx sync.ts --year <year>")
139181
}
@@ -148,7 +190,12 @@ type Comparison<T> = {
148190
unchanged: T[]
149191
}
150192

151-
function compare<T extends object>(olds: T[], news: T[], key: keyof T) {
193+
function compare<T extends object>(
194+
olds: T[],
195+
news: T[],
196+
key: keyof T,
197+
options: { merge: boolean },
198+
) {
152199
const oldMap = new Map(olds.map(o => [o[key], o]))
153200
const newMap = new Map(news.map(n => [n[key], n]))
154201

@@ -163,7 +210,10 @@ function compare<T extends object>(olds: T[], news: T[], key: keyof T) {
163210
if (JSON.stringify(oldItem) === JSON.stringify(newItem)) {
164211
unchanged.push(oldItem)
165212
} else {
166-
changed.push({ old: oldItem, new: newItem })
213+
changed.push({
214+
old: oldItem,
215+
new: options.merge ? { ...oldItem, ...newItem } : newItem,
216+
})
167217
}
168218
} else {
169219
added.push(newItem)
@@ -211,10 +261,7 @@ function printComparison<T extends object>(
211261

212262
function objectDiff<T extends object>(change: Change<T>): string {
213263
const allKeys = [
214-
...new Set([
215-
...(Object.keys(change.old) as Array<keyof T>),
216-
...(Object.keys(change.new) as Array<keyof T>),
217-
]),
264+
...new Set([...unsafeKeys(change.old), ...unsafeKeys(change.new)]),
218265
].sort()
219266

220267
const diff = allKeys

src/app/conf/_api/sched-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ export type SchedSpeaker = {
2525
socialurls: { service: string; url: string }[]
2626
year?: "2025" | "2024" | "2023"
2727
/* unix timestamp */
28-
["~syncedAt"]?: number
28+
["~syncedDetailsAt"]?: number
2929
}

0 commit comments

Comments
 (0)