From bdd701e015ab1aef77525fbc11ed30bac55a611b Mon Sep 17 00:00:00 2001 From: Eric Bach Date: Wed, 1 Jan 2025 13:31:05 -0700 Subject: [PATCH] Add balance field to account --- TODO.md | 13 +++++-------- backend/src/appsync/Mutation.createAccount.ts | 1 + backend/src/appsync/api/codegen/appsync.ts | 1 + backend/src/appsync/schema.graphql | 1 + frontend/src/actions/api/mutations.ts | 3 +++ frontend/src/actions/api/queries.ts | 2 ++ references/Tables.xlsx | Bin 16689 -> 16520 bytes 7 files changed, 13 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index dd818505..4620c92a 100644 --- a/TODO.md +++ b/TODO.md @@ -40,20 +40,17 @@ X Switch to sonner X Add tests to validate APIs and workflows X Switch frontend to use NextJS 14 with turbo X Switch transactionsResolver to use AppSync JS pipeline resolvers +X Rearchitect updatePositions/updateBalances to use AppSync JS Resolvers ##### Current Task -X Rearchitect updatePositions/updateBalances to use AppSync JS Resolvers -X BUG: Creates multiple symbols in drop down -X Ensure updatePositions updates on investment transactions create/update - -- Create updateBalances updates on bank transactions create/update -- simulate failure in updatePosition Lambda to ensure it goes to DLQ (i.e. set the pk to undefined L133) -- add tests to updatePosition Lambda -- Update dashboard to pull positions and totals - requires new API getPositions(userId) +- On PositionUpdatedEvent, update account balance with Position.marketvalue +- On BankTransactionSavedEvent, update account balance with Transaction.amount ##### Future Task +- add tests to updatePosition Lambda +- Update dashboard to pull positions and totals - requires new API getPositions(userId) - create L3 constructs for AppSync CDK - Update to nodejs 22 - Frontend - remove "Loading..." on screens, remove landing page for login page, improvements to FE diff --git a/backend/src/appsync/Mutation.createAccount.ts b/backend/src/appsync/Mutation.createAccount.ts index dea6b735..84e812d0 100644 --- a/backend/src/appsync/Mutation.createAccount.ts +++ b/backend/src/appsync/Mutation.createAccount.ts @@ -18,6 +18,7 @@ export function request(ctx: Context): DynamoDBPutIte category: util.dynamodb.toDynamoDB(ctx.args.input.category), type: util.dynamodb.toDynamoDB(ctx.args.input.type), name: util.dynamodb.toDynamoDB(ctx.args.input.name), + balance: util.dynamodb.toDynamoDB(0), userId: util.dynamodb.toDynamoDB((ctx.identity as AppSyncIdentityCognito).username), createdAt: util.dynamodb.toDynamoDB(datetime), updatedAt: util.dynamodb.toDynamoDB(datetime), diff --git a/backend/src/appsync/api/codegen/appsync.ts b/backend/src/appsync/api/codegen/appsync.ts index 75acda78..adb29605 100644 --- a/backend/src/appsync/api/codegen/appsync.ts +++ b/backend/src/appsync/api/codegen/appsync.ts @@ -26,6 +26,7 @@ export type Scalars = { export type Account = { __typename?: 'Account'; accountId: Scalars['ID']['output']; + balance: Scalars['Float']['output']; category: Scalars['String']['output']; createdAt: Scalars['AWSDateTime']['output']; entity: Scalars['String']['output']; diff --git a/backend/src/appsync/schema.graphql b/backend/src/appsync/schema.graphql index 47903c37..8f5e25bf 100644 --- a/backend/src/appsync/schema.graphql +++ b/backend/src/appsync/schema.graphql @@ -98,6 +98,7 @@ type Account { category: String! type: String! name: String! + balance: Float! userId: String! createdAt: AWSDateTime! updatedAt: AWSDateTime! diff --git a/frontend/src/actions/api/mutations.ts b/frontend/src/actions/api/mutations.ts index 8c3a39c4..123232f1 100644 --- a/frontend/src/actions/api/mutations.ts +++ b/frontend/src/actions/api/mutations.ts @@ -36,6 +36,7 @@ export const createAccount = `mutation CreateAccount($input: CreateAccountInput! name category type + balance createdAt updatedAt } @@ -47,6 +48,7 @@ export const updateAccount = `mutation UpdateAccount($input: UpdateAccountInput! name type entity + balance createdAt updatedAt } @@ -59,6 +61,7 @@ export const deleteAccount = `mutation DeleteAccount($accountId: String!) { name type entity + balance createdAt updatedAt } diff --git a/frontend/src/actions/api/queries.ts b/frontend/src/actions/api/queries.ts index 053a8433..b1d8d96c 100644 --- a/frontend/src/actions/api/queries.ts +++ b/frontend/src/actions/api/queries.ts @@ -24,6 +24,7 @@ export const getAccount = `query GetAccount($accountId: String!) { name category type + balance createdAt updatedAt } @@ -36,6 +37,7 @@ export const getAccounts = `query GetAccounts($lastEvaluatedKey: String) { name category type + balance createdAt updatedAt } diff --git a/references/Tables.xlsx b/references/Tables.xlsx index c688f0d20527879acc2abc03099883ec7c52250a..223c0cb70accb161835b5a7c012863a04b2172dc 100644 GIT binary patch delta 7903 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 b&(QzB;U@qel@vp{5rSiZ=`n^y|5^G!NS@?} delta 8089 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