Home · Apps · rl-bank-mvp · rl-main-infra · todo_api · todo_mobile
flowchart TB
Users[Customers + Merchant Portal Users]
Auth0[Auth0 Tenant]
subgraph Edge[Edge / Public]
CFWeb[CloudFront\ncustomer web]
CFMp[CloudFront\nmerchant portal]
WAF[WAF optional phase 2]
end
subgraph AWS[AWS ap-southeast-1 / account 982233224911]
subgraph VPC[VPC]
ALB[Public ALB\napi.bank.example]
ECS[ECS Fargate Service\nrl-bank-api]
Cache[ElastiCache Serverless\nValkey]
DB[(External DB via DB_URI\nor managed DB later)]
SM[Secrets Manager]
SSM[SSM Parameter Store]
CW[CloudWatch Logs + Alarms]
end
S3Web[S3 bucket\ncustomer app build]
S3Mp[S3 bucket\nmerchant portal build]
ECR[ECR repo\nrl-bank-api]
ACMEdge[ACM us-east-1\nCloudFront certs]
ACMAlb[ACM ap-southeast-1\nALB cert]
R53[Route53 Hosted Zone]
end
Users --> CFWeb
Users --> CFMp
Users --> ALB
CFWeb --> S3Web
CFMp --> S3Mp
Users -. login .-> Auth0
CFWeb -. tokens .-> Auth0
CFMp -. tokens .-> Auth0
ALB --> ECS
ECS --> Cache
ECS --> DB
ECS --> Auth0
ECS --> SM
ECS --> SSM
ECS --> CW
ECS --> ECR
R53 --> CFWeb
R53 --> CFMp
R53 --> ALB
ACMEdge --> CFWeb
ACMEdge --> CFMp
ACMAlb --> ALB
WAF -. attach later .-> CFWeb
WAF -. attach later .-> CFMp
If the fake-bank MVP exposes PBX telephony, voice-bot, or IVR entry points, treat those as part of the product safety surface rather than just an ops detail.
The standing documentation rule is:
See PBX IVR / Call Script for the canonical wording and routing guidance.
Assume one hosted zone already exists or will be created in Route53. Suggested public names:
api.<bank-domain> → ALB for rl-bank-apiapp.<bank-domain> → CloudFront for rl-bank-webappmerchant.<bank-domain> → CloudFront for rl-bank-mp-webappTLS assumptions:
us-east-1.ap-southeast-1.Auth0 callback/logout/web origin assumptions:
https://app.<bank-domain>https://merchant.<bank-domain>https://api.<bank-domain>rl-bank-apiCACHE_URIrl-bank-webapprl-bank-mp-webappReuse the rl-main-infra production VPC patterns where possible.
assignPublicIp=falseFor MVP, deploy rl-bank-api as a single ECS Fargate service.
Why this is the right call:
rl-main-infra patterns better than introducing Lambda or EKS now.Suggested runtime shape:
20.5 vCPU / 1-2 GBGET /health9000/api/* or whole hostname to the API target groupImportant repo observation:
/api/api/graphql/healthrl-bank-mp-webapp (React/Vite)Host as a static SPA on S3 + CloudFront.
Needs:
index.html) for client-side routesVITE_API_BASE_URLVITE_AUTH_DOMAINVITE_AUTH_CLIENT_IDVITE_SENTRY_DSNVITE_SENTRY_RELEASErl-bank-webapp (Flutter web)Also host on S3 + CloudFront.
Needs:
flutter build web --dart-define=...AUTH0_DOMAINAUTH0_CLIENT_IDGRAPHQL_ENDPOINTSENTRY_ENVindex.htmlOpinionated note: for this MVP I would keep both web apps on the same hosting pattern rather than mixing Amplify Hosting for one and S3/CloudFront for the other. One pattern means less drift, easier Pulumi reuse, and clearer IAM.
The next realism slice stays entirely inside the existing app/API architecture:
rl-bank-mp-webapp gains a staff support workspace for account servicingrl-bank-api extends admin account GraphQL for support detail + status mutationssequenceDiagram
participant Staff as Staff user
participant Portal as rl-bank-mp-webapp
participant API as rl-bank-api GraphQL
participant Guard as Staff authz guard
participant Account as Account service
participant Audit as Auditlog service
participant DB as Mongo collections
Staff->>Portal: Open support workspace
Portal->>API: adminListAccountsForSupport
API->>Guard: validate account.read
Guard-->>API: allowed
API->>Account: list support accounts
Account->>DB: read accounts + latest transactions
DB-->>Account: account list
Account-->>API: support list payload
API-->>Portal: account queue
Staff->>Portal: Submit freeze/unfreeze/close + reason
Portal->>API: adminUpdateAccountStatus
API->>Guard: validate account.manage
Guard-->>API: allowed
API->>Account: validate transition + update status
Account->>DB: update accounts
Account->>Audit: record servicing action
Audit->>DB: insert audit_logs entry
Account->>DB: read refreshed support detail
DB-->>Account: refreshed context
Account-->>API: support detail payload
API-->>Portal: updated detail + audit-backed history
https://api.<bank-domain>/api/graphql.rl-bank-api already has the right broad shape:
MVP requirements to formalize:
https://app.<bank-domain>https://merchant.<bank-domain>Suggested Auth0 objects:
rl-bank-apirl-bank-api Management API accessFollow the rl-main-infra config style: explicit Pulumi config for infrastructure decisions, app runtime config delivered through AWS-native services.
infra:*)Use for infrastructure decisions only, for example:
Use for runtime values that are not sensitive:
NODE_ENVPORTCORS_ORIGIN_WHITELISTAUTH_DOMAINAUTH_AUDIENCEGRAPHQL_PUBLIC_URLSENTRY_RELEASEUse for anything credential-like:
DB_URICACHE_URI if it contains auth materialAUTH_M2M_CLIENT_IDAUTH_M2M_CLIENT_SECRETAPI_KEYSENTRY_DSN if treated as secret in your practiceMAILGUN_API_KEYTWILIO_*/rl/bank/api/<key>rl/bank/api/<key>prd-rl-bank-webapp, prd-rl-bank-mp-webapp style if globally availableprd-rl-bank-apirl-bank-apiThe code currently only tells us this for sure:
DB_URIThat is a giant hint not to hard-code a database choice into the first infra pass.
My recommendation:
DB_URI via Secrets Manager.If the engine is confirmed later:
For MVP planning today, forcing a DB resource before the app team confirms the engine is how infra gets stupid. Better to leave a clean interface now.
Needs only:
ecr:GetAuthorizationToken on *ecr:BatchCheckLayerAvailabilityecr:InitiateLayerUploadecr:UploadLayerPartecr:CompleteLayerUploadecr:BatchGetImageecr:PutImageScope recommendation:
rl-bank-apiGetAuthorizationToken remains * because AWS requires itNeeds only:
s3:ListBuckets3:GetObjects3:PutObjects3:DeleteObjectcloudfront:CreateInvalidationScope recommendation:
Needs only:
ecr:GetAuthorizationTokenecr:BatchGetImageecr:GetDownloadUrlForLayerlogs:CreateLogStreamlogs:PutLogEventssecretsmanager:GetSecretValue for only referenced secretsssm:GetParameters / ssm:GetParameter for only referenced parameterskms:Decrypt only for the CMKs protecting those secrets/parameters, if customer-managed keys are usedStart narrow. Only grant app runtime calls it actually makes. Likely set:
secretsmanager:GetSecretValue only if the app fetches secrets at runtime itselfssm:GetParameter(s) only if the app fetches config at runtime itselfkms:Decrypt scoped to specific keys if neededFor infra Pulumi only:
route53:ChangeResourceRecordSets scoped to hosted zone ARNroute53:ListHostedZonesByName, route53:GetHostedZone, route53:ListResourceRecordSetsacm:DescribeCertificate, acm:ListCertificates, acm:RequestCertificate, acm:DeleteCertificate scoped as tightly as possibleIf creating serverless cache via Pulumi, infra role needs ElastiCache create/read/update for only the relevant cache resources. App runtime itself normally does not need AWS IAM for cache access; it connects over the VPC network using the endpoint/secret.
To stay consistent with rl-main-infra, I would add a gated module rather than a one-off script pile.
Suggested module layout:
src/modules/bank-api.tssrc/modules/bank-web-static-site.tssrc/modules/bank-mp-web-static-site.tssrc/modules/acm-edge.tssrc/modules/bank-dns.tssrc/modules/bank-observability.tssrc/modules/bank-secrets-scaffold.tsSuggested config shape:
infra:bankMvp:
enabled: true
createResources: false
namePrefix: prd-rl-bank
domain:
rootDomain: example.com
apiSubdomain: api
appSubdomain: app
merchantSubdomain: merchant
hostedZoneId: Z123456789
network:
vpcId: vpc-...
publicSubnetIds:
- subnet-a
- subnet-b
privateSubnetIds:
- subnet-c
- subnet-d
api:
image: 982233224911.dkr.ecr.ap-southeast-1.amazonaws.com/rl-bank-api:latest
containerPort: 9000
desiredCount: 2
healthCheckPath: /health
webapp:
bucketName: prd-rl-bank-webapp
merchantPortal:
bucketName: prd-rl-bank-mp-webapp
auth0:
domain: tenant.region.auth0.com
audience: https://api.example.com
Pattern note:
enabled=true, createResources=false should produce a plan/scaffold output only, matching the safer patterns already in rl-main-infra.createResources=true be allowed.This application-layer slice sits on top of the same architecture above and does not require new infrastructure.
flowchart LR
Customer --> CustomerApp[Customer web/mobile app]
CustomerApp --> GraphQL[rl-bank-api /api/graphql]
GraphQL --> AccountService[Account service]
GraphQL --> TransactionService[Transaction service]
AccountService --> Accounts[(accounts)]
AccountService --> Cards[(cards)]
AccountService --> Transactions[(transactions)]
TransactionService --> Transactions
The main design choice is to compute account realism from existing persisted data rather than invent a separate account-history subsystem too early.
createResources=false/health/api/graphqlrl-bank-api.rl-main-infra or a dedicated rl-bank-infra repo that follows the same patterns.Short version:
rl-main-infra with gated bank modules.rl-bank-infra but copy the same Pulumi conventions from rl-main-infra.My bias for this MVP: start inside rl-main-infra as a gated module set, because the patterns already exist and there is only one production AWS account.