Skip to content

Commit

Permalink
Implement UpdateSessionOperation in dynamo-agnostic way
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-cooksley-gds committed Jan 16, 2025
1 parent 45dd941 commit f20d33f
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 0 deletions.
89 changes: 89 additions & 0 deletions backend-api/src/functions/adapters/dynamoDbAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {
AttributeValue,
ConditionalCheckFailedException,
DynamoDBClient,
PutItemCommand,
PutItemCommandInput,
QueryCommand,
QueryCommandInput,
QueryCommandOutput,
UpdateItemCommand,
} from "@aws-sdk/client-dynamodb";
import { CreateSessionAttributes } from "../services/session/sessionService";
import { NodeHttpHandler } from "@smithy/node-http-handler";
Expand All @@ -14,6 +16,13 @@ import {
NativeAttributeValue,
unmarshall,
} from "@aws-sdk/util-dynamodb";
import { UpdateSessionOperation } from "../common/session/updateOperations/UpdateSessionOperation";
import {
emptySuccess,
ErrorCategory,
errorResult,
Result,
} from "../utils/result";

const sessionStates = {
ASYNC_AUTH_SESSION_CREATED: "ASYNC_AUTH_SESSION_CREATED",
Expand Down Expand Up @@ -114,7 +123,87 @@ export class DynamoDbAdapter {
}
}

async updateSession(
sessionId: string,
updateOperation: UpdateSessionOperation,
): Promise<Result<void>> {
const updateItemCommand = new UpdateItemCommand({
TableName: "mockTableName",
Key: {
sessionId: {
S: "mockSessionId",
},
},
ExpressionAttributeValues: getExpressionAttributeValues(updateOperation),
ConditionExpression: getConditionExpression(updateOperation),
UpdateExpression: getUpdateExpression(updateOperation),
});

try {
console.log(
"Update session attempt",
updateItemCommand.input.UpdateExpression,
); // replace with proper logging
await this.dynamoDbClient.send(updateItemCommand);
} catch (error) {
if (error instanceof ConditionalCheckFailedException) {
console.log(
"Conditional check failed",
updateItemCommand.input.ConditionExpression,
); // replace with proper logging
return errorResult({
errorMessage: "Conditional check failed",
errorCategory: ErrorCategory.CLIENT_ERROR,
});
} else {
console.log("Unexpected error", error); // replace with proper logging
return errorResult({
errorMessage: "Unexpected error",
errorCategory: ErrorCategory.SERVER_ERROR,
});
}
}
console.log("Update session success"); // replace with proper logging
return emptySuccess();
}

private getTimeNowInSeconds() {
return Math.floor(Date.now() / 1000);
}
}

function getExpressionAttributeValues(
updateOperation: UpdateSessionOperation,
): Record<string, AttributeValue> {
const attributeValues: Record<string, AttributeValue> = {
":sessionState": marshall(updateOperation.targetState),
};
updateOperation.eligibleStartingStates.forEach((state) => {
attributeValues[`:${state}`] = marshall(state);
});
Object.entries(updateOperation.getFieldUpdates()).forEach(([key, value]) => {
attributeValues[`:${key}`] = marshall(value);
});
return attributeValues;
}

function getConditionExpression(
updateOperation: UpdateSessionOperation,
): string {
const permissibleStatesAsAttributes: string =
updateOperation.eligibleStartingStates
.map((state) => `:${state}`)
.join(", ");
return `sessionState in (${permissibleStatesAsAttributes})`; // sessionState in (:EXAMPLE_SESSION_STATE_1, :EXAMPLE_SESSION_STATE_2)
}

function getUpdateExpression(updateOperation: UpdateSessionOperation): string {
const fieldsToUpdate = [
"sessionState",
...Object.keys(updateOperation.getFieldUpdates()),
];
const updateExpressions = fieldsToUpdate.map(
(fieldName) => `${fieldName} = :${fieldName}`,
);
return `set ${updateExpressions.join(", ")}`; // set sessionState = :EXAMPLE_SESSION_STATE, field1 = :field1, field2 = :field2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
SessionState,
UpdateSessionOperation,
} from "../UpdateSessionOperation";
import { DocumentType } from "../../../../types/document";

export class BiometricTokenIssued implements UpdateSessionOperation {
constructor(
private readonly documentType: DocumentType,
private readonly opaqueId: string,
) {}

readonly targetState = SessionState.BIOMETRIC_TOKEN_ISSUED;

readonly eligibleStartingStates = [SessionState.AUTH_SESSION_CREATED];

getFieldUpdates() {
return {
documentType: this.documentType,
opaqueId: this.opaqueId,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SessionState } from "../session";

type SessionFieldValue = string | number;

export interface UpdateSessionOperation {
readonly targetState: SessionState;
readonly eligibleStartingStates: SessionState[];
getFieldUpdates(): Record<string, SessionFieldValue>;
}

0 comments on commit f20d33f

Please sign in to comment.