Serverless Webapp with Security Deployment Steps & Images



Deployment Images

Step 1:


Step 2:


Step 3:


Step 4:


Step 5:


Step 6:


Lambda Code - CustomerProfilesCRUD
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