Skip to content

Commit

Permalink
Refactor scheduled circulars Lambda
Browse files Browse the repository at this point in the history
I'm preparing to add yet a third action to generate sitemap files.
To keep everything nice and neat, here are a few refactorings:

- Collect all of the actions into the actions/ directory.
- Factor out repeated S3 bucket config details into a common file.
- Collect the mapCirculars function and the CircularsAction type
  into one file to keep the Lambda entry point itself clean as
  we add more imports for the actions.
- Rename the mapCirculars function to forAllCirculars, because its
  behavior more closely resembles the .forEach() array method than
  the .map() method.
  • Loading branch information
lpsinger committed Sep 11, 2023
1 parent a957a9a commit 9322a6b
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 56 deletions.
2 changes: 1 addition & 1 deletion app/routes/circulars.archive[.]json[.]tar[.]gz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { redirect } from '@remix-run/node'

import { publicStaticShortTermCacheControlHeaders } from '~/lib/headers.server'
import { getArchiveURL } from '~/scheduled/circulars/uploadTar'
import { getArchiveURL } from '~/scheduled/circulars/actions/tar'

export function loader() {
return redirect(getArchiveURL('json'), {
Expand Down
2 changes: 1 addition & 1 deletion app/routes/circulars.archive[.]txt[.]tar[.]gz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { redirect } from '@remix-run/node'

import { publicStaticShortTermCacheControlHeaders } from '~/lib/headers.server'
import { getArchiveURL } from '~/scheduled/circulars/uploadTar'
import { getArchiveURL } from '~/scheduled/circulars/actions/tar'

export function loader() {
return redirect(getArchiveURL('txt'), {
Expand Down
41 changes: 41 additions & 0 deletions app/scheduled/circulars/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*!
* Copyright © 2023 United States Government as represented by the
* Administrator of the National Aeronautics and Space Administration.
* All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { tables } from '@architect/functions'
import type { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'
import { paginateScan } from '@aws-sdk/lib-dynamodb'

import type { Circular } from '~/routes/circulars/circulars.lib'

export interface CircularAction<T = any> {
initialize: () => T | Promise<T>
action: (circulars: Circular[], context: T) => void | Promise<void>
finalize: (context: T) => void | Promise<void>
}

export async function forAllCirculars(...actions: CircularAction[]) {
const contexts = await Promise.all(
actions.map((action) => action.initialize())
)
for await (const circulars of getAllRecords()) {
await Promise.all(
actions.map(({ action }, i) => action(circulars, contexts[i]))
)
}
await Promise.all(actions.map(({ finalize }, i) => finalize(contexts[i])))
}

async function* getAllRecords() {
const db = await tables()
const client = db._doc as unknown as DynamoDBDocument
const TableName = db.name('circulars')
const pages = paginateScan({ client }, { TableName })

for await (const page of pages) {
yield page.Items as Circular[]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { PutObjectCommand } from '@aws-sdk/client-s3'

import type { CircularAction } from './circularAction'
import type { CircularAction } from '../actions'
import { keyPrefix, s3 } from '../storage'
import { staticBucket as Bucket } from '~/lib/env.server'

const s3 = new S3Client({})
const Key = 'generated/circulars/stats.json'
const Key = `${keyPrefix}/stats.json`

export const statsAction: CircularAction<Record<string, number>> = {
initialize() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,26 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
import { S3Client } from '@aws-sdk/client-s3'
import { Upload } from '@aws-sdk/lib-storage'
import { basename } from 'node:path'
import { PassThrough } from 'node:stream'
import { createGzip } from 'node:zlib'
import type { Pack } from 'tar-stream'
import { pack as tarPack } from 'tar-stream'

import type { CircularAction } from './circularAction'
import type { CircularAction } from '../actions'
import { keyPrefix, s3 } from '../storage'
import { staticBucket as Bucket, region } from '~/lib/env.server'
import type { Circular } from '~/routes/circulars/circulars.lib'
import {
formatCircularJson,
formatCircularText,
} from '~/routes/circulars/circulars.lib'

const s3 = new S3Client({})
const archiveSuffix = '.tar.gz'

function getBucketKey(suffix: string) {
return `generated/circulars/archive.${suffix}${archiveSuffix}`
return `${keyPrefix}/archive.${suffix}${archiveSuffix}`
}

function getBucketUrl(region: string, bucket: string, key: string) {
Expand Down
14 changes: 0 additions & 14 deletions app/scheduled/circulars/circularAction.ts

This file was deleted.

36 changes: 4 additions & 32 deletions app/scheduled/circulars/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,12 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
import { tables } from '@architect/functions'
import type { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'
import { paginateScan } from '@aws-sdk/lib-dynamodb'

import type { CircularAction } from './circularAction'
import { statsAction } from './stats'
import { jsonUploadAction, txtUploadAction } from './uploadTar'
import { type Circular } from '~/routes/circulars/circulars.lib'

async function mapCirculars(...actions: CircularAction[]) {
const contexts = await Promise.all(
actions.map((action) => action.initialize())
)
for await (const circulars of getAllRecords()) {
await Promise.all(
actions.map(({ action }, i) => action(circulars, contexts[i]))
)
}
await Promise.all(actions.map(({ finalize }, i) => finalize(contexts[i])))
}

async function* getAllRecords() {
const db = await tables()
const client = db._doc as unknown as DynamoDBDocument
const TableName = db.name('circulars')
const pages = paginateScan({ client }, { TableName })

for await (const page of pages) {
yield page.Items as Circular[]
}
}
import { forAllCirculars } from './actions'
import { statsAction } from './actions/stats'
import { jsonUploadAction, txtUploadAction } from './actions/tar'

// FIXME: must use module.exports here for OpenTelemetry shim to work correctly.
// See https://dev.to/heymarkkop/how-to-solve-cannot-redefine-property-handler-on-aws-lambda-3j67
module.exports.handler = async () => {
await mapCirculars(jsonUploadAction, txtUploadAction, statsAction)
await forAllCirculars(jsonUploadAction, txtUploadAction, statsAction)
}
11 changes: 11 additions & 0 deletions app/scheduled/circulars/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*!
* Copyright © 2023 United States Government as represented by the
* Administrator of the National Aeronautics and Space Administration.
* All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { S3Client } from '@aws-sdk/client-s3'

export const s3 = new S3Client({})
export const keyPrefix = 'generated/circulars'

0 comments on commit 9322a6b

Please sign in to comment.