triangle-utils 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +11 -12
- package/package.json +4 -2
- package/test.js +6 -0
- package/utils/Utils_DynamoDB.js +291 -0
- package/utils/Utils_Misc.js +114 -0
- package/utils/Utils_S3.js +55 -0
- package/clients.js +0 -24
- package/misc.js +0 -95
- package/utils/dynamodb.js +0 -282
package/index.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
import states from "./states.js"
|
|
1
2
|
|
|
3
|
+
import Utils_DynamoDB from "./utils/Utils_DynamoDB.js"
|
|
4
|
+
import Utils_S3 from "./utils/Utils_S3.js"
|
|
5
|
+
import Utils_Misc from "./utils/Utils_Misc.js"
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
for (const utils_list of [utils_misc, utils_dynamodb]) {
|
|
11
|
-
for (const [util_name, util] of Object.entries(utils_list)) {
|
|
12
|
-
utils[util_name] = (...args) => util(clients, ...args)
|
|
13
|
-
}
|
|
7
|
+
export default class Utils extends Utils_Misc {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
super(config)
|
|
10
|
+
this.config = config
|
|
11
|
+
this.states = states
|
|
12
|
+
this.dynamodb = new Utils_DynamoDB(config)
|
|
13
|
+
this.s3 = new Utils_S3(config)
|
|
14
14
|
}
|
|
15
|
-
return utils
|
|
16
15
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "triangle-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"description": "",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@aws-sdk/client-dynamodb": "^3.953.0"
|
|
10
|
+
"@aws-sdk/client-dynamodb": "^3.953.0",
|
|
11
|
+
"@aws-sdk/client-s3": "^3.953.0",
|
|
12
|
+
"nodemailer": "^7.0.11"
|
|
11
13
|
}
|
|
12
14
|
}
|
package/test.js
CHANGED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { DynamoDB } from "@aws-sdk/client-dynamodb"
|
|
2
|
+
|
|
3
|
+
function convert_output(dynamoobject) {
|
|
4
|
+
if (dynamoobject.S !== undefined) {
|
|
5
|
+
return dynamoobject.S
|
|
6
|
+
} else if (dynamoobject.BOOL !== undefined) {
|
|
7
|
+
return dynamoobject.BOOL
|
|
8
|
+
} else if (dynamoobject.N !== undefined) {
|
|
9
|
+
return Number(dynamoobject.N)
|
|
10
|
+
} else if (dynamoobject.L !== undefined) {
|
|
11
|
+
return dynamoobject.L.map(a => convert_output(a))
|
|
12
|
+
} else if (dynamoobject.SS !== undefined) {
|
|
13
|
+
return new Set(dynamoobject.SS)
|
|
14
|
+
} else if (dynamoobject.M !== undefined) {
|
|
15
|
+
return Object.fromEntries(Object.entries(dynamoobject.M).map(([key, value]) => [key, convert_output(value)]))
|
|
16
|
+
}
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function convert_input(input) {
|
|
21
|
+
if (typeof input === typeof "") {
|
|
22
|
+
return { S : input }
|
|
23
|
+
} else if (typeof input === typeof true) {
|
|
24
|
+
return { BOOL : input }
|
|
25
|
+
} else if (typeof input === typeof 3.2) {
|
|
26
|
+
return { N : input.toString() }
|
|
27
|
+
} else if (Array.isArray(input)) {
|
|
28
|
+
return { L : input.map(a => convert_input(a)) }
|
|
29
|
+
} else if (input instanceof Set) {
|
|
30
|
+
return { SS : Array.from(input) }
|
|
31
|
+
} else if (typeof input === typeof {}) {
|
|
32
|
+
return {
|
|
33
|
+
M : Object.fromEntries(Object.entries(input)
|
|
34
|
+
.filter(([key, value]) => value !== undefined && value !== null && key !== "")
|
|
35
|
+
.map(([key, value]) => [key, convert_input(value)]))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function paginate(request, f) {
|
|
42
|
+
const items = []
|
|
43
|
+
let last_eval_key = undefined
|
|
44
|
+
while (true) {
|
|
45
|
+
const response = last_eval_key !== undefined ? (
|
|
46
|
+
await f({...request, ExclusiveStartKey : last_eval_key })
|
|
47
|
+
) : (
|
|
48
|
+
await f(request)
|
|
49
|
+
)
|
|
50
|
+
const new_items = response.Items.map(item => convert_output({ M : item }))
|
|
51
|
+
items.push(...new_items)
|
|
52
|
+
if (response.LastEvaluatedKey === undefined) {
|
|
53
|
+
return items
|
|
54
|
+
}
|
|
55
|
+
last_eval_key = response.LastEvaluatedKey
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default class Utils_DynamoDB {
|
|
60
|
+
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.dynamodb = new DynamoDB({ region : config.region })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async scan(table, attribute_names = [], filters = {}) {
|
|
66
|
+
const request = Object.fromEntries(Object.entries({
|
|
67
|
+
TableName : table,
|
|
68
|
+
ExpressionAttributeNames : Object.fromEntries((Object.keys(filters).concat(attribute_names)).map(attribute_name => ["#" + attribute_name, attribute_name])),
|
|
69
|
+
ExpressionAttributeValues : Object.fromEntries(Object.entries(filters).map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)])),
|
|
70
|
+
FilterExpression : Object.keys(filters).map(attribute_name => "#" + attribute_name + " = :" + attribute_name).join(","),
|
|
71
|
+
ProjectionExpression : attribute_names.map(attribute_name => "#" + attribute_name).join(", ")
|
|
72
|
+
}).filter(([field, value]) => value !== undefined && Object.keys(value).length !== 0))
|
|
73
|
+
return await paginate(request, (request) => this.dynamodb.scan(request))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async get(table, key, consistent=false) {
|
|
77
|
+
const item = await this.dynamodb.getItem({
|
|
78
|
+
ConsistentRead : consistent,
|
|
79
|
+
TableName : table,
|
|
80
|
+
Key : convert_input(key).M
|
|
81
|
+
})
|
|
82
|
+
.then(response => response.Item)
|
|
83
|
+
if (item === undefined) {
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
return convert_output({ M : item })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async get_max(table, primary_key) {
|
|
90
|
+
if (Object.keys(primary_key).length !== 1) {
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
93
|
+
const request = {
|
|
94
|
+
TableName : table,
|
|
95
|
+
ExpressionAttributeNames: {
|
|
96
|
+
"#a": Object.keys(primary_key)[0]
|
|
97
|
+
},
|
|
98
|
+
ExpressionAttributeValues: {
|
|
99
|
+
":a": convert_input(Object.values(primary_key)[0])
|
|
100
|
+
},
|
|
101
|
+
KeyConditionExpression: "#a = :a",
|
|
102
|
+
Limit : 1,
|
|
103
|
+
ScanIndexForward : false
|
|
104
|
+
}
|
|
105
|
+
const items = await this.dynamodb.query(request)
|
|
106
|
+
.then(response => response.Items)
|
|
107
|
+
if (items[0] === undefined) {
|
|
108
|
+
return undefined
|
|
109
|
+
}
|
|
110
|
+
return convert_output({ M : items[0] })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async query(table, primary_key, reverse=false) {
|
|
116
|
+
if (Object.keys(primary_key).length !== 1) {
|
|
117
|
+
return undefined
|
|
118
|
+
}
|
|
119
|
+
const request = {
|
|
120
|
+
TableName : table,
|
|
121
|
+
ExpressionAttributeNames: {
|
|
122
|
+
"#a": Object.keys(primary_key)[0]
|
|
123
|
+
},
|
|
124
|
+
ExpressionAttributeValues: {
|
|
125
|
+
":a": convert_input(Object.values(primary_key)[0])
|
|
126
|
+
},
|
|
127
|
+
KeyConditionExpression: "#a = :a",
|
|
128
|
+
ScanIndexForward : !reverse
|
|
129
|
+
}
|
|
130
|
+
return await paginate(request, (request) => this.dynamodb.query(request))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async query_prefix(table, primary_key, secondary_key_prefix, reverse=false) {
|
|
134
|
+
if (Object.keys(primary_key).length !== 1 || Object.keys(secondary_key_prefix).length !== 1) {
|
|
135
|
+
return undefined
|
|
136
|
+
}
|
|
137
|
+
const request = {
|
|
138
|
+
TableName : table,
|
|
139
|
+
ExpressionAttributeNames: {
|
|
140
|
+
"#a": Object.keys(primary_key)[0],
|
|
141
|
+
"#b": Object.keys(secondary_key_prefix)[0]
|
|
142
|
+
},
|
|
143
|
+
ExpressionAttributeValues: {
|
|
144
|
+
":a": convert_input(Object.values(primary_key)[0]),
|
|
145
|
+
":b": convert_input(Object.values(secondary_key_prefix)[0])
|
|
146
|
+
},
|
|
147
|
+
KeyConditionExpression: "#a = :a AND begins_with(#b, :b)",
|
|
148
|
+
ScanIndexForward : !reverse
|
|
149
|
+
}
|
|
150
|
+
return await paginate(request, (request) => this.dynamodb.query(request))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async query_range(table, primary_key, secondary_key_range, reverse=false) {
|
|
154
|
+
if (Object.keys(primary_key).length !== 1 || Object.keys(secondary_key_range).length !== 1 || Object.values(secondary_key_range)[0].length !== 2) {
|
|
155
|
+
return undefined
|
|
156
|
+
}
|
|
157
|
+
const request = {
|
|
158
|
+
TableName : table,
|
|
159
|
+
ExpressionAttributeNames: {
|
|
160
|
+
"#a": Object.keys(primary_key)[0],
|
|
161
|
+
"#b": Object.keys(secondary_key_range)[0]
|
|
162
|
+
},
|
|
163
|
+
ExpressionAttributeValues: {
|
|
164
|
+
":a": convert_input(Object.values(primary_key)[0]),
|
|
165
|
+
":b1": convert_input(Object.values(secondary_key_range)[0][0]),
|
|
166
|
+
":b2": convert_input(Object.values(secondary_key_range)[0][1])
|
|
167
|
+
},
|
|
168
|
+
KeyConditionExpression: "#a = :a AND (#b BETWEEN :b1 AND :b2)",
|
|
169
|
+
ScanIndexForward : !reverse
|
|
170
|
+
}
|
|
171
|
+
return await paginate(request, (request) => this.dynamodb.query(request))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async set(table, key, attributes) {
|
|
175
|
+
return await this.dynamodb.updateItem({
|
|
176
|
+
TableName : table,
|
|
177
|
+
Key : convert_input(key).M,
|
|
178
|
+
UpdateExpression: "set " + Object.keys(attributes)
|
|
179
|
+
.filter(attribute_name => !Object.keys(key).includes(attribute_name))
|
|
180
|
+
.filter(attribute_name => attributes[attribute_name] !== undefined)
|
|
181
|
+
.map(attribute_name => "#" + attribute_name + " = :" + attribute_name
|
|
182
|
+
).join(", "),
|
|
183
|
+
ExpressionAttributeNames: Object.fromEntries(Object.keys(attributes)
|
|
184
|
+
.filter(attribute_name => !Object.keys(key).includes(attribute_name))
|
|
185
|
+
.filter(attribute_name => attributes[attribute_name] !== undefined)
|
|
186
|
+
.map(attribute_name => ["#" + attribute_name, attribute_name]
|
|
187
|
+
)),
|
|
188
|
+
ExpressionAttributeValues: Object.fromEntries(Object.entries(attributes)
|
|
189
|
+
.filter(([attribute_name, attribute_value]) => !Object.keys(key).includes(attribute_name))
|
|
190
|
+
.filter(([attribute_name, attribute_value]) => attribute_value !== undefined)
|
|
191
|
+
.map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)]
|
|
192
|
+
))
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async append(table, key, attributes) {
|
|
197
|
+
return await this.dynamodb.updateItem({
|
|
198
|
+
TableName : table,
|
|
199
|
+
Key : convert_input(key).M,
|
|
200
|
+
UpdateExpression: "set " + Object.keys(attributes)
|
|
201
|
+
.filter(attribute_name => attributes[attribute_name] !== undefined)
|
|
202
|
+
.map(attribute_name => "#" + attribute_name + " = list_append(#" + attribute_name + ", :" + attribute_name + ")").join(", "),
|
|
203
|
+
ExpressionAttributeNames: Object.fromEntries(Object.keys(attributes)
|
|
204
|
+
.filter(attribute_name => attributes[attribute_name] !== undefined)
|
|
205
|
+
.map(attribute_name => ["#" + attribute_name, attribute_name]
|
|
206
|
+
)),
|
|
207
|
+
ExpressionAttributeValues: Object.fromEntries(Object.entries(attributes)
|
|
208
|
+
.filter(([attribute_name, attribute_value]) => attribute_value !== undefined)
|
|
209
|
+
.map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)]
|
|
210
|
+
))
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async add(table, key, attributes) {
|
|
215
|
+
const item = await this.get(table, key, true)
|
|
216
|
+
const new_attributes = {}
|
|
217
|
+
for (const [attribute, values] of Object.entries(attributes)) {
|
|
218
|
+
if (item[attribute] === undefined) {
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
const new_values = values.filter(value => !item[attribute].includes(value))
|
|
222
|
+
if (new_values.length > 0) {
|
|
223
|
+
new_attributes[attribute] = new_values
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (Object.values(new_attributes).flat().length === 0) {
|
|
227
|
+
return undefined
|
|
228
|
+
}
|
|
229
|
+
return await this.append(table, key, attributes)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async remove(table, key, attributes) {
|
|
233
|
+
return await this.dynamodb.updateItem({
|
|
234
|
+
TableName : table,
|
|
235
|
+
Key : convert_input(key).M,
|
|
236
|
+
UpdateExpression: "remove " + attributes
|
|
237
|
+
.map(attribute_name => "#" + attribute_name).join(", "),
|
|
238
|
+
ExpressionAttributeNames: Object.fromEntries(attributes
|
|
239
|
+
.map(attribute_name => ["#" + attribute_name, attribute_name]
|
|
240
|
+
))
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async create(table, key, attributes = {}) {
|
|
245
|
+
const item = await this.get(table, key)
|
|
246
|
+
if (item !== undefined) {
|
|
247
|
+
return undefined
|
|
248
|
+
}
|
|
249
|
+
return await this.dynamodb.putItem({
|
|
250
|
+
TableName : table,
|
|
251
|
+
Item : { ...convert_input(key).M, ...convert_input(attributes).M }
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async delete(table, key) {
|
|
256
|
+
return await this.dynamodb.deleteItem({
|
|
257
|
+
TableName: table,
|
|
258
|
+
Key: Object.fromEntries(Object.keys(key).map(key_name => [key_name, convert_input(key[key_name])]))
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async duplicate_attribute(table, attribute_name, new_attribute_name) {
|
|
263
|
+
const table_key_names = await this.dynamodb.describeTable({
|
|
264
|
+
TableName : table
|
|
265
|
+
}).then(metadata => metadata.Table.KeySchema.map(key => key.AttributeName))
|
|
266
|
+
const items = await this.scan(table, table_key_names.concat([attribute_name, new_attribute_name]))
|
|
267
|
+
if (items.filter(item => item[new_attribute_name] !== undefined).length > 0) {
|
|
268
|
+
console.log("Cannot rename.", new_attribute_name, "is an existing item.")
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
for (const item of items) {
|
|
272
|
+
if (item[attribute_name] === undefined) {
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
const key = Object.fromEntries(table_key_names.map(key_name => [key_name, item[key_name]]))
|
|
276
|
+
await this.set(table, key, { [new_attribute_name] : item[attribute_name] })
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async remove_attribute(table, attribute_name) {
|
|
281
|
+
const table_key_names = await this.dynamodb.describeTable({
|
|
282
|
+
TableName : table
|
|
283
|
+
}).then(metadata => metadata.Table.KeySchema.map(key => key.AttributeName))
|
|
284
|
+
const items = await this.scan(table)
|
|
285
|
+
.then(items => items.filter(item => item[attribute_name] !== undefined))
|
|
286
|
+
for (const item of items) {
|
|
287
|
+
const key = Object.fromEntries(table_key_names.map(key_name => [key_name, item[key_name]]))
|
|
288
|
+
await this.remove(table, key, [attribute_name])
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import nodemailer from "nodemailer"
|
|
2
|
+
import election_ids from "./election_ids.js"
|
|
3
|
+
import crypto from "crypto"
|
|
4
|
+
|
|
5
|
+
export default class Utils_Misc {
|
|
6
|
+
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config
|
|
9
|
+
this.text_encoder = new TextEncoder()
|
|
10
|
+
if (config.google_email !== undefined && config.google_app_password !== undefined) {
|
|
11
|
+
this.transporter = nodemailer.createTransport({
|
|
12
|
+
service: "Gmail",
|
|
13
|
+
host: "smtp.gmail.com",
|
|
14
|
+
port: 465,
|
|
15
|
+
secure: true,
|
|
16
|
+
auth: {
|
|
17
|
+
user: config.google_email,
|
|
18
|
+
pass: config.google_app_password,
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async wait(duration) {
|
|
25
|
+
return new Promise(resolve => setTimeout(resolve, duration))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get_current_time( milliseconds = false) {
|
|
29
|
+
if (milliseconds) {
|
|
30
|
+
return (new Date()).getTime()
|
|
31
|
+
}
|
|
32
|
+
return (new Date()).toISOString()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async send_email(recipient, subject, text) {
|
|
36
|
+
for (let i = 0; i < 3; i++) {
|
|
37
|
+
try {
|
|
38
|
+
await this.transporter.sendMail({
|
|
39
|
+
from: clients.config.alerts_email,
|
|
40
|
+
to: recipient,
|
|
41
|
+
subject: subject,
|
|
42
|
+
text: text
|
|
43
|
+
})
|
|
44
|
+
return
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.log("EMAIL ERROR", error)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async admin_alert(text) {
|
|
52
|
+
console.log("ADMIN ALERT:", text)
|
|
53
|
+
await send_email(this.config.google_email, "ADMIN ALERT", text)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async safe_run( f) {
|
|
57
|
+
try {
|
|
58
|
+
await f()
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error.stack !== undefined) {
|
|
61
|
+
await admin_alert(error.stack.toString())
|
|
62
|
+
} else {
|
|
63
|
+
await admin_alert(error.toString())
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async iterate(inputs, f, concurrency = 1, print_indices = false) {
|
|
70
|
+
let index = 0
|
|
71
|
+
let iterators = []
|
|
72
|
+
for (let i = 0; i < concurrency; i++) {
|
|
73
|
+
iterators.push((async () => {
|
|
74
|
+
while (index < inputs.length) {
|
|
75
|
+
index += 1
|
|
76
|
+
if (print_indices) {
|
|
77
|
+
console.log(i + ":" + (index - 1) + "/" + inputs.length)
|
|
78
|
+
}
|
|
79
|
+
await safe_run(() => f(inputs[index - 1]))
|
|
80
|
+
}
|
|
81
|
+
})())
|
|
82
|
+
}
|
|
83
|
+
await Promise.all(iterators)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async sha256(input) {
|
|
87
|
+
const input_buffer = text_encoder.encode(input);
|
|
88
|
+
|
|
89
|
+
const hash_buffer = await crypto.subtle.digest("SHA-256", input_buffer)
|
|
90
|
+
|
|
91
|
+
const hash_array = Array.from(new Uint8Array(hash_buffer))
|
|
92
|
+
|
|
93
|
+
const hash = hash_array.map(b => b.toString(16).padStart(2, "0")).join("")
|
|
94
|
+
return hash;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get_election_id(year, office, state, district) {
|
|
98
|
+
if (year === undefined || office === undefined || state === undefined || office === "P") {
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
if (office === "H" && (district === undefined || district.length !== 2)) {
|
|
102
|
+
return undefined
|
|
103
|
+
}
|
|
104
|
+
if (year.length !== 4 || office.length !== 1 || state.length !== 2) {
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
107
|
+
const office_name = { "H" : "house", "S" : "sen", "P" : "pres" }[office]
|
|
108
|
+
const election_id = year + "-" + office_name + "-" + state.toLowerCase() + (office === "H" ? ("-" + district.toLowerCase()) : "") + "-election"
|
|
109
|
+
if (!election_ids.has(election_id)) {
|
|
110
|
+
return undefined
|
|
111
|
+
}
|
|
112
|
+
return election_id
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { S3, NoSuchKey } from "@aws-sdk/client-s3"
|
|
2
|
+
|
|
3
|
+
export default class Utils_S3 {
|
|
4
|
+
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.s3 = new S3({ region : config.region })
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async file_exists(bucket, filename) {
|
|
10
|
+
try {
|
|
11
|
+
const head = await s3.headObject({
|
|
12
|
+
Bucket : bucket,
|
|
13
|
+
Key : filename
|
|
14
|
+
})
|
|
15
|
+
return true
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async get_file(bucket, filename, json=false) {
|
|
22
|
+
try {
|
|
23
|
+
return await s3.getObject({
|
|
24
|
+
Bucket : bucket,
|
|
25
|
+
Key : filename
|
|
26
|
+
}).then(response => response.Body.transformToString("utf-8"))
|
|
27
|
+
.then(text => json ? JSON.parse(text) : text)
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof NoSuchKey) {
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
throw error
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async create_file(bucket, filename, content) {
|
|
37
|
+
const exists = await file_exists(bucket, filename)
|
|
38
|
+
if (exists) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
return await s3.putObject({
|
|
42
|
+
Bucket : bucket,
|
|
43
|
+
Key : filename,
|
|
44
|
+
Body : content
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async delete_file(bucket, filename) {
|
|
49
|
+
return await s3.deleteObject({
|
|
50
|
+
Bucket : bucket,
|
|
51
|
+
Key : filename
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
package/clients.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import nodemailer from "nodemailer"
|
|
3
|
-
import { DynamoDB } from "@aws-sdk/client-dynamodb"
|
|
4
|
-
|
|
5
|
-
export default function get_clients(config) {
|
|
6
|
-
const clients = {}
|
|
7
|
-
if (config.region !== undefined) {
|
|
8
|
-
clients.dynamodb = new DynamoDB({ region : config.region })
|
|
9
|
-
}
|
|
10
|
-
if (config.google_email !== undefined && config.google_app_password !== undefined) {
|
|
11
|
-
clients.transporter = nodemailer.createTransport({
|
|
12
|
-
service: "Gmail",
|
|
13
|
-
host: "smtp.gmail.com",
|
|
14
|
-
port: 465,
|
|
15
|
-
secure: true,
|
|
16
|
-
auth: {
|
|
17
|
-
user: config.google_email,
|
|
18
|
-
pass: config.google_app_password,
|
|
19
|
-
}
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
clients.config = config
|
|
23
|
-
return clients
|
|
24
|
-
}
|
package/misc.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import election_ids from "./utils/election_ids.js"
|
|
2
|
-
import crypto from "crypto"
|
|
3
|
-
|
|
4
|
-
export async function wait(clients, duration) {
|
|
5
|
-
return new Promise(resolve => setTimeout(resolve, duration))
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function get_current_time(clients, milliseconds = false) {
|
|
9
|
-
if (milliseconds) {
|
|
10
|
-
return (new Date()).getTime()
|
|
11
|
-
}
|
|
12
|
-
return (new Date()).toISOString()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function send_email(clients, recipient, subject, text) {
|
|
16
|
-
for (let i = 0; i < 3; i++) {
|
|
17
|
-
try {
|
|
18
|
-
await clients.transporter.sendMail({
|
|
19
|
-
from: clients.config.alerts_email,
|
|
20
|
-
to: recipient,
|
|
21
|
-
subject: subject,
|
|
22
|
-
text: text
|
|
23
|
-
})
|
|
24
|
-
return
|
|
25
|
-
} catch (error) {
|
|
26
|
-
console.log("EMAIL ERROR", error)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function admin_alert(clients, text) {
|
|
32
|
-
console.log("ADMIN ALERT:", text)
|
|
33
|
-
await send_email(clients, clients.config.google_email, "ADMIN ALERT", text)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function safe_run(clients, f) {
|
|
37
|
-
try {
|
|
38
|
-
await f()
|
|
39
|
-
} catch (error) {
|
|
40
|
-
if (error.stack !== undefined) {
|
|
41
|
-
await admin_alert(clients, error.stack.toString())
|
|
42
|
-
} else {
|
|
43
|
-
await admin_alert(clients, error.toString())
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function iterate(clients, inputs, f, concurrency = 1, print_indices = false) {
|
|
50
|
-
let index = 0
|
|
51
|
-
let iterators = []
|
|
52
|
-
for (let i = 0; i < concurrency; i++) {
|
|
53
|
-
iterators.push((async () => {
|
|
54
|
-
while (index < inputs.length) {
|
|
55
|
-
index += 1
|
|
56
|
-
if (print_indices) {
|
|
57
|
-
console.log(i + ":" + (index - 1) + "/" + inputs.length)
|
|
58
|
-
}
|
|
59
|
-
await safe_run(clients, () => f(inputs[index - 1]))
|
|
60
|
-
}
|
|
61
|
-
})())
|
|
62
|
-
}
|
|
63
|
-
await Promise.all(iterators)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const text_encoder = new TextEncoder()
|
|
67
|
-
|
|
68
|
-
export async function sha256(clients, input) {
|
|
69
|
-
const input_buffer = text_encoder.encode(input);
|
|
70
|
-
|
|
71
|
-
const hash_buffer = await crypto.subtle.digest("SHA-256", input_buffer)
|
|
72
|
-
|
|
73
|
-
const hash_array = Array.from(new Uint8Array(hash_buffer))
|
|
74
|
-
|
|
75
|
-
const hash = hash_array.map(b => b.toString(16).padStart(2, "0")).join("")
|
|
76
|
-
return hash;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function get_election_id(clients, year, office, state, district) {
|
|
80
|
-
if (year === undefined || office === undefined || state === undefined || office === "P") {
|
|
81
|
-
return undefined
|
|
82
|
-
}
|
|
83
|
-
if (office === "H" && (district === undefined || district.length !== 2)) {
|
|
84
|
-
return undefined
|
|
85
|
-
}
|
|
86
|
-
if (year.length !== 4 || office.length !== 1 || state.length !== 2) {
|
|
87
|
-
return undefined
|
|
88
|
-
}
|
|
89
|
-
const office_name = { "H" : "house", "S" : "sen", "P" : "pres" }[office]
|
|
90
|
-
const election_id = year + "-" + office_name + "-" + state.toLowerCase() + (office === "H" ? ("-" + district.toLowerCase()) : "") + "-election"
|
|
91
|
-
if (!election_ids.has(election_id)) {
|
|
92
|
-
return undefined
|
|
93
|
-
}
|
|
94
|
-
return election_id
|
|
95
|
-
}
|
package/utils/dynamodb.js
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
function convert_output(dynamoobject) {
|
|
2
|
-
if (dynamoobject.S !== undefined) {
|
|
3
|
-
return dynamoobject.S
|
|
4
|
-
} else if (dynamoobject.BOOL !== undefined) {
|
|
5
|
-
return dynamoobject.BOOL
|
|
6
|
-
} else if (dynamoobject.N !== undefined) {
|
|
7
|
-
return Number(dynamoobject.N)
|
|
8
|
-
} else if (dynamoobject.L !== undefined) {
|
|
9
|
-
return dynamoobject.L.map(a => convert_output(a))
|
|
10
|
-
} else if (dynamoobject.SS !== undefined) {
|
|
11
|
-
return new Set(dynamoobject.SS)
|
|
12
|
-
} else if (dynamoobject.M !== undefined) {
|
|
13
|
-
return Object.fromEntries(Object.entries(dynamoobject.M).map(([key, value]) => [key, convert_output(value)]))
|
|
14
|
-
}
|
|
15
|
-
return undefined
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function convert_input(input) {
|
|
19
|
-
if (typeof input === typeof "") {
|
|
20
|
-
return { S : input }
|
|
21
|
-
} else if (typeof input === typeof true) {
|
|
22
|
-
return { BOOL : input }
|
|
23
|
-
} else if (typeof input === typeof 3.2) {
|
|
24
|
-
return { N : input.toString() }
|
|
25
|
-
} else if (Array.isArray(input)) {
|
|
26
|
-
return { L : input.map(a => convert_input(a)) }
|
|
27
|
-
} else if (input instanceof Set) {
|
|
28
|
-
return { SS : Array.from(input) }
|
|
29
|
-
} else if (typeof input === typeof {}) {
|
|
30
|
-
return {
|
|
31
|
-
M : Object.fromEntries(Object.entries(input)
|
|
32
|
-
.filter(([key, value]) => value !== undefined && value !== null && key !== "")
|
|
33
|
-
.map(([key, value]) => [key, convert_input(value)]))
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return undefined
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function paginate(request, f) {
|
|
40
|
-
const items = []
|
|
41
|
-
let last_eval_key = undefined
|
|
42
|
-
while (true) {
|
|
43
|
-
const response = last_eval_key !== undefined ? (
|
|
44
|
-
await f({...request, ExclusiveStartKey : last_eval_key })
|
|
45
|
-
) : (
|
|
46
|
-
await f(request)
|
|
47
|
-
)
|
|
48
|
-
const new_items = response.Items.map(item => convert_output({ M : item }))
|
|
49
|
-
items.push(...new_items)
|
|
50
|
-
if (response.LastEvaluatedKey === undefined) {
|
|
51
|
-
return items
|
|
52
|
-
}
|
|
53
|
-
last_eval_key = response.LastEvaluatedKey
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function dynamodb_scan(clients, table, attribute_names = [], filters = {}) {
|
|
58
|
-
const request = Object.fromEntries(Object.entries({
|
|
59
|
-
TableName : table,
|
|
60
|
-
ExpressionAttributeNames : Object.fromEntries((Object.keys(filters).concat(attribute_names)).map(attribute_name => ["#" + attribute_name, attribute_name])),
|
|
61
|
-
ExpressionAttributeValues : Object.fromEntries(Object.entries(filters).map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)])),
|
|
62
|
-
FilterExpression : Object.keys(filters).map(attribute_name => "#" + attribute_name + " = :" + attribute_name).join(","),
|
|
63
|
-
ProjectionExpression : attribute_names.map(attribute_name => "#" + attribute_name).join(", ")
|
|
64
|
-
}).filter(([field, value]) => value !== undefined && Object.keys(value).length !== 0))
|
|
65
|
-
return await paginate(request, (request) => clients.dynamodb.scan(request))
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function dynamodb_get(clients, table, key, consistent=false) {
|
|
69
|
-
const item = await clients.dynamodb.getItem({
|
|
70
|
-
ConsistentRead : consistent,
|
|
71
|
-
TableName : table,
|
|
72
|
-
Key : convert_input(key).M
|
|
73
|
-
})
|
|
74
|
-
.then(response => response.Item)
|
|
75
|
-
if (item === undefined) {
|
|
76
|
-
return undefined
|
|
77
|
-
}
|
|
78
|
-
return convert_output({ M : item })
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function dynamodb_get_max(clients, table, primary_key) {
|
|
82
|
-
if (Object.keys(primary_key).length !== 1) {
|
|
83
|
-
return undefined
|
|
84
|
-
}
|
|
85
|
-
const request = {
|
|
86
|
-
TableName : table,
|
|
87
|
-
ExpressionAttributeNames: {
|
|
88
|
-
"#a": Object.keys(primary_key)[0]
|
|
89
|
-
},
|
|
90
|
-
ExpressionAttributeValues: {
|
|
91
|
-
":a": convert_input(Object.values(primary_key)[0])
|
|
92
|
-
},
|
|
93
|
-
KeyConditionExpression: "#a = :a",
|
|
94
|
-
Limit : 1,
|
|
95
|
-
ScanIndexForward : false
|
|
96
|
-
}
|
|
97
|
-
const items = await clients.dynamodb.query(request)
|
|
98
|
-
.then(response => response.Items)
|
|
99
|
-
if (items[0] === undefined) {
|
|
100
|
-
return undefined
|
|
101
|
-
}
|
|
102
|
-
return convert_output({ M : items[0] })
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
export async function dynamodb_query(clients, table, primary_key, reverse=false) {
|
|
108
|
-
if (Object.keys(primary_key).length !== 1) {
|
|
109
|
-
return undefined
|
|
110
|
-
}
|
|
111
|
-
const request = {
|
|
112
|
-
TableName : table,
|
|
113
|
-
ExpressionAttributeNames: {
|
|
114
|
-
"#a": Object.keys(primary_key)[0]
|
|
115
|
-
},
|
|
116
|
-
ExpressionAttributeValues: {
|
|
117
|
-
":a": convert_input(Object.values(primary_key)[0])
|
|
118
|
-
},
|
|
119
|
-
KeyConditionExpression: "#a = :a",
|
|
120
|
-
ScanIndexForward : !reverse
|
|
121
|
-
}
|
|
122
|
-
return await paginate(request, (request) => clients.dynamodb.query(request))
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export async function dynamodb_query_prefix(clients, table, primary_key, secondary_key_prefix, reverse=false) {
|
|
126
|
-
if (Object.keys(primary_key).length !== 1 || Object.keys(secondary_key_prefix).length !== 1) {
|
|
127
|
-
return undefined
|
|
128
|
-
}
|
|
129
|
-
const request = {
|
|
130
|
-
TableName : table,
|
|
131
|
-
ExpressionAttributeNames: {
|
|
132
|
-
"#a": Object.keys(primary_key)[0],
|
|
133
|
-
"#b": Object.keys(secondary_key_prefix)[0]
|
|
134
|
-
},
|
|
135
|
-
ExpressionAttributeValues: {
|
|
136
|
-
":a": convert_input(Object.values(primary_key)[0]),
|
|
137
|
-
":b": convert_input(Object.values(secondary_key_prefix)[0])
|
|
138
|
-
},
|
|
139
|
-
KeyConditionExpression: "#a = :a AND begins_with(#b, :b)",
|
|
140
|
-
ScanIndexForward : !reverse
|
|
141
|
-
}
|
|
142
|
-
return await paginate(request, (request) => clients.dynamodb.query(request))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export async function dynamodb_query_range(clients, table, primary_key, secondary_key_range, reverse=false) {
|
|
146
|
-
if (Object.keys(primary_key).length !== 1 || Object.keys(secondary_key_range).length !== 1 || Object.values(secondary_key_range)[0].length !== 2) {
|
|
147
|
-
return undefined
|
|
148
|
-
}
|
|
149
|
-
const request = {
|
|
150
|
-
TableName : table,
|
|
151
|
-
ExpressionAttributeNames: {
|
|
152
|
-
"#a": Object.keys(primary_key)[0],
|
|
153
|
-
"#b": Object.keys(secondary_key_range)[0]
|
|
154
|
-
},
|
|
155
|
-
ExpressionAttributeValues: {
|
|
156
|
-
":a": convert_input(Object.values(primary_key)[0]),
|
|
157
|
-
":b1": convert_input(Object.values(secondary_key_range)[0][0]),
|
|
158
|
-
":b2": convert_input(Object.values(secondary_key_range)[0][1])
|
|
159
|
-
},
|
|
160
|
-
KeyConditionExpression: "#a = :a AND (#b BETWEEN :b1 AND :b2)",
|
|
161
|
-
ScanIndexForward : !reverse
|
|
162
|
-
}
|
|
163
|
-
return await paginate(request, (request) => clients.dynamodb.query(request))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export async function dynamodb_set(clients, table, key, attributes) {
|
|
167
|
-
return await clients.dynamodb.updateItem({
|
|
168
|
-
TableName : table,
|
|
169
|
-
Key : convert_input(key).M,
|
|
170
|
-
UpdateExpression: "set " + Object.keys(attributes)
|
|
171
|
-
.filter(attribute_name => !Object.keys(key).includes(attribute_name))
|
|
172
|
-
.filter(attribute_name => attributes[attribute_name] !== undefined)
|
|
173
|
-
.map(attribute_name => "#" + attribute_name + " = :" + attribute_name
|
|
174
|
-
).join(", "),
|
|
175
|
-
ExpressionAttributeNames: Object.fromEntries(Object.keys(attributes)
|
|
176
|
-
.filter(attribute_name => !Object.keys(key).includes(attribute_name))
|
|
177
|
-
.filter(attribute_name => attributes[attribute_name] !== undefined)
|
|
178
|
-
.map(attribute_name => ["#" + attribute_name, attribute_name]
|
|
179
|
-
)),
|
|
180
|
-
ExpressionAttributeValues: Object.fromEntries(Object.entries(attributes)
|
|
181
|
-
.filter(([attribute_name, attribute_value]) => !Object.keys(key).includes(attribute_name))
|
|
182
|
-
.filter(([attribute_name, attribute_value]) => attribute_value !== undefined)
|
|
183
|
-
.map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)]
|
|
184
|
-
))
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export async function dynamodb_append(clients, table, key, attributes) {
|
|
189
|
-
return await clients.dynamodb.updateItem({
|
|
190
|
-
TableName : table,
|
|
191
|
-
Key : convert_input(key).M,
|
|
192
|
-
UpdateExpression: "set " + Object.keys(attributes)
|
|
193
|
-
.filter(attribute_name => attributes[attribute_name] !== undefined)
|
|
194
|
-
.map(attribute_name => "#" + attribute_name + " = list_append(#" + attribute_name + ", :" + attribute_name + ")").join(", "),
|
|
195
|
-
ExpressionAttributeNames: Object.fromEntries(Object.keys(attributes)
|
|
196
|
-
.filter(attribute_name => attributes[attribute_name] !== undefined)
|
|
197
|
-
.map(attribute_name => ["#" + attribute_name, attribute_name]
|
|
198
|
-
)),
|
|
199
|
-
ExpressionAttributeValues: Object.fromEntries(Object.entries(attributes)
|
|
200
|
-
.filter(([attribute_name, attribute_value]) => attribute_value !== undefined)
|
|
201
|
-
.map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)]
|
|
202
|
-
))
|
|
203
|
-
})
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export async function dynamodb_add(clients, table, key, attributes) {
|
|
207
|
-
const item = await get(table, key, true)
|
|
208
|
-
const new_attributes = {}
|
|
209
|
-
for (const [attribute, values] of Object.entries(attributes)) {
|
|
210
|
-
if (item[attribute] === undefined) {
|
|
211
|
-
continue
|
|
212
|
-
}
|
|
213
|
-
const new_values = values.filter(value => !item[attribute].includes(value))
|
|
214
|
-
if (new_values.length > 0) {
|
|
215
|
-
new_attributes[attribute] = new_values
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (Object.values(new_attributes).flat().length === 0) {
|
|
219
|
-
return undefined
|
|
220
|
-
}
|
|
221
|
-
return await append(table, key, attributes)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export async function dynamodb_remove(clients, table, key, attributes) {
|
|
225
|
-
return await clients.dynamodb.updateItem({
|
|
226
|
-
TableName : table,
|
|
227
|
-
Key : convert_input(key).M,
|
|
228
|
-
UpdateExpression: "remove " + attributes
|
|
229
|
-
.map(attribute_name => "#" + attribute_name).join(", "),
|
|
230
|
-
ExpressionAttributeNames: Object.fromEntries(attributes
|
|
231
|
-
.map(attribute_name => ["#" + attribute_name, attribute_name]
|
|
232
|
-
))
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export async function dynamodb_create(clients, table, key, attributes = {}) {
|
|
237
|
-
const item = await get(table, key)
|
|
238
|
-
if (item !== undefined) {
|
|
239
|
-
return undefined
|
|
240
|
-
}
|
|
241
|
-
return await clients.dynamodb.putItem({
|
|
242
|
-
TableName : table,
|
|
243
|
-
Item : { ...convert_input(key).M, ...convert_input(attributes).M }
|
|
244
|
-
})
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export async function dynamodb_delete(clients, table, key) {
|
|
248
|
-
return await clients.dynamodb.deleteItem({
|
|
249
|
-
TableName: table,
|
|
250
|
-
Key: Object.fromEntries(Object.keys(key).map(key_name => [key_name, convert_input(key[key_name])]))
|
|
251
|
-
})
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export async function dynamodb_duplicate_attribute(clients, table, attribute_name, new_attribute_name) {
|
|
255
|
-
const table_key_names = await clients.dynamodb.describeTable({
|
|
256
|
-
TableName : table
|
|
257
|
-
}).then(metadata => metadata.Table.KeySchema.map(key => key.AttributeName))
|
|
258
|
-
const items = await scan(table, table_key_names.concat([attribute_name, new_attribute_name]))
|
|
259
|
-
if (items.filter(item => item[new_attribute_name] !== undefined).length > 0) {
|
|
260
|
-
console.log("Cannot rename.", new_attribute_name, "is an existing item.")
|
|
261
|
-
return
|
|
262
|
-
}
|
|
263
|
-
for (const item of items) {
|
|
264
|
-
if (item[attribute_name] === undefined) {
|
|
265
|
-
continue
|
|
266
|
-
}
|
|
267
|
-
const key = Object.fromEntries(table_key_names.map(key_name => [key_name, item[key_name]]))
|
|
268
|
-
await set(table, key, { [new_attribute_name] : item[attribute_name] })
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export async function dynamodb_remove_attribute(clients, table, attribute_name) {
|
|
273
|
-
const table_key_names = await clients.dynamodb.describeTable({
|
|
274
|
-
TableName : table
|
|
275
|
-
}).then(metadata => metadata.Table.KeySchema.map(key => key.AttributeName))
|
|
276
|
-
const items = await scan(table)
|
|
277
|
-
.then(items => items.filter(item => item[attribute_name] !== undefined))
|
|
278
|
-
for (const item of items) {
|
|
279
|
-
const key = Object.fromEntries(table_key_names.map(key_name => [key_name, item[key_name]]))
|
|
280
|
-
await remove(table, key, [attribute_name])
|
|
281
|
-
}
|
|
282
|
-
}
|