diff options
| author | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-07-02 11:55:17 -0700 |
|---|---|---|
| committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-07-02 11:55:17 -0700 |
| commit | 6bf4b90c90f15f4ab60833bddf5b5756d1a6b1f6 (patch) | |
| tree | ed97e39ec77c5231ffd2c394493e68d00ddac5a4 /Homework/cs5260/consumer/dao | |
| download | misc-undergrad-main.tar.gz misc-undergrad-main.zip | |
Diffstat (limited to 'Homework/cs5260/consumer/dao')
| -rw-r--r-- | Homework/cs5260/consumer/dao/DynamoWidgetDAO.ts | 100 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/dao/S3WidgetDao.ts | 93 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/dao/WidgetDAO.ts | 7 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/dao/index.ts | 3 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/dao/keys/S3WidgetKeyGenerator.ts | 43 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/dao/keys/index.ts | 1 |
6 files changed, 247 insertions, 0 deletions
diff --git a/Homework/cs5260/consumer/dao/DynamoWidgetDAO.ts b/Homework/cs5260/consumer/dao/DynamoWidgetDAO.ts new file mode 100644 index 0000000..a90788f --- /dev/null +++ b/Homework/cs5260/consumer/dao/DynamoWidgetDAO.ts @@ -0,0 +1,100 @@ +import { Widget, WidgetT } from "@/shared/schema"; +import { WidgetDAO } from "."; +import { TracingLogger } from "@/consumer/log"; +import { + AttributeValue, + DeleteItemCommand, + DynamoDBClient, + GetItemCommand, + PutItemCommand, +} from "@aws-sdk/client-dynamodb"; + +export class DynamoWidgetDAO implements WidgetDAO { + private table: string; + private ddbClient: DynamoDBClient; + private logger: TracingLogger; + + constructor(table: string, ddbClient: DynamoDBClient, logger: TracingLogger) { + this.table = table; + this.ddbClient = ddbClient; + this.logger = logger; + } + + public async retrieve(widget: WidgetT) { + const getReq = new GetItemCommand({ + TableName: this.table, + Key: { + id: { + S: widget.id, + }, + }, + }); + + const response = await this.ddbClient.send(getReq); + if (response.Item) { + this.logger.info(`sucessfully retrieved Widget=(${widget.id})`); + return this.deflate(response.Item); + } + } + + public async save(widget: WidgetT) { + const putReq = new PutItemCommand({ + TableName: this.table, + Item: this.inflate(widget), + }); + + await this.ddbClient.send(putReq); + + this.logger.info(`sucessfully saved Widget=(${widget.id})`); + return widget; + } + + public async delete(widget: WidgetT) { + const deleteReq = new DeleteItemCommand({ + TableName: this.table, + Key: { + id: { S: widget.id }, + }, + }); + + await this.ddbClient.send(deleteReq); + + this.logger.info(`sucessfully deleted Widget=(${widget.id})`); + return widget; + } + + private inflate(widget: WidgetT): Record<string, AttributeValue> { + const additionalAttributes = + widget.otherAttributes?.reduce((acc, { name, value }) => { + acc[name] = { S: value }; + return acc; + }, {} as Record<string, { S: string }>) ?? {}; + if (widget.label) additionalAttributes["label"] = { S: widget.label }; + if (widget.description) + additionalAttributes["description"] = { S: widget.description }; + + return { + id: { S: widget.id }, + owner: { S: widget.owner }, + ...additionalAttributes, + }; + } + + private deflate(item: Record<string, AttributeValue>): WidgetT { + const respWidget = Object.keys(item).reduce((widge, key: string) => { + const attributeValue = item![key]; + if (["id", "owner", "label", "description"].includes(key)) { + widge[key] = attributeValue.S; + return widge; + } + + const attribute = { name: key, value: attributeValue.S! }; + widge["otherAttributes"] = widge["otherAttributes"]?.concat([ + attribute, + ]) ?? [attribute]; + + return widge; + }, {} as Record<string, any>); + return Widget.parse(respWidget); + } +} diff --git a/Homework/cs5260/consumer/dao/S3WidgetDao.ts b/Homework/cs5260/consumer/dao/S3WidgetDao.ts new file mode 100644 index 0000000..d5aee72 --- /dev/null +++ b/Homework/cs5260/consumer/dao/S3WidgetDao.ts @@ -0,0 +1,93 @@ +import { WidgetT, Widget } from "@/shared/schema"; +import { WidgetDAO } from "."; +import { + DeleteObjectCommand, + GetObjectCommand, + NoSuchKey, + PutObjectCommand, + S3Client, +} from "@aws-sdk/client-s3"; +import { S3WidgetKeyGenerator } from "./keys"; +import { TracingLogger } from "@/consumer/log"; + +export class S3WidgetDAO implements WidgetDAO { + private bucket: string; + private s3Client: S3Client; + private widgetKeyGenerator: S3WidgetKeyGenerator; + private logger: TracingLogger; + + constructor( + bucket: string, + s3Client: S3Client, + widgetKeyGenerator: S3WidgetKeyGenerator, + logger: TracingLogger + ) { + this.bucket = bucket; + this.s3Client = s3Client; + this.widgetKeyGenerator = widgetKeyGenerator; + this.logger = logger; + } + + public async retrieve(widget: WidgetT) { + const key = this.widgetKeyGenerator.fromWidget(widget); + + this.logger.info( + `getting Widget=(${widget.id}) in Bucket=(${this.bucket})` + ); + + const retrievalRequest = new GetObjectCommand({ + Bucket: this.bucket, + Key: key, + }); + + try { + const objectResp = await this.s3Client.send(retrievalRequest); + const textRepr = await objectResp.Body?.transformToString(); + + if (textRepr) { + this.logger.info(`retrieved WidgetObject=(${widget.id}) from S3`); + return Widget.parse(JSON.parse(textRepr)); + } + } catch (e: unknown) { + if (e instanceof NoSuchKey) { + return; + } + throw e; + } + } + + public async save(widget: WidgetT) { + const key = this.widgetKeyGenerator.fromWidget(widget); + + this.logger.info(`saving Widget=(${widget.id}) in Bucket=(${this.bucket})`); + + const putReq = new PutObjectCommand({ + Bucket: this.bucket, + Key: key, + Body: JSON.stringify(widget, null, 2), + }); + + await this.s3Client.send(putReq); + + this.logger.info(`successfully saved Widget=(${widget.id}) in S3`); + return widget; + } + + public async delete(widget: WidgetT) { + const key = this.widgetKeyGenerator.fromWidget(widget); + + this.logger.info( + `deleting Widget=(${widget.id}) in Bucket=(${this.bucket})` + ); + + const deleteReq = new DeleteObjectCommand({ + Key: key, + Bucket: this.bucket, + }); + + await this.s3Client.send(deleteReq); + + this.logger.info(`successfully deleted Widget=(${widget.id}) in S3`); + return widget; + } +} diff --git a/Homework/cs5260/consumer/dao/WidgetDAO.ts b/Homework/cs5260/consumer/dao/WidgetDAO.ts new file mode 100644 index 0000000..2b7f3d6 --- /dev/null +++ b/Homework/cs5260/consumer/dao/WidgetDAO.ts @@ -0,0 +1,7 @@ +import { WidgetT } from "@/shared/schema"; + +export interface WidgetDAO { + retrieve(widget: WidgetT): Promise<WidgetT | undefined>; + save(widget: WidgetT): Promise<WidgetT>; + delete(widget: WidgetT): Promise<WidgetT>; +} diff --git a/Homework/cs5260/consumer/dao/index.ts b/Homework/cs5260/consumer/dao/index.ts new file mode 100644 index 0000000..82e158c --- /dev/null +++ b/Homework/cs5260/consumer/dao/index.ts @@ -0,0 +1,3 @@ +export * from "./WidgetDAO"; +export * from "./S3WidgetDao"; +export * from "./DynamoWidgetDAO"; diff --git a/Homework/cs5260/consumer/dao/keys/S3WidgetKeyGenerator.ts b/Homework/cs5260/consumer/dao/keys/S3WidgetKeyGenerator.ts new file mode 100644 index 0000000..2e4c254 --- /dev/null +++ b/Homework/cs5260/consumer/dao/keys/S3WidgetKeyGenerator.ts @@ -0,0 +1,43 @@ +import { WidgetT } from "@/shared/schema"; + +export class S3WidgetKeyGenerator { + private prefix: string = ""; + private owner: string = ""; + private widgetId: string = ""; + + public withPrefix(prefix: string): S3WidgetKeyGenerator { + this.prefix = prefix; + return this; + } + + public withOwner(owner: string): S3WidgetKeyGenerator { + this.owner = owner; + return this; + } + + public withWidgetId(widgetId: string): S3WidgetKeyGenerator { + this.widgetId = widgetId; + return this; + } + + public build(): string { + const parts = [ + this.prefix, + this.owner.replaceAll(" ", "-").toLowerCase(), + this.widgetId, + ]; + if (parts.some((part) => !part || !part.match(/^[a-zA-Z0-9!\-_.*'()]+$/))) { + throw new Error("Invalid Key Specification"); + } + return parts.join("/"); + } + + public fromWidget(widget: WidgetT) { + const keyBuilder = new S3WidgetKeyGenerator(); + return keyBuilder + .withPrefix(this.prefix) + .withOwner(widget.owner) + .withWidgetId(widget.id) + .build(); + } +} diff --git a/Homework/cs5260/consumer/dao/keys/index.ts b/Homework/cs5260/consumer/dao/keys/index.ts new file mode 100644 index 0000000..c7c8467 --- /dev/null +++ b/Homework/cs5260/consumer/dao/keys/index.ts @@ -0,0 +1 @@ +export * from "./S3WidgetKeyGenerator"; |
