diff --git a/infrastructure/aws/README.md b/infrastructure/aws/README.md
index c52d8c9..c011548 100644
--- a/infrastructure/aws/README.md
+++ b/infrastructure/aws/README.md
@@ -11,6 +11,7 @@
| Name | Version |
|------|---------|
+| [archive](#provider\_archive) | n/a |
| [aws](#provider\_aws) | 6.14.1 |
| [infisical](#provider\_infisical) | n/a |
@@ -25,7 +26,18 @@ No modules.
| [aws_cognito_user_pool.branch_user_pool](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/cognito_user_pool) | resource |
| [aws_cognito_user_pool_client.branch_client](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/cognito_user_pool_client) | resource |
| [aws_db_instance.branch_rds](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/db_instance) | resource |
+| [aws_iam_role.lambda_role](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.lambda_basic](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_lambda_function.functions](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/lambda_function) | resource |
+| [aws_s3_bucket.lambda_deployments](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/s3_bucket) | resource |
| [aws_s3_bucket.reports_bucket](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket_policy.reports_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/s3_bucket_policy) | resource |
+| [aws_s3_bucket_public_access_block.reports_bucket_public_access](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_s3_bucket_server_side_encryption_configuration.lambda_deployments](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
+| [aws_s3_bucket_versioning.lambda_deployments](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/s3_bucket_versioning) | resource |
+| [aws_s3_object.lambda_placeholder](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/resources/s3_object) | resource |
+| [archive_file.lambda_placeholder](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
+| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/6.14.1/docs/data-sources/caller_identity) | data source |
| [infisical_secrets.rds_folder](https://registry.terraform.io/providers/infisical/infisical/latest/docs/data-sources/secrets) | data source |
## Inputs
@@ -40,9 +52,8 @@ No modules.
| Name | Description |
|------|-------------|
-| [cognito\_client\_id](#output\_cognito\_client\_id) | Cognito User Pool Client ID |
| [cognito\_region](#output\_cognito\_region) | AWS Region for Cognito |
| [cognito\_user\_pool\_arn](#output\_cognito\_user\_pool\_arn) | Cognito User Pool ARN |
| [cognito\_user\_pool\_endpoint](#output\_cognito\_user\_pool\_endpoint) | Cognito User Pool Endpoint |
-| [cognito\_user\_pool\_id](#output\_cognito\_user\_pool\_id) | Cognito User Pool ID |
+| [reports\_bucket\_name](#output\_reports\_bucket\_name) | Name of the S3 bucket for generated reports |
diff --git a/infrastructure/aws/lambda.tf b/infrastructure/aws/lambda.tf
new file mode 100644
index 0000000..2453f6e
--- /dev/null
+++ b/infrastructure/aws/lambda.tf
@@ -0,0 +1,107 @@
+# IAM role for Lambda functions
+resource "aws_iam_role" "lambda_role" {
+ name = "branch-lambda-role"
+ assume_role_policy = jsonencode({
+ Version = "2012-10-17"
+ Statement = [{
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = { Service = "lambda.amazonaws.com" }
+ }]
+ })
+}
+
+# Attach basic execution policy for CloudWatch Logs
+resource "aws_iam_role_policy_attachment" "lambda_basic" {
+ role = "aws_iam_role.lambda_role.name"
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
+}
+
+# Get AWS account ID for unique bucket naming
+data "aws_caller_identity" "current" {}
+
+resource "aws_s3_bucket" "lambda_deployments" {
+ bucket = "branch-lambda-deployments-${data.aws_caller_identity.current.account_id}"
+}
+
+resource "aws_s3_bucket_versioning" "lambda_deployments" {
+ bucket = aws_s3_bucket.lambda_deployments.id
+
+ versioning_configuration {
+ status = "Enabled"
+ }
+}
+
+resource "aws_s3_bucket_server_side_encryption_configuration" "lambda_deployments" {
+ bucket = aws_s3_bucket.lambda_deployments.id
+
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+}
+
+# Define all Lambda functions in one place
+locals {
+ lambda_functions = toset([
+ "projects",
+ "reports",
+ "users",
+ "donors",
+ "expenditures"
+ ])
+}
+
+# Minimal placeholder that will be replaced by GitHub Actions on first deployment
+data "archive_file" "lambda_placeholder" {
+ type = "zip"
+ output_path = "$${path.module}/lambda-placeholder.zip"
+ source {
+ content = "exports.handler = async () => ({ statusCode: 200, body: JSON.stringify({ message: 'Placeholder - will be replaced by CI/CD' }) });"
+ filename = "handler.js"
+ }
+}
+
+# This allows Terraform to create the Lambda functions initially
+resource "aws_s3_object" "lambda_placeholder" {
+ for_each = local.lambda_functions
+
+ bucket = aws_s3_bucket.lambda_deployments.id
+ key = "${each.key}/initial.zip"
+ source = data.archive_file.lambda_placeholder.output_path
+
+ content_type = "application/zip"
+}
+
+# Create all Lambda functions with a single resource block
+resource "aws_lambda_function" "functions" {
+ for_each = local.lambda_functions
+
+ function_name = "branch-${each.key}"
+ runtime = "nodejs20.x"
+ handler = "handler.handler"
+ timeout = 30
+ memory_size = 256
+ role = aws_iam_role.lambda_role.arn
+
+ # Use S3 for deployment (initial placeholder, replaced by GitHub Actions)
+ s3_bucket = aws_s3_bucket.lambda_deployments.id
+ s3_key = aws_s3_object.lambda_placeholder[each.key].key
+
+ # Prevent Terraform from reverting code deployments made by GitHub Actions
+ lifecycle {
+ ignore_changes = [s3_key]
+ }
+
+ environment {
+ variables = {
+ NODE_ENV = "production"
+ DB_HOST = aws_db_instance.branch_rds.address
+ DB_USER = data.infisical_secrets.rds_folder.secrets["username"].value
+ DB_PASSWORD = data.infisical_secrets.rds_folder.secrets["password"].value
+ DB_PORT = try(data.infisical_secrets.rds_folder.secrets["db_port"].value, "5432")
+ DB_NAME = try(data.infisical_secrets.rds_folder.secrets["db_name"].value, aws_db_instance.branch_rds.db_name)
+ }
+ }
+}
\ No newline at end of file