summaryrefslogtreecommitdiff
path: root/Homework/cs5260/consumer/dao
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2026-07-02 11:55:17 -0700
committerElizabeth Alexander Hunt <me@liz.coffee>2026-07-02 11:55:17 -0700
commit6bf4b90c90f15f4ab60833bddf5b5756d1a6b1f6 (patch)
treeed97e39ec77c5231ffd2c394493e68d00ddac5a4 /Homework/cs5260/consumer/dao
downloadmisc-undergrad-6bf4b90c90f15f4ab60833bddf5b5756d1a6b1f6.tar.gz
misc-undergrad-6bf4b90c90f15f4ab60833bddf5b5756d1a6b1f6.zip
Diffstat (limited to 'Homework/cs5260/consumer/dao')
-rw-r--r--Homework/cs5260/consumer/dao/DynamoWidgetDAO.ts100
-rw-r--r--Homework/cs5260/consumer/dao/S3WidgetDao.ts93
-rw-r--r--Homework/cs5260/consumer/dao/WidgetDAO.ts7
-rw-r--r--Homework/cs5260/consumer/dao/index.ts3
-rw-r--r--Homework/cs5260/consumer/dao/keys/S3WidgetKeyGenerator.ts43
-rw-r--r--Homework/cs5260/consumer/dao/keys/index.ts1
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";