triangle-utils 1.2.12 → 1.3.1

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.
@@ -1,316 +0,0 @@
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 compile_pages(request, f, compile = true) {
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 || !compile) {
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, options = {}) {
66
- const filters = options.filters !== undefined ? options.filters : {}
67
- const undefined_attribute_names = options.undefined_attribute_names !== undefined ? options.undefined_attribute_names : []
68
- const defined_attribute_names = options.defined_attribute_names !== undefined ? options.defined_attribute_names : []
69
- const concurrency = options.concurrency !== undefined ? options.concurrency : 1
70
- const attribute_names = options.attribute_names !== undefined ? options.attribute_names : []
71
- const iterators = []
72
- for (let i = 0; i < concurrency; i++) {
73
- const request = Object.fromEntries(Object.entries({
74
- TableName : table,
75
- ExpressionAttributeNames : Object.fromEntries(
76
- [...Object.keys(filters), ...attribute_names, ...undefined_attribute_names, ...defined_attribute_names]
77
- .map(attribute_name => ["#" + attribute_name, attribute_name])
78
- ),
79
- ExpressionAttributeValues : Object.fromEntries(Object.entries(filters).map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)])),
80
- FilterExpression : [
81
- ...Object.keys(filters).map(attribute_name => "#" + attribute_name + " = :" + attribute_name),
82
- ...undefined_attribute_names.map(attribute_name => "attribute_not_exists(#" + attribute_name + ")"),
83
- ...defined_attribute_names.map(attribute_name => "attribute_exists(#" + attribute_name + ")")
84
- ].join(" AND "),
85
- ProjectionExpression : attribute_names.map(attribute_name => "#" + attribute_name).join(", "),
86
- Segment : i,
87
- TotalSegments : concurrency
88
- }).filter(([field, value]) => value !== undefined && (typeof value === typeof 3 || Object.keys(value).length !== 0)))
89
- iterators.push(compile_pages(request, (request) => this.dynamodb.scan(request)))
90
- }
91
- const segments = await Promise.all(iterators)
92
- return segments.flat()
93
- }
94
-
95
- async get(table, key, consistent=false) {
96
- const item = await this.dynamodb.getItem({
97
- ConsistentRead : consistent,
98
- TableName : table,
99
- Key : convert_input(key).M
100
- })
101
- .then(response => response.Item)
102
- if (item === undefined) {
103
- return undefined
104
- }
105
- return convert_output({ M : item })
106
- }
107
-
108
- async get_max(table, primary_key) {
109
- if (Object.keys(primary_key).length !== 1) {
110
- return undefined
111
- }
112
- const request = {
113
- TableName : table,
114
- ExpressionAttributeNames: {
115
- "#a": Object.keys(primary_key)[0]
116
- },
117
- ExpressionAttributeValues: {
118
- ":a": convert_input(Object.values(primary_key)[0])
119
- },
120
- KeyConditionExpression: "#a = :a",
121
- Limit : 1,
122
- ScanIndexForward : false
123
- }
124
- const items = await this.dynamodb.query(request)
125
- .then(response => response.Items)
126
- if (items[0] === undefined) {
127
- return undefined
128
- }
129
- return convert_output({ M : items[0] })
130
- }
131
-
132
-
133
-
134
- async query(table, primary_key, options = {}) {
135
- const reverse = options.reverse !== undefined ? options.reverse : false
136
- const compile = options.reverse !== undefined ? options.compile : true
137
- if (Object.keys(primary_key).length !== 1) {
138
- return undefined
139
- }
140
- const request = {
141
- TableName : table,
142
- ExpressionAttributeNames: {
143
- "#a": Object.keys(primary_key)[0]
144
- },
145
- ExpressionAttributeValues: {
146
- ":a": convert_input(Object.values(primary_key)[0])
147
- },
148
- KeyConditionExpression: "#a = :a",
149
- ScanIndexForward : !reverse
150
- }
151
- return await compile_pages(request, (request) => this.dynamodb.query(request), compile)
152
- }
153
-
154
- async query_prefix(table, primary_key, secondary_key_prefix, options = {}) {
155
- const reverse = options.reverse !== undefined ? options.reverse : false
156
- const compile = options.reverse !== undefined ? options.compile : true
157
- if (Object.keys(primary_key).length !== 1 || Object.keys(secondary_key_prefix).length !== 1) {
158
- return undefined
159
- }
160
- const request = {
161
- TableName : table,
162
- ExpressionAttributeNames: {
163
- "#a": Object.keys(primary_key)[0],
164
- "#b": Object.keys(secondary_key_prefix)[0]
165
- },
166
- ExpressionAttributeValues: {
167
- ":a": convert_input(Object.values(primary_key)[0]),
168
- ":b": convert_input(Object.values(secondary_key_prefix)[0])
169
- },
170
- KeyConditionExpression: "#a = :a AND begins_with(#b, :b)",
171
- ScanIndexForward : !reverse
172
- }
173
- return await compile_pages(request, (request) => this.dynamodb.query(request), compile)
174
- }
175
-
176
- async query_range(table, primary_key, secondary_key_range, options = {}) {
177
- const reverse = options.reverse !== undefined ? options.reverse : false
178
- const compile = options.reverse !== undefined ? options.compile : true
179
- if (Object.keys(primary_key).length !== 1 || Object.keys(secondary_key_range).length !== 1 || Object.values(secondary_key_range)[0].length !== 2) {
180
- return undefined
181
- }
182
- const request = {
183
- TableName : table,
184
- ExpressionAttributeNames: {
185
- "#a": Object.keys(primary_key)[0],
186
- "#b": Object.keys(secondary_key_range)[0]
187
- },
188
- ExpressionAttributeValues: {
189
- ":a": convert_input(Object.values(primary_key)[0]),
190
- ":b1": convert_input(Object.values(secondary_key_range)[0][0]),
191
- ":b2": convert_input(Object.values(secondary_key_range)[0][1])
192
- },
193
- KeyConditionExpression: "#a = :a AND (#b BETWEEN :b1 AND :b2)",
194
- ScanIndexForward : !reverse
195
- }
196
- return await compile_pages(request, (request) => this.dynamodb.query(request), compile)
197
- }
198
-
199
- async set(table, key, attributes) {
200
- return await this.dynamodb.updateItem({
201
- TableName : table,
202
- Key : convert_input(key).M,
203
- UpdateExpression: "set " + Object.keys(attributes)
204
- .filter(attribute_name => !Object.keys(key).includes(attribute_name))
205
- .filter(attribute_name => attributes[attribute_name] !== undefined)
206
- .map(attribute_name => "#" + attribute_name + " = :" + attribute_name
207
- ).join(", "),
208
- ExpressionAttributeNames: Object.fromEntries(Object.keys(attributes)
209
- .filter(attribute_name => !Object.keys(key).includes(attribute_name))
210
- .filter(attribute_name => attributes[attribute_name] !== undefined)
211
- .map(attribute_name => ["#" + attribute_name, attribute_name]
212
- )),
213
- ExpressionAttributeValues: Object.fromEntries(Object.entries(attributes)
214
- .filter(([attribute_name, attribute_value]) => !Object.keys(key).includes(attribute_name))
215
- .filter(([attribute_name, attribute_value]) => attribute_value !== undefined)
216
- .map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)]
217
- ))
218
- })
219
- }
220
-
221
- async append(table, key, attributes) {
222
- return await this.dynamodb.updateItem({
223
- TableName : table,
224
- Key : convert_input(key).M,
225
- UpdateExpression: "set " + Object.keys(attributes)
226
- .filter(attribute_name => attributes[attribute_name] !== undefined)
227
- .map(attribute_name => "#" + attribute_name + " = list_append(#" + attribute_name + ", :" + attribute_name + ")").join(", "),
228
- ExpressionAttributeNames: Object.fromEntries(Object.keys(attributes)
229
- .filter(attribute_name => attributes[attribute_name] !== undefined)
230
- .map(attribute_name => ["#" + attribute_name, attribute_name]
231
- )),
232
- ExpressionAttributeValues: Object.fromEntries(Object.entries(attributes)
233
- .filter(([attribute_name, attribute_value]) => attribute_value !== undefined)
234
- .map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)]
235
- ))
236
- })
237
- }
238
-
239
- async add(table, key, attributes) {
240
- const item = await this.get(table, key, true)
241
- const new_attributes = {}
242
- for (const [attribute, values] of Object.entries(attributes)) {
243
- if (item[attribute] === undefined) {
244
- continue
245
- }
246
- const new_values = values.filter(value => !item[attribute].includes(value))
247
- if (new_values.length > 0) {
248
- new_attributes[attribute] = new_values
249
- }
250
- }
251
- if (Object.values(new_attributes).flat().length === 0) {
252
- return undefined
253
- }
254
- return await this.append(table, key, attributes)
255
- }
256
-
257
- async remove(table, key, attributes) {
258
- return await this.dynamodb.updateItem({
259
- TableName : table,
260
- Key : convert_input(key).M,
261
- UpdateExpression: "remove " + attributes
262
- .map(attribute_name => "#" + attribute_name).join(", "),
263
- ExpressionAttributeNames: Object.fromEntries(attributes
264
- .map(attribute_name => ["#" + attribute_name, attribute_name]
265
- ))
266
- })
267
- }
268
-
269
- async create(table, key, attributes = {}) {
270
- const item = await this.get(table, key, true)
271
- if (item !== undefined) {
272
- return undefined
273
- }
274
- return await this.dynamodb.putItem({
275
- TableName : table,
276
- Item : { ...convert_input(key).M, ...convert_input(attributes).M }
277
- })
278
- }
279
-
280
- async delete(table, key) {
281
- return await this.dynamodb.deleteItem({
282
- TableName: table,
283
- Key: Object.fromEntries(Object.keys(key).map(key_name => [key_name, convert_input(key[key_name])]))
284
- })
285
- }
286
-
287
- async duplicate_attribute(table, attribute_name, new_attribute_name) {
288
- const table_key_names = await this.dynamodb.describeTable({
289
- TableName : table
290
- }).then(metadata => metadata.Table.KeySchema.map(key => key.AttributeName))
291
- const items = await this.scan(table, table_key_names.concat([attribute_name, new_attribute_name]))
292
- if (items.filter(item => item[new_attribute_name] !== undefined).length > 0) {
293
- console.log("Cannot rename.", new_attribute_name, "is an existing item.")
294
- return
295
- }
296
- for (const item of items) {
297
- if (item[attribute_name] === undefined) {
298
- continue
299
- }
300
- const key = Object.fromEntries(table_key_names.map(key_name => [key_name, item[key_name]]))
301
- await this.set(table, key, { [new_attribute_name] : item[attribute_name] })
302
- }
303
- }
304
-
305
- async remove_attribute(table, attribute_name) {
306
- const table_key_names = await this.dynamodb.describeTable({
307
- TableName : table
308
- }).then(metadata => metadata.Table.KeySchema.map(key => key.AttributeName))
309
- const items = await this.scan(table)
310
- .then(items => items.filter(item => item[attribute_name] !== undefined))
311
- for (const item of items) {
312
- const key = Object.fromEntries(table_key_names.map(key_name => [key_name, item[key_name]]))
313
- await this.remove(table, key, [attribute_name])
314
- }
315
- }
316
- }
@@ -1,161 +0,0 @@
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: this.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 this.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 this.admin_alert(error.stack.toString())
62
- } else {
63
- await this.admin_alert(error.toString())
64
- }
65
- }
66
- }
67
-
68
- async iterate(inputs, f, concurrency = 1, print_indices = false) {
69
- let index = 0
70
- let iterators = []
71
- for (let i = 0; i < concurrency; i++) {
72
- iterators.push((async () => {
73
- while (index < inputs.length) {
74
- index += 1
75
- if (print_indices) {
76
- console.log(i + ":" + (index - 1) + "/" + inputs.length)
77
- }
78
- await this.safe_run(() => f(inputs[index - 1]))
79
- }
80
- })())
81
- }
82
- await Promise.all(iterators)
83
- }
84
-
85
- async sha256(input) {
86
- const input_buffer = this.text_encoder.encode(input);
87
-
88
- const hash_buffer = await crypto.subtle.digest("SHA-256", input_buffer)
89
-
90
- const hash_array = Array.from(new Uint8Array(hash_buffer))
91
-
92
- const hash = hash_array.map(b => b.toString(16).padStart(2, "0")).join("")
93
- return hash
94
- }
95
-
96
- encrypt(text) {
97
- const iv = crypto.randomBytes(16)
98
- const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(this.config.secret_key, "hex"), iv)
99
- let ciphertext = cipher.update(text, "utf8", "hex")
100
- ciphertext += cipher.final("hex")
101
- return {
102
- iv: iv.toString("hex"),
103
- ciphertext: ciphertext
104
- }
105
- }
106
-
107
- decrypt(encryption) {
108
- const iv = encryption.iv
109
- const ciphertext = encryption.ciphertext
110
- const decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(this.config.secret_key, "hex"), Buffer.from(iv, "hex"))
111
- let text = decipher.update(ciphertext, "hex", "utf8")
112
- text += decipher.final("utf8")
113
- return text
114
- }
115
-
116
- get_secret_hash(username) {
117
- const secret_hash = crypto.createHmac("sha256", this.config.cognito_client_secret)
118
- .update(username + this.config.cognito_client_id)
119
- .digest("base64")
120
- return secret_hash
121
- }
122
-
123
- get_election_id(year, office, state, district) {
124
- if (year === undefined || office === undefined || state === undefined || office === "P") {
125
- return undefined
126
- }
127
- if (office === "H" && (district === undefined || district.length !== 2)) {
128
- return undefined
129
- }
130
- if (year.length !== 4 || office.length !== 1 || state.length !== 2) {
131
- return undefined
132
- }
133
- const office_name = { "H" : "house", "S" : "sen", "P" : "pres" }[office]
134
- const election_id = year + "-" + office_name + "-" + state.toLowerCase() + (office === "H" ? ("-" + district.toLowerCase()) : "") + "-election"
135
- if (!election_ids.has(election_id)) {
136
- return undefined
137
- }
138
- return election_id
139
- }
140
-
141
- get_chunk_indices(text_length, max_length = 2500, overlap = 50) {
142
- const num_chunks = Math.ceil((text_length + overlap) / max_length)
143
- const chunk_length = Math.ceil((text_length - overlap) / num_chunks + overlap)
144
- const chunk_indices = []
145
- for (let i = 0; i < num_chunks; i++) {
146
- chunk_indices.push([
147
- (chunk_length - overlap) * i,
148
- (chunk_length - overlap) * i + chunk_length
149
- ])
150
- }
151
- return chunk_indices
152
- }
153
-
154
- chunkify(text, max_length = 2500, overlap = 50) {
155
- const chunk_indices = this.get_chunk_indices(text.length, max_length, overlap)
156
- return chunk_indices.map(chunk_index => ({
157
- chunk_index : chunk_index,
158
- chunk_text : text.substring(...chunk_index)
159
- }))
160
- }
161
- }