Updates and added SES client.
This commit is contained in:
parent
ae4c215e5e
commit
a4a4f55d08
@ -3,7 +3,7 @@ import * as cdk from 'aws-cdk-lib/core';
|
|||||||
import { JbApiAwsStack } from '../lib/jb-api-aws-stack';
|
import { JbApiAwsStack } from '../lib/jb-api-aws-stack';
|
||||||
|
|
||||||
const app = new cdk.App();
|
const app = new cdk.App();
|
||||||
new JbApiAwsStack(app, 'JbApiAwsStack', {
|
new JbApiAwsStack(app, 'JbApi', {
|
||||||
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
|
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
|
||||||
env: {
|
env: {
|
||||||
account: process.env.CDK_DEFAULT_ACCOUNT,
|
account: process.env.CDK_DEFAULT_ACCOUNT,
|
||||||
|
|||||||
@ -1,10 +1,19 @@
|
|||||||
import * as cdk from 'aws-cdk-lib/core';
|
import * as cdk from 'aws-cdk-lib/core';
|
||||||
import { Construct } from 'constructs';
|
import { Construct } from 'constructs';
|
||||||
import { ContactConstruct } from './lambda/contact/ContactConstruct';
|
import { ContactConstruct } from './lambda/contact/ContactConstruct';
|
||||||
|
import { DomainName } from 'aws-cdk-lib/aws-apigateway';
|
||||||
|
|
||||||
export class JbApiAwsStack extends cdk.Stack {
|
export class JbApiAwsStack extends cdk.Stack {
|
||||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||||
super(scope, id, props);
|
super(scope, id, props);
|
||||||
new ContactConstruct(this, id);
|
|
||||||
|
const apiDomainName = DomainName.fromDomainNameAttributes(this, id, {
|
||||||
|
domainName: 'api.jessebrault.com',
|
||||||
|
domainNameAliasTarget:
|
||||||
|
'd-fax16c4l5l.execute-api.us-east-2.amazonaws.com',
|
||||||
|
domainNameAliasHostedZoneId: 'ZOJJZC49E0EPZ'
|
||||||
|
});
|
||||||
|
|
||||||
|
new ContactConstruct(this, 'ContactConstruct', { apiDomainName });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
132
lib/lambda/contact/ContactConstruct.Contact.ts
Normal file
132
lib/lambda/contact/ContactConstruct.Contact.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import {
|
||||||
|
APIGatewayProxyEvent,
|
||||||
|
APIGatewayProxyResult,
|
||||||
|
Context
|
||||||
|
} from 'aws-lambda';
|
||||||
|
import {
|
||||||
|
SendEmailCommand,
|
||||||
|
SESClient,
|
||||||
|
SESClientConfig
|
||||||
|
} from '@aws-sdk/client-ses';
|
||||||
|
|
||||||
|
interface ContactRequest {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
institution: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ValidationError {
|
||||||
|
field: keyof ContactRequest;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ValidationErrorsResponse {
|
||||||
|
errors: ValidationError[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sesClient = new SESClient({
|
||||||
|
region: 'us-east-2'
|
||||||
|
} satisfies SESClientConfig);
|
||||||
|
|
||||||
|
export async function handler(
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context: Context
|
||||||
|
): Promise<APIGatewayProxyResult> {
|
||||||
|
if (event.body === null) {
|
||||||
|
return {
|
||||||
|
statusCode: 400,
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: 'ContactRequest body required.'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, email, institution, message } = JSON.parse(
|
||||||
|
event.body
|
||||||
|
) as ContactRequest;
|
||||||
|
const errors: ValidationError[] = [];
|
||||||
|
|
||||||
|
// name
|
||||||
|
if (!name) {
|
||||||
|
errors.push({
|
||||||
|
field: 'name',
|
||||||
|
message: 'Name is required.'
|
||||||
|
});
|
||||||
|
} else if (name.trim().length === 0) {
|
||||||
|
errors.push({
|
||||||
|
field: 'name',
|
||||||
|
message: 'Name may not be blank.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// email
|
||||||
|
if (!email) {
|
||||||
|
errors.push({
|
||||||
|
field: 'email',
|
||||||
|
message: 'Email is required.'
|
||||||
|
});
|
||||||
|
} else if (email.trim().length === 0) {
|
||||||
|
errors.push({
|
||||||
|
field: 'email',
|
||||||
|
message: 'Email may not be blank.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// message
|
||||||
|
if (!message) {
|
||||||
|
errors.push({
|
||||||
|
field: 'message',
|
||||||
|
message: 'Message is required.'
|
||||||
|
});
|
||||||
|
} else if (message.trim().length === 0) {
|
||||||
|
errors.push({
|
||||||
|
field: 'message',
|
||||||
|
message: 'Message may not be blank.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
return {
|
||||||
|
statusCode: 400,
|
||||||
|
headers: {
|
||||||
|
'Content-type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ errors } satisfies ValidationErrorsResponse)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const sendEmailCommand = new SendEmailCommand({
|
||||||
|
Source: 'noreply@api.jessebrault.com',
|
||||||
|
Destination: {
|
||||||
|
ToAddresses: ['jesse@jessebrault.com']
|
||||||
|
},
|
||||||
|
Message: {
|
||||||
|
Subject: {
|
||||||
|
Charset: 'UTF-8',
|
||||||
|
Data: 'Contact request'
|
||||||
|
},
|
||||||
|
Body: {
|
||||||
|
Text: {
|
||||||
|
Charset: 'utf-8',
|
||||||
|
Data: `
|
||||||
|
Contact Request from jessebrault.com
|
||||||
|
From ${name}
|
||||||
|
Email ${email}
|
||||||
|
Institution ${institution ?? '<none>'}
|
||||||
|
--- Message ---
|
||||||
|
|
||||||
|
${message}
|
||||||
|
`.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await sesClient.send(sendEmailCommand);
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: 'Success'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { APIGatewayEvent, Context, APIGatewayProxyResult } from 'aws-lambda';
|
|
||||||
|
|
||||||
export async function handler(
|
|
||||||
event: APIGatewayEvent,
|
|
||||||
context: Context
|
|
||||||
): Promise<APIGatewayProxyResult> {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
message: 'Hello, World!'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,22 +1,38 @@
|
|||||||
import { Construct } from 'constructs';
|
import { Construct } from 'constructs';
|
||||||
import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs';
|
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
|
||||||
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
||||||
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
import {
|
||||||
|
BasePathMapping,
|
||||||
|
IDomainName,
|
||||||
|
LambdaIntegration,
|
||||||
|
RestApi
|
||||||
|
} from 'aws-cdk-lib/aws-apigateway';
|
||||||
|
|
||||||
|
export interface ContactConstructProps {
|
||||||
|
apiDomainName: IDomainName;
|
||||||
|
}
|
||||||
|
|
||||||
export class ContactConstruct extends Construct {
|
export class ContactConstruct extends Construct {
|
||||||
constructor(scope: Construct, id: string) {
|
constructor(scope: Construct, id: string, props: ContactConstructProps) {
|
||||||
super(scope, id);
|
super(scope, id);
|
||||||
|
|
||||||
const contactLambda = new nodejs.NodejsFunction(this, 'contact', {
|
const contactServiceRestApi = new RestApi(this, 'ContactService', {
|
||||||
|
description: 'The ContactService rest api.'
|
||||||
|
});
|
||||||
|
new BasePathMapping(this, 'ContactServiceMapping', {
|
||||||
|
domainName: props.apiDomainName,
|
||||||
|
restApi: contactServiceRestApi,
|
||||||
|
basePath: 'contact'
|
||||||
|
});
|
||||||
|
|
||||||
|
// contact endpoint
|
||||||
|
const contactLambda = new NodejsFunction(this, 'Contact', {
|
||||||
|
description: 'The lambda for the contact endpoint.',
|
||||||
runtime: lambda.Runtime.NODEJS_24_X
|
runtime: lambda.Runtime.NODEJS_24_X
|
||||||
});
|
});
|
||||||
|
const contactIntegration = new LambdaIntegration(contactLambda);
|
||||||
const api = new apigateway.LambdaRestApi(this, 'ContactApi', {
|
const contactResource =
|
||||||
handler: contactLambda,
|
contactServiceRestApi.root.addResource('contact');
|
||||||
proxy: false
|
contactResource.addMethod('POST', contactIntegration);
|
||||||
});
|
|
||||||
|
|
||||||
const contactResource = api.root.addResource('contact');
|
|
||||||
contactResource.addMethod('GET');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1288
package-lock.json
generated
1288
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@
|
|||||||
"typescript": "~5.9.3"
|
"typescript": "~5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-ses": "^3.966.0",
|
||||||
"aws-cdk-lib": "^2.232.2",
|
"aws-cdk-lib": "^2.232.2",
|
||||||
"constructs": "^10.0.0"
|
"constructs": "^10.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user