-
-
Notifications
You must be signed in to change notification settings - Fork 229
/
Copy pathchartConfigHelpers.ts
102 lines (92 loc) · 3.46 KB
/
chartConfigHelpers.ts
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
import {
Base64String,
ChartConfigsTableName,
DbInsertChartConfig,
DbRawChartConfig,
GrapherInterface,
R2GrapherConfigDirectory,
serializeChartConfig,
} from "@ourworldindata/types"
import { uuidv7 } from "uuidv7"
import * as db from "../db/db.js"
import {
saveGrapherConfigToR2,
saveGrapherConfigToR2ByUUID,
} from "./chartConfigR2Helpers.js"
/**
* One particular detail of of MySQL's JSON support is that MySQL _normalizes_ JSON when storing it.
* This means that the JSON string representation of a JSON object stored in MySQL is not equivalent
* to the input of an INSERT statement: it may have different whitespace and key order.
* This is a problem when we compute MD5 hashes of JSON objects using computed MySQL columns - in
* order to get the correct hash, we need to first store the JSON object in MySQL and then retrieve
* it and its hash again from MySQL immediately afterwards, such that we can store the exact same
* JSON representation and hash in R2 also.
* The below is a helper function that does just this.
* - @marcelgerber, 2024-11-20
*/
export const retrieveChartConfigFromDbAndSaveToR2 = async (
knex: db.KnexReadonlyTransaction,
chartConfigId: Base64String,
r2Path?: { directory: R2GrapherConfigDirectory; filename: string }
) => {
// We need to get the full config and the md5 hash from the database instead of
// computing our own md5 hash because MySQL normalizes JSON and our
// client computed md5 would be different from the ones computed by and stored in R2
const fullConfigMd5: Pick<DbRawChartConfig, "full" | "fullMd5"> =
await knex(ChartConfigsTableName)
.select("full", "fullMd5")
.where({ id: chartConfigId })
.first()
if (!fullConfigMd5)
throw new Error(
`Chart config not found in the database! id=${chartConfigId}`
)
if (!r2Path) {
await saveGrapherConfigToR2ByUUID(
chartConfigId,
fullConfigMd5.full,
fullConfigMd5.fullMd5 as Base64String
)
} else {
await saveGrapherConfigToR2(
fullConfigMd5.full,
r2Path.directory,
r2Path.filename,
fullConfigMd5.fullMd5 as Base64String
)
}
return {
chartConfigId,
fullConfig: fullConfigMd5.full,
fullConfigMd5: fullConfigMd5.fullMd5,
}
}
export const updateChartConfigInDbAndR2 = async (
knex: db.KnexReadWriteTransaction,
chartConfigId: Base64String,
patchConfig: GrapherInterface,
fullConfig: GrapherInterface
) => {
await knex<DbInsertChartConfig>(ChartConfigsTableName)
.update({
patch: serializeChartConfig(patchConfig),
full: serializeChartConfig(fullConfig),
updatedAt: new Date(), // It's not updated automatically in the DB.
})
.where({ id: chartConfigId })
return retrieveChartConfigFromDbAndSaveToR2(knex, chartConfigId)
}
export const saveNewChartConfigInDbAndR2 = async (
knex: db.KnexReadWriteTransaction,
chartConfigId: Base64String | undefined,
patchConfig: GrapherInterface,
fullConfig: GrapherInterface
) => {
chartConfigId ??= uuidv7() as Base64String
await knex<DbInsertChartConfig>(ChartConfigsTableName).insert({
id: chartConfigId,
patch: serializeChartConfig(patchConfig),
full: serializeChartConfig(fullConfig),
})
return retrieveChartConfigFromDbAndSaveToR2(knex, chartConfigId)
}