condition check lambda
This commit is contained in:
63
infrastructure/README.md
Normal file
63
infrastructure/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Rentall Infrastructure
|
||||
|
||||
AWS CDK infrastructure for Rentall Lambda functions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 20+
|
||||
- AWS CLI configured with appropriate credentials
|
||||
- AWS CDK CLI (`npm install -g aws-cdk`)
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
cd infrastructure/cdk
|
||||
npm install
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
### Staging
|
||||
|
||||
```bash
|
||||
npm run deploy:staging
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
npm run deploy:prod
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables should be set before deployment:
|
||||
|
||||
- `DATABASE_URL` - PostgreSQL connection string
|
||||
- `CDK_DEFAULT_ACCOUNT` - AWS account ID
|
||||
- `CDK_DEFAULT_REGION` - AWS region (defaults to us-east-1)
|
||||
|
||||
## Stacks
|
||||
|
||||
### ConditionCheckLambdaStack
|
||||
|
||||
Creates:
|
||||
- Lambda function for condition check reminders
|
||||
- EventBridge Scheduler group for per-rental schedules
|
||||
- IAM roles for Lambda execution and Scheduler invocation
|
||||
- Dead letter queue for failed invocations
|
||||
|
||||
## Outputs
|
||||
|
||||
After deployment, the following values are exported:
|
||||
|
||||
- `ConditionCheckLambdaArn-{env}` - Lambda function ARN
|
||||
- `ConditionCheckScheduleGroup-{env}` - Schedule group name
|
||||
- `ConditionCheckSchedulerRoleArn-{env}` - Scheduler IAM role ARN
|
||||
- `ConditionCheckDLQUrl-{env}` - Dead letter queue URL
|
||||
|
||||
## Useful Commands
|
||||
|
||||
- `npm run synth` - Synthesize CloudFormation template
|
||||
- `npm run diff` - Compare deployed stack with current state
|
||||
- `npm run destroy` - Destroy all stacks
|
||||
63
infrastructure/cdk/bin/app.ts
Normal file
63
infrastructure/cdk/bin/app.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node
|
||||
import "source-map-support/register";
|
||||
import * as cdk from "aws-cdk-lib";
|
||||
import { ConditionCheckLambdaStack } from "../lib/condition-check-lambda-stack";
|
||||
|
||||
const app = new cdk.App();
|
||||
|
||||
// Get environment from context or default to staging
|
||||
const environment = app.node.tryGetContext("env") || "staging";
|
||||
|
||||
// Environment-specific configurations
|
||||
const envConfig: Record<
|
||||
string,
|
||||
{
|
||||
databaseUrl: string;
|
||||
frontendUrl: string;
|
||||
sesFromEmail: string;
|
||||
emailEnabled: boolean;
|
||||
}
|
||||
> = {
|
||||
staging: {
|
||||
// These should be passed via CDK context or SSM parameters in production
|
||||
databaseUrl:
|
||||
process.env.DATABASE_URL ||
|
||||
"postgresql://user:password@localhost:5432/rentall_staging",
|
||||
frontendUrl: "https://staging.villageshare.app",
|
||||
sesFromEmail: "noreply@villageshare.app",
|
||||
emailEnabled: true,
|
||||
},
|
||||
prod: {
|
||||
databaseUrl:
|
||||
process.env.DATABASE_URL ||
|
||||
"postgresql://user:password@localhost:5432/rentall_prod",
|
||||
frontendUrl: "https://villageshare.app",
|
||||
sesFromEmail: "noreply@villageshare.app",
|
||||
emailEnabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
const config = envConfig[environment];
|
||||
|
||||
if (!config) {
|
||||
throw new Error(`Unknown environment: ${environment}`);
|
||||
}
|
||||
|
||||
// Create the Lambda stack
|
||||
new ConditionCheckLambdaStack(app, `ConditionCheckLambdaStack-${environment}`, {
|
||||
environment,
|
||||
databaseUrl: config.databaseUrl,
|
||||
frontendUrl: config.frontendUrl,
|
||||
sesFromEmail: config.sesFromEmail,
|
||||
emailEnabled: config.emailEnabled,
|
||||
env: {
|
||||
account: process.env.CDK_DEFAULT_ACCOUNT,
|
||||
region: process.env.CDK_DEFAULT_REGION || "us-east-1",
|
||||
},
|
||||
description: `Condition Check Reminder Lambda infrastructure (${environment})`,
|
||||
tags: {
|
||||
Environment: environment,
|
||||
Project: "rentall",
|
||||
Service: "condition-check-reminder",
|
||||
},
|
||||
});
|
||||
21
infrastructure/cdk/cdk.json
Normal file
21
infrastructure/cdk/cdk.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"app": "npx ts-node --prefer-ts-exts bin/app.ts",
|
||||
"watch": {
|
||||
"include": ["**"],
|
||||
"exclude": [
|
||||
"README.md",
|
||||
"cdk*.json",
|
||||
"**/*.d.ts",
|
||||
"**/*.js",
|
||||
"tsconfig.json",
|
||||
"package*.json",
|
||||
"node_modules",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
"context": {
|
||||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
||||
"@aws-cdk/core:checkSecretUsage": true,
|
||||
"@aws-cdk/core:target-partitions": ["aws"]
|
||||
}
|
||||
}
|
||||
225
infrastructure/cdk/lib/condition-check-lambda-stack.ts
Normal file
225
infrastructure/cdk/lib/condition-check-lambda-stack.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import * as cdk from "aws-cdk-lib";
|
||||
import * as lambda from "aws-cdk-lib/aws-lambda";
|
||||
import * as iam from "aws-cdk-lib/aws-iam";
|
||||
import * as scheduler from "aws-cdk-lib/aws-scheduler";
|
||||
import * as sqs from "aws-cdk-lib/aws-sqs";
|
||||
import { Construct } from "constructs";
|
||||
import * as path from "path";
|
||||
|
||||
interface ConditionCheckLambdaStackProps extends cdk.StackProps {
|
||||
/**
|
||||
* Environment name (staging, prod)
|
||||
*/
|
||||
environment: string;
|
||||
|
||||
/**
|
||||
* Database URL for the Lambda
|
||||
*/
|
||||
databaseUrl: string;
|
||||
|
||||
/**
|
||||
* Frontend URL for email links
|
||||
*/
|
||||
frontendUrl: string;
|
||||
|
||||
/**
|
||||
* SES sender email
|
||||
*/
|
||||
sesFromEmail: string;
|
||||
|
||||
/**
|
||||
* SES sender name
|
||||
*/
|
||||
sesFromName?: string;
|
||||
|
||||
/**
|
||||
* Whether emails are enabled
|
||||
*/
|
||||
emailEnabled?: boolean;
|
||||
}
|
||||
|
||||
export class ConditionCheckLambdaStack extends cdk.Stack {
|
||||
/**
|
||||
* The Lambda function for condition check reminders
|
||||
*/
|
||||
public readonly lambdaFunction: lambda.Function;
|
||||
|
||||
/**
|
||||
* The EventBridge Scheduler group for condition check schedules
|
||||
*/
|
||||
public readonly scheduleGroup: scheduler.CfnScheduleGroup;
|
||||
|
||||
/**
|
||||
* Dead letter queue for failed Lambda invocations
|
||||
*/
|
||||
public readonly deadLetterQueue: sqs.Queue;
|
||||
|
||||
/**
|
||||
* IAM role for EventBridge Scheduler to invoke Lambda
|
||||
*/
|
||||
public readonly schedulerRole: iam.Role;
|
||||
|
||||
constructor(
|
||||
scope: Construct,
|
||||
id: string,
|
||||
props: ConditionCheckLambdaStackProps
|
||||
) {
|
||||
super(scope, id, props);
|
||||
|
||||
const {
|
||||
environment,
|
||||
databaseUrl,
|
||||
frontendUrl,
|
||||
sesFromEmail,
|
||||
sesFromName = "Village Share",
|
||||
emailEnabled = true,
|
||||
} = props;
|
||||
|
||||
// Dead Letter Queue for failed Lambda invocations
|
||||
this.deadLetterQueue = new sqs.Queue(this, "ConditionCheckDLQ", {
|
||||
queueName: `condition-check-reminder-dlq-${environment}`,
|
||||
retentionPeriod: cdk.Duration.days(14),
|
||||
});
|
||||
|
||||
// Lambda execution role
|
||||
const lambdaRole = new iam.Role(this, "ConditionCheckLambdaRole", {
|
||||
roleName: `condition-check-lambda-role-${environment}`,
|
||||
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
|
||||
description: "Execution role for Condition Check Reminder Lambda",
|
||||
});
|
||||
|
||||
// CloudWatch Logs permissions
|
||||
lambdaRole.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
actions: [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents",
|
||||
],
|
||||
resources: ["*"],
|
||||
})
|
||||
);
|
||||
|
||||
// SES permissions for sending emails
|
||||
lambdaRole.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
actions: ["ses:SendEmail", "ses:SendRawEmail"],
|
||||
resources: ["*"],
|
||||
})
|
||||
);
|
||||
|
||||
// EventBridge Scheduler permissions (for self-cleanup)
|
||||
lambdaRole.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
actions: ["scheduler:DeleteSchedule"],
|
||||
resources: [
|
||||
`arn:aws:scheduler:${this.region}:${this.account}:schedule/condition-check-reminders-${environment}/*`,
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// Lambda function
|
||||
this.lambdaFunction = new lambda.Function(
|
||||
this,
|
||||
"ConditionCheckReminderLambda",
|
||||
{
|
||||
functionName: `condition-check-reminder-${environment}`,
|
||||
runtime: lambda.Runtime.NODEJS_20_X,
|
||||
handler: "index.handler",
|
||||
code: lambda.Code.fromAsset(
|
||||
path.join(__dirname, "../../../lambdas/conditionCheckReminder"),
|
||||
{
|
||||
bundling: {
|
||||
image: lambda.Runtime.NODEJS_20_X.bundlingImage,
|
||||
command: [
|
||||
"bash",
|
||||
"-c",
|
||||
[
|
||||
"cp -r /asset-input/* /asset-output/",
|
||||
"cd /asset-output",
|
||||
"npm install --omit=dev",
|
||||
// Copy shared modules
|
||||
"mkdir -p shared",
|
||||
"cp -r /asset-input/../shared/* shared/",
|
||||
"cd shared && npm install --omit=dev",
|
||||
].join(" && "),
|
||||
],
|
||||
},
|
||||
}
|
||||
),
|
||||
role: lambdaRole,
|
||||
timeout: cdk.Duration.seconds(30),
|
||||
memorySize: 256,
|
||||
environment: {
|
||||
NODE_ENV: environment,
|
||||
DATABASE_URL: databaseUrl,
|
||||
FRONTEND_URL: frontendUrl,
|
||||
SES_FROM_EMAIL: sesFromEmail,
|
||||
SES_FROM_NAME: sesFromName,
|
||||
EMAIL_ENABLED: emailEnabled ? "true" : "false",
|
||||
SCHEDULE_GROUP_NAME: `condition-check-reminders-${environment}`,
|
||||
AWS_REGION: this.region,
|
||||
},
|
||||
deadLetterQueue: this.deadLetterQueue,
|
||||
retryAttempts: 2,
|
||||
description: "Sends condition check reminder emails for rentals",
|
||||
}
|
||||
);
|
||||
|
||||
// EventBridge Scheduler group
|
||||
this.scheduleGroup = new scheduler.CfnScheduleGroup(
|
||||
this,
|
||||
"ConditionCheckScheduleGroup",
|
||||
{
|
||||
name: `condition-check-reminders-${environment}`,
|
||||
}
|
||||
);
|
||||
|
||||
// IAM role for EventBridge Scheduler to invoke Lambda
|
||||
this.schedulerRole = new iam.Role(this, "SchedulerRole", {
|
||||
roleName: `condition-check-scheduler-role-${environment}`,
|
||||
assumedBy: new iam.ServicePrincipal("scheduler.amazonaws.com"),
|
||||
description: "Role for EventBridge Scheduler to invoke Lambda",
|
||||
});
|
||||
|
||||
// Allow scheduler to invoke the Lambda
|
||||
this.schedulerRole.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
actions: ["lambda:InvokeFunction"],
|
||||
resources: [
|
||||
this.lambdaFunction.functionArn,
|
||||
`${this.lambdaFunction.functionArn}:*`,
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// Outputs
|
||||
new cdk.CfnOutput(this, "LambdaFunctionArn", {
|
||||
value: this.lambdaFunction.functionArn,
|
||||
description: "ARN of the Condition Check Reminder Lambda",
|
||||
exportName: `ConditionCheckLambdaArn-${environment}`,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, "ScheduleGroupName", {
|
||||
value: this.scheduleGroup.name!,
|
||||
description: "Name of the EventBridge Scheduler group",
|
||||
exportName: `ConditionCheckScheduleGroup-${environment}`,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, "SchedulerRoleArn", {
|
||||
value: this.schedulerRole.roleArn,
|
||||
description: "ARN of the EventBridge Scheduler IAM role",
|
||||
exportName: `ConditionCheckSchedulerRoleArn-${environment}`,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, "DLQUrl", {
|
||||
value: this.deadLetterQueue.queueUrl,
|
||||
description: "URL of the Dead Letter Queue",
|
||||
exportName: `ConditionCheckDLQUrl-${environment}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
25
infrastructure/cdk/package.json
Normal file
25
infrastructure/cdk/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "rentall-infrastructure",
|
||||
"version": "1.0.0",
|
||||
"description": "AWS CDK infrastructure for Rentall Lambda functions",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"cdk": "cdk",
|
||||
"synth": "cdk synth",
|
||||
"deploy": "cdk deploy --all",
|
||||
"deploy:staging": "cdk deploy --all --context env=staging",
|
||||
"deploy:prod": "cdk deploy --all --context env=prod",
|
||||
"diff": "cdk diff",
|
||||
"destroy": "cdk destroy --all"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-cdk-lib": "^2.170.0",
|
||||
"constructs": "^10.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"aws-cdk": "^2.170.0"
|
||||
}
|
||||
}
|
||||
26
infrastructure/cdk/tsconfig.json
Normal file
26
infrastructure/cdk/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"typeRoots": ["./node_modules/@types"]
|
||||
},
|
||||
"include": ["bin/**/*", "lib/**/*"],
|
||||
"exclude": ["node_modules", "cdk.out"]
|
||||
}
|
||||
Reference in New Issue
Block a user