Serverless Webapp with Security Deployment Steps & Images

Deployment Images
Step 1:

Step 2:

Step 3:

Step 4:

Step 5:

Step 6:

Lambda Code - CustomerProfilesCRUD





































DynamoDB Data - JSON




































































































ArgoCD Service & HTTPRoute
import json
import boto3
import uuid
from datetime import datetime
from decimal import Decimal
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('CustomerMaster')
def lambda_handler(event, context):
"""
Unified Lambda function for all CRUD operations on Customer Profiles
Routes based on HTTP method and path
"""
print(f"Received event: {json.dumps(event)}")
try:
# Extract HTTP method and path
http_method = event.get('httpMethod', event.get('requestContext', {}).get('http', {}).get('method'))
path = event.get('path', '')
path_params = event.get('pathParameters', {}) or {}
query_params = event.get('queryStringParameters', {}) or {}
# Route to appropriate handler
if http_method == 'GET':
if path_params.get('profileId'):
# GET /customers/{profileId}
return get_customer_by_id(path_params['profileId'])
elif query_params.get('phoneNumber'):
# GET /customers?phoneNumber=xxx
return get_customer_by_phone(query_params['phoneNumber'])
elif query_params.get('email'):
# GET /customers?email=xxx
return get_customer_by_email(query_params['email'])
else:
# GET /customers (list all)
return get_all_customers(query_params)
elif http_method == 'POST':
# POST /customers (create)
return create_customer(event)
elif http_method == 'PUT':
# PUT /customers/{profileId} (update)
profile_id = path_params.get('profileId')
if not profile_id:
return error_response(400, 'ProfileId is required in path')
return update_customer(profile_id, event)
elif http_method == 'DELETE':
# DELETE /customers/{profileId}
profile_id = path_params.get('profileId')
if not profile_id:
return error_response(400, 'ProfileId is required in path')
return delete_customer(profile_id)
else:
return error_response(405, f'Method {http_method} not allowed')
except Exception as e:
print(f"Error in lambda_handler: {str(e)}")
import traceback
traceback.print_exc()
return error_response(500, 'Internal server error', str(e))
# ==================== CREATE ====================
def create_customer(event):
"""Create a new customer profile"""
try:
# Parse body
if isinstance(event.get('body'), str):
body = json.loads(event['body'])
else:
body = event.get('body', {})
# Validate required fields
required_fields = ['FirstName', 'LastName', 'PhoneNumber', 'Email']
for field in required_fields:
if not body.get(field):
return error_response(400, f'Missing required field: {field}')
# Generate ProfileId if not provided
profile_id = body.get('ProfileId', f"cust-{str(uuid.uuid4())[:8]}")
# Check if customer with phone number already exists
existing = check_existing_customer(body['PhoneNumber'])
if existing:
return error_response(409, 'Customer with this phone number already exists',
existing_profile_id=existing['ProfileId'])
# Prepare item for DynamoDB
customer_item = {
'ProfileId': profile_id,
'FirstName': body['FirstName'],
'LastName': body['LastName'],
'PhoneNumber': body['PhoneNumber'],
'Email': body['Email'],
'City': body.get('City', ''),
'State': body.get('State', ''),
'CustomerType': body.get('CustomerType', 'Standard'),
'LoyaltyTier': body.get('LoyaltyTier', 'Bronze'),
'CreatedAt': datetime.utcnow().isoformat(),
'UpdatedAt': datetime.utcnow().isoformat()
}
# Add optional fields if provided
optional_fields = ['Address', 'PostalCode', 'Country', 'BirthDate',
'AccountNumber', 'AccountBalance', 'Notes', 'TotalPurchases',
'LastPurchaseDate', 'PreferredLanguage']
for field in optional_fields:
if field in body:
customer_item[field] = body[field]
# Insert into DynamoDB
table.put_item(Item=customer_item)
print(f"Successfully created customer: {profile_id}")
return success_response(201, {
'message': 'Customer profile created successfully',
'customer': customer_item
})
except Exception as e:
print(f"Error creating customer: {str(e)}")
return error_response(500, 'Failed to create customer', str(e))
# ==================== READ ====================
def get_customer_by_id(profile_id):
"""Get customer by ProfileId"""
try:
response = table.get_item(Key={'ProfileId': profile_id})
if 'Item' not in response:
return error_response(404, 'Customer not found')
return success_response(200, {
'customer': response['Item']
})
except Exception as e:
print(f"Error getting customer by ID: {str(e)}")
return error_response(500, 'Failed to retrieve customer', str(e))
def get_customer_by_phone(phone_number):
"""Get customer by phone number using GSI"""
try:
response = table.query(
IndexName='PhoneNumber-index',
KeyConditionExpression='PhoneNumber = :phone',
ExpressionAttributeValues={':phone': phone_number}
)
if not response['Items']:
return error_response(404, 'Customer not found')
return success_response(200, {
'customer': response['Items'][0]
})
except Exception as e:
print(f"Error getting customer by phone: {str(e)}")
return error_response(500, 'Failed to retrieve customer', str(e))
def get_customer_by_email(email):
"""Get customer by email using GSI"""
try:
response = table.query(
IndexName='EmailAddress-index',
KeyConditionExpression='Email = :email',
ExpressionAttributeValues={':email': email}
)
if not response['Items']:
return error_response(404, 'Customer not found')
return success_response(200, {
'customer': response['Items'][0]
})
except Exception as e:
print(f"Error getting customer by email: {str(e)}")
return error_response(500, 'Failed to retrieve customer', str(e))
def get_all_customers(query_params):
"""Get all customers with pagination"""
try:
limit = int(query_params.get('limit', 50))
last_key = query_params.get('lastKey')
scan_kwargs = {
'Limit': limit
}
if last_key:
scan_kwargs['ExclusiveStartKey'] = {'ProfileId': last_key}
response = table.scan(**scan_kwargs)
result = {
'customers': response['Items'],
'count': len(response['Items'])
}
if 'LastEvaluatedKey' in response:
result['lastKey'] = response['LastEvaluatedKey']['ProfileId']
result['hasMore'] = True
else:
result['hasMore'] = False
return success_response(200, result)
except Exception as e:
print(f"Error getting all customers: {str(e)}")
return error_response(500, 'Failed to retrieve customers', str(e))
# ==================== UPDATE ====================
def update_customer(profile_id, event):
"""Update customer profile"""
try:
# Parse body
if isinstance(event.get('body'), str):
body = json.loads(event['body'])
else:
body = event.get('body', {})
if not body:
return error_response(400, 'Request body is required')
# Check if customer exists
existing = table.get_item(Key={'ProfileId': profile_id})
if 'Item' not in existing:
return error_response(404, 'Customer not found')
# Build update expression
update_expression = "SET UpdatedAt = :updated"
expression_values = {':updated': datetime.utcnow().isoformat()}
expression_names = {}
# Updatable fields
updatable_fields = [
'FirstName', 'LastName', 'PhoneNumber', 'Email', 'City',
'State', 'CustomerType', 'LoyaltyTier', 'Address',
'PostalCode', 'Country', 'BirthDate', 'AccountNumber',
'AccountBalance', 'Notes', 'TotalPurchases', 'LastPurchaseDate',
'PreferredLanguage'
]
for field in updatable_fields:
if field in body:
# Handle reserved keywords (State is a DynamoDB reserved word)
if field in ['State']:
field_placeholder = f"#{field}"
expression_names[field_placeholder] = field
update_expression += f", {field_placeholder} = :{field}"
else:
update_expression += f", {field} = :{field}"
expression_values[f":{field}"] = body[field]
# Perform update
update_kwargs = {
'Key': {'ProfileId': profile_id},
'UpdateExpression': update_expression,
'ExpressionAttributeValues': expression_values,
'ReturnValues': 'ALL_NEW'
}
if expression_names:
update_kwargs['ExpressionAttributeNames'] = expression_names
response = table.update_item(**update_kwargs)
print(f"Successfully updated customer: {profile_id}")
return success_response(200, {
'message': 'Customer profile updated successfully',
'customer': response['Attributes']
})
except Exception as e:
print(f"Error updating customer: {str(e)}")
return error_response(500, 'Failed to update customer', str(e))
# ==================== DELETE ====================
def delete_customer(profile_id):
"""Delete customer profile"""
try:
# Check if customer exists
existing = table.get_item(Key={'ProfileId': profile_id})
if 'Item' not in existing:
return error_response(404, 'Customer not found')
# Delete the item
table.delete_item(Key={'ProfileId': profile_id})
print(f"Successfully deleted customer: {profile_id}")
return success_response(200, {
'message': 'Customer profile deleted successfully',
'profileId': profile_id
})
except Exception as e:
print(f"Error deleting customer: {str(e)}")
return error_response(500, 'Failed to delete customer', str(e))
# ==================== HELPER FUNCTIONS ====================
def check_existing_customer(phone_number):
"""Check if customer with phone number exists"""
try:
response = table.query(
IndexName='PhoneNumber-index',
KeyConditionExpression='PhoneNumber = :phone',
ExpressionAttributeValues={':phone': phone_number}
)
return response['Items'][0] if response['Items'] else None
except:
return None
def success_response(status_code, data):
"""Generate success response with CORS headers"""
return {
'statusCode': status_code,
'headers': get_cors_headers(),
'body': json.dumps(data, default=decimal_default)
}
def error_response(status_code, message, details=None, **kwargs):
"""Generate error response with CORS headers"""
error_body = {
'error': message
}
if details:
error_body['details'] = details
# Add any additional fields (like existingProfileId)
error_body.update(kwargs)
return {
'statusCode': status_code,
'headers': get_cors_headers(),
'body': json.dumps(error_body)
}
def get_cors_headers():
"""Return CORS headers"""
return {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS'
}
def decimal_default(obj):
"""JSON serializer for Decimal objects"""
if isinstance(obj, Decimal):
return float(obj)
raise TypeError
Step 7:

Step 8:

Step 9:

Step 10:

Step 11:

Step 12:

Step 13:

Step 14:

Step 15:

Step 16:

Step 17:

Step 18:

Step 19:

Step 20:

Step 21:

Step 22:

Step 23:

Step 24:

Step 25:

Step 26:

Step 27:

Step 28:

Step 29:

Step 30:

Step 31:

Step 32:

Step 33:

Step 34:

Step 35:

Step 36:

Step 37:

Step 38:

Step 39:

Step 40:

Step 41:

Step 42:

Step 43:

DynamoDB Data - JSON
{
"ProfileId": "cust-f8ece72c",
"City": "Chennai",
"CreatedAt": "2025-12-25T07:42:35.572860",
"CustomerType": "Premium",
"Email": "vijay.raghavan@gmail.com",
"FirstName": "Vijay",
"LastName": "Raghavan",
"LoyaltyTier": "Platinum",
"PhoneNumber": "+9199922233312",
"State": "TN",
"UpdatedAt": "2025-12-25T07:42:35.572878"
}
Bucket Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::email-dashboard-2025/*"
}
]
}
Front End Link Click Here
Step 44:

Step 45:

Step 46:

Step 47:

Step 48:

Step 49:

Step 50:

Step 51:

Step 52:


Step 53:

Step 54:

Step 55:

Step 56:

Step 57:

Step 58:

Step 59:

Step 60:

Step 61:

Step 62:

Step 63:

Step 64:

Step 65:

Step 66:

Step 67:

Step 68:

Step 69:

Step 70:

Step 71:

Step 72:

Step 73:

Step 74:

Step 75:

Step 76:

Step 77:

Step 78:

Step 79:

Step 80:

Step 81:

Step 82:

Step 83:

Step 84:

Step 85:

Step 86:

Step 87:

Step 88:

Step 89:

Step 90:

Step 91:

Step 92:

Step 93:

Step 94:

Step 95:

Step 96:

Step 97:

Step 98:

Step 99:

Step 100:

Step 101:

Step 102:

Step 103:

Step 104:

Step 105:

Step 106:

Step 107:

Step 108:

Step 109:

Step 110:

Step 111:

Step 112:

Step 113:

Step 114:

Step 115:

Step 116:

Step 117:

Step 118:

Step 119:

Step 120:

Step 121:

Step 122:

Step 123:

Step 124:

Step 125:

Step 126:

Step 127:

Step 128:

Step 129:

Step 130:

Step 131:

Step 132:

Step 133:

Step 134:

Step 135:

Step 136:

Step 137:

Step 138:

Step 139:

Step 140:

Step 141:

Step :

Step :

ArgoCD Service & HTTPRoute