diff --git a/.DS_Store b/.DS_Store index a273ef7..68e823e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Product_Service2/.DS_Store b/Product_Service2/.DS_Store new file mode 100644 index 0000000..a273ef7 Binary files /dev/null and b/Product_Service2/.DS_Store differ diff --git a/.coverage b/Product_Service2/.coverage similarity index 100% rename from .coverage rename to Product_Service2/.coverage diff --git a/.gitignore b/Product_Service2/.gitignore similarity index 75% rename from .gitignore rename to Product_Service2/.gitignore index 37833f8..f61fe9b 100644 --- a/.gitignore +++ b/Product_Service2/.gitignore @@ -8,3 +8,8 @@ __pycache__ # CDK asset staging directory .cdk.staging cdk.out + +# Enviroment files +.env +*.env +.env.* diff --git a/Product_Service2/README.md b/Product_Service2/README.md new file mode 100644 index 0000000..d89e59f --- /dev/null +++ b/Product_Service2/README.md @@ -0,0 +1,14 @@ +## Environment Variables + +The following environment variables are required: + +- `AWS_REGION`: AWS region (default: us-east-1) +- `PRODUCTS_TABLE_NAME`: DynamoDB products table name +- `STOCKS_TABLE_NAME`: DynamoDB stocks table name +- `AWS_ACCESS_KEY_ID`: AWS access key +- `AWS_SECRET_ACCESS_KEY`: AWS secret access key + +Copy `.env.example` to `.env` and fill in your values: + +```bash +cp .env.example .env diff --git a/app.py b/Product_Service2/app.py similarity index 100% rename from app.py rename to Product_Service2/app.py diff --git a/cdk.json b/Product_Service2/cdk.json similarity index 100% rename from cdk.json rename to Product_Service2/cdk.json diff --git a/openapi.yaml b/Product_Service2/openapi.yaml similarity index 100% rename from openapi.yaml rename to Product_Service2/openapi.yaml diff --git a/product_service/__init__.py b/Product_Service2/product_service/__init__.py similarity index 100% rename from product_service/__init__.py rename to Product_Service2/product_service/__init__.py diff --git a/Product_Service2/product_service/api_gateway_stack.py b/Product_Service2/product_service/api_gateway_stack.py new file mode 100644 index 0000000..4194b8f --- /dev/null +++ b/Product_Service2/product_service/api_gateway_stack.py @@ -0,0 +1,239 @@ +""" from aws_cdk import ( + Stack, + aws_apigateway as apigw, + CfnOutput +) +from constructs import Construct + +class ApiGatewayStack(Stack): + def __init__(self, scope: Construct, construct_id: str, lambda_stack, **kwargs) -> None: + super().__init__(scope, construct_id, **kwargs) + + # Create API Gateway with CORS + api = apigw.RestApi( + self, 'ProductsApi', + rest_api_name='Products Service', + default_cors_preflight_options=apigw.CorsOptions( + allow_origins=apigw.Cors.ALL_ORIGINS, + allow_methods=apigw.Cors.ALL_METHODS, + allow_headers=[ + 'Content-Type', + 'X-Amz-Date', + 'Authorization', + 'X-Api-Key', + 'X-Amz-Security-Token', + ], + ) + ) + + # Create API resources and methods + products = api.root.add_resource('products') + products.add_method( + 'GET', + apigw.LambdaIntegration( + lambda_stack.list_products_function, + proxy=True, + integration_responses=[{ + 'statusCode': '200', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': "'*'" + } + }] + ), + method_responses=[{ + 'statusCode': '200', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': True + } + }] + ) + + product = products.add_resource('{productId}') + product.add_method( + 'GET', + apigw.LambdaIntegration( + lambda_stack.get_product_function, + proxy=True, + integration_responses=[{ + 'statusCode': '200', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': "'*'" + } + }] + ), + method_responses=[{ + 'statusCode': '200', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': True + } + }] + ) + + # Output the API URL + CfnOutput( + self, "APIGatewayURL", + value=f"{api.url}products", + description="API Gateway endpoint URL" + ) + """ +from aws_cdk import ( + Stack, + aws_apigateway as apigw, + CfnOutput + ) +from constructs import Construct + +class ApiGatewayStack(Stack): + def __init__(self, scope: Construct, construct_id: str, lambda_stack, **kwargs) -> None: + super().__init__(scope, construct_id, **kwargs) + + # Create API Gateway with CORS + api = apigw.RestApi( + self, 'ProductsApi', + rest_api_name='Products Service', + default_cors_preflight_options=apigw.CorsOptions( + allow_origins=apigw.Cors.ALL_ORIGINS, + allow_methods=apigw.Cors.ALL_METHODS, + allow_headers=[ + 'Content-Type', + 'X-Amz-Date', + 'Authorization', + 'X-Api-Key', + 'X-Amz-Security-Token', + ], + ) + ) + + # Common response configurations + cors_response_parameters = { + 'method.response.header.Access-Control-Allow-Origin': "'*'" + } + + success_response = { + 'statusCode': '200', + 'responseParameters': cors_response_parameters + } + + error_responses = [ + { + 'statusCode': '400', + 'responseParameters': cors_response_parameters + }, + { + 'statusCode': '500', + 'responseParameters': cors_response_parameters + } + ] + + method_responses = [ + { + 'statusCode': '200', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': True + } + }, + { + 'statusCode': '400', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': True + } + }, + { + 'statusCode': '500', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': True + } + } + ] + + # Create API resources and methods + products = api.root.add_resource('products') + + # GET /products + products.add_method( + 'GET', + apigw.LambdaIntegration( + lambda_stack.list_products_function, + proxy=True, + integration_responses=[success_response, *error_responses] + ), + method_responses=method_responses + ) + + # POST /products + products.add_method( + 'POST', + apigw.LambdaIntegration( + lambda_stack.product_function, + proxy=True, + integration_responses=[ + { + 'statusCode': '201', + 'responseParameters': cors_response_parameters + }, + *error_responses + ] + ), + method_responses=[ + { + 'statusCode': '201', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': True + } + }, + *method_responses[1:] # Include 400 and 500 responses + ] + ) + + # GET /products/{productId} + product = products.add_resource('{productId}') + product.add_method( + 'GET', + apigw.LambdaIntegration( + lambda_stack.get_product_function, + proxy=True, + integration_responses=[ + success_response, + { + 'statusCode': '404', + 'responseParameters': cors_response_parameters + }, + *error_responses + ] + ), + method_responses=[ + *method_responses, + { + 'statusCode': '404', + 'responseParameters': { + 'method.response.header.Access-Control-Allow-Origin': True + } + } + ] + ) + + # Output the API URL + CfnOutput( + self, "APIGatewayURL", + value=f"{api.url}products", + description="API Gateway endpoint URL" + ) + + # Output separate endpoints for each operation + CfnOutput( + self, "GetProductsURL", + value=f"{api.url}products", + description="GET products endpoint URL" + ) + + CfnOutput( + self, "CreateProductURL", + value=f"{api.url}products", + description="POST product endpoint URL" + ) + + CfnOutput( + self, "GetProductByIdURL", + value=f"{api.url}products/{{productId}}", + description="GET product by ID endpoint URL" + ) diff --git a/product_service/get_product_by_id.py b/Product_Service2/product_service/get_product_by_id.py similarity index 64% rename from product_service/get_product_by_id.py rename to Product_Service2/product_service/get_product_by_id.py index dad3e49..fd24cd4 100644 --- a/product_service/get_product_by_id.py +++ b/Product_Service2/product_service/get_product_by_id.py @@ -3,11 +3,13 @@ ) from constructs import Construct -def create_get_product_lambda(scope: Construct, id: str) -> _lambda.Function: +def create_get_product_lambda(scope: Construct, + id: str, environment: dict, role: None) -> _lambda.Function: return _lambda.Function( scope, id, runtime=_lambda.Runtime.PYTHON_3_9, handler='product_by_id.handler', - code=_lambda.Code.from_asset('product_service/lambda_func') + code=_lambda.Code.from_asset('product_service/lambda_func'), + environment=environment or {} ) diff --git a/product_service/get_products.py b/Product_Service2/product_service/get_products.py similarity index 65% rename from product_service/get_products.py rename to Product_Service2/product_service/get_products.py index ec81065..5855c7c 100644 --- a/product_service/get_products.py +++ b/Product_Service2/product_service/get_products.py @@ -3,11 +3,12 @@ ) from constructs import Construct -def create_list_products_lambda(scope: Construct, id: str) -> _lambda.Function: +def create_list_products_lambda(scope: Construct, id: str, environment: dict, role: None) -> _lambda.Function: return _lambda.Function( scope, id, runtime=_lambda.Runtime.PYTHON_3_9, handler='product_list.handler', - code=_lambda.Code.from_asset('product_service/lambda_func') + code=_lambda.Code.from_asset('product_service/lambda_func'), + environment=environment or {} ) diff --git a/product_service/lambda_func/__init__.py b/Product_Service2/product_service/lambda_func/__init__.py similarity index 100% rename from product_service/lambda_func/__init__.py rename to Product_Service2/product_service/lambda_func/__init__.py diff --git a/Product_Service2/product_service/lambda_func/create_product.py b/Product_Service2/product_service/lambda_func/create_product.py new file mode 100644 index 0000000..90c6cc3 --- /dev/null +++ b/Product_Service2/product_service/lambda_func/create_product.py @@ -0,0 +1,123 @@ +import json +import os +import boto3 +import uuid +from decimal import Decimal +from typing import Dict, Any + +class DecimalEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, Decimal): + return float(obj) + return super(DecimalEncoder, self).default(obj) + +def create_response(status_code: int, body: Any) -> Dict[str, Any]: + return { + 'statusCode': status_code, + 'headers': { + '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': 'OPTIONS,POST' + }, + 'body': json.dumps(body, cls=DecimalEncoder) + } + +def validate_product_data(data: Dict) -> tuple[bool, str]: + """Validate product data""" + if not data: + return False, "Request body is empty" + + required_fields = ['title', 'description', 'price', 'count'] + for field in required_fields: + if field not in data: + return False, f"Missing required field: {field}" + + if not isinstance(data['price'], (int, float)) or data['price'] <= 0: + return False, "Price must be a positive number" + + if not isinstance(data['count'], int) or data['count'] < 0: + return False, "Count must be a non-negative integer" + + return True, "" + + +def handler(event, context): + try: + print(f"Received event: {event}") + print(f"Context: RequestId: {context.aws_request_id}") + + # Parse request body + body = event.get('body', '{}') + if isinstance(body, str): + body = json.loads(body) + + print(f"Validating product data: {body}") + + # Validate request data + is_valid, error_message = validate_product_data(body) + if not is_valid: + print(f"Validation failed: {error_message}") + return create_response(400, {'message': error_message}) + + # Generate unique ID for the product + product_id = str(uuid.uuid4()) + + # Prepare product data + product_data = { + 'id': product_id, + 'title': body['title'], + 'description': body['description'], + 'price': Decimal(str(body['price'])) + } + + stock_data = { + 'product_id': product_id, + 'count': body['count'] + } + + # Initialize DynamoDB + dynamodb = boto3.resource('dynamodb', region_name=os.environ['REGION']) + + # Create transaction items + transaction_items = [ + { + 'Put': { + 'TableName': os.environ['PRODUCTS_TABLE_NAME'], + 'Item': product_data, + 'ConditionExpression': 'attribute_not_exists(id)' + } + }, + { + 'Put': { + 'TableName': os.environ['STOCKS_TABLE_NAME'], + 'Item': stock_data, + 'ConditionExpression': 'attribute_not_exists(product_id)' + } + } + ] + + # Execute transaction + print(f"Executing transaction for product: {product_id}") + dynamodb.meta.client.transact_write_items( + TransactItems=transaction_items + ) + + # Combine product and stock data for response + response_data = {**product_data, 'count': stock_data['count']} + + print(f"Successfully created product with ID: {product_id}") + return create_response(201, { + 'message': 'Product created successfully', + 'product': response_data + }) + + except dynamodb.meta.client.exceptions.TransactionCanceledException as e: + print(f"Transaction cancelled: {str(e)}") + return create_response(400, {'message': 'Failed to create product - transaction cancelled'}) + except json.JSONDecodeError as e: + print(f"JSON decode error: {str(e)}") + return create_response(400, {'message': 'Invalid JSON in request body'}) + except Exception as e: + print(f"Error: {str(e)}") + return create_response(500, {'message': f'Internal server error: {str(e)}'}) diff --git a/Product_Service2/product_service/lambda_func/product_by_id.py b/Product_Service2/product_service/lambda_func/product_by_id.py new file mode 100644 index 0000000..1b6e84e --- /dev/null +++ b/Product_Service2/product_service/lambda_func/product_by_id.py @@ -0,0 +1,71 @@ +import json +import os +import boto3 +from decimal import Decimal +from typing import Dict, Any, Optional + +class DecimalEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, Decimal): + return float(obj) + return super(DecimalEncoder, self).default(obj) + +def get_product_by_id(dynamodb, product_id: str) -> Optional[Dict[str, Any]]: + """Get product from DynamoDB by ID""" + table = dynamodb.Table(os.environ['PRODUCTS_TABLE_NAME']) + response = table.get_item( + Key={'id': product_id} + ) + return response.get('Item') + +def get_stock_by_product_id(dynamodb, product_id: str) -> Optional[Dict[str, Any]]: + """Get stock information from DynamoDB by product ID""" + table = dynamodb.Table(os.environ['STOCKS_TABLE_NAME']) + response = table.get_item( + Key={'product_id': product_id} + ) + return response.get('Item') + +def create_response(status_code: int, body: Any) -> Dict[str, Any]: + """Create API Gateway response""" + return { + 'statusCode': status_code, + 'headers': { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + }, + 'body': json.dumps(body, cls=DecimalEncoder) + } + +def handler(event, context): + try: + # Log the incoming event + print(f"Event: {json.dumps(event)}") + + # Get product ID from path parameters + product_id = event.get('pathParameters', {}).get('productId') + + if not product_id: + return create_response(400, {'message': 'Product ID is required'}) + + # Initialize DynamoDB client + dynamodb = boto3.resource('dynamodb', region_name=os.environ['REGION']) + + # Get product details + product = get_product_by_id(dynamodb, product_id) + + if not product: + return create_response(404, {'message': 'Product not found'}) + + # Get stock information + stock = get_stock_by_product_id(dynamodb, product_id) + + # Add stock count to product + product['count'] = stock['count'] if stock else 0 + + # Return successful response + return create_response(200, product) + + except Exception as e: + print(f"Error: {str(e)}") # Log the error + return create_response(500, {'message': f'Internal server error: {str(e)}'}) diff --git a/Product_Service2/product_service/lambda_func/product_list.py b/Product_Service2/product_service/lambda_func/product_list.py new file mode 100644 index 0000000..5e58de8 --- /dev/null +++ b/Product_Service2/product_service/lambda_func/product_list.py @@ -0,0 +1,67 @@ +import json +import os +import boto3 +from decimal import Decimal +from typing import Dict, List, Any + +# Custom JSON encoder for Decimal types +class DecimalEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, Decimal): + return float(obj) + return super(DecimalEncoder, self).default(obj) + +def get_products(dynamodb) -> List[Dict[str, Any]]: + """Get all products from DynamoDB""" + table = dynamodb.Table(os.environ['PRODUCTS_TABLE_NAME']) + response = table.scan() + return response.get('Items', []) + +def get_stocks(dynamodb) -> List[Dict[str, Any]]: + """Get all stocks from DynamoDB""" + table = dynamodb.Table(os.environ['STOCKS_TABLE_NAME']) + response = table.scan() + return response.get('Items', []) + +def join_products_with_stocks(products: List[Dict], stocks: List[Dict]) -> List[Dict]: + """Join products with their stock information""" + stocks_by_id = {stock['product_id']: stock['count'] for stock in stocks} + + for product in products: + product['count'] = stocks_by_id.get(product['id'], 0) + + return products + +def create_response(status_code: int, body: Any) -> Dict[str, Any]: + """Create API Gateway response""" + return { + 'statusCode': status_code, + 'headers': { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + }, + 'body': json.dumps(body, cls=DecimalEncoder) # Used DecimalEncoder here + } + +def handler(event, context): + try: + + # Simple log for incoming request + print(f"Incoming request: {json.dumps(event)}") + print(f"Using DynamoDB table: {os.environ['PRODUCTS_TABLE_NAME']}") + # Initialize DynamoDB client + dynamodb = boto3.resource('dynamodb', region_name=os.environ['REGION']) + + # Get products and stocks + products = get_products(dynamodb) + stocks = get_stocks(dynamodb) + + # Join products with stocks + joined_products = join_products_with_stocks(products, stocks) + + # Return successful response + return create_response(200, joined_products) + + except Exception as e: + print(f"Error: {str(e)}") # Log the error + return create_response(500, {'message': f'Internal server error: {str(e)}'}) diff --git a/product_service/lambda_func/products_mock.py b/Product_Service2/product_service/lambda_func/products_mock.py similarity index 100% rename from product_service/lambda_func/products_mock.py rename to Product_Service2/product_service/lambda_func/products_mock.py diff --git a/Product_Service2/product_service/lambda_stack.py b/Product_Service2/product_service/lambda_stack.py new file mode 100644 index 0000000..779a4e3 --- /dev/null +++ b/Product_Service2/product_service/lambda_stack.py @@ -0,0 +1,108 @@ +from aws_cdk import ( + Stack, + aws_lambda as _lambda, + aws_iam as iam, + aws_dynamodb as dynamodb, +) +from constructs import Construct +from product_service.get_products import create_list_products_lambda +from product_service.get_product_by_id import create_get_product_lambda +from product_service.post_create_product import create_product_lambda + +class LambdaStack(Stack): + def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: + super().__init__(scope, construct_id, **kwargs) + + #Referencing DynamoDB + products_table = dynamodb.Table.from_table_name( + self, + 'ProductsTable', + table_name='products' + ) + + stocks_table = dynamodb.Table.from_table_name( + self, + 'StocksTable', + table_name='stocks' + ) + + print(f"Products Table Name: {products_table.table_name}") + print(f"Stocks Table Name: {stocks_table.table_name}") + + # Create Lambda role with explicit permissions + lambda_role = iam.Role( + self, 'ProductsLambdaRole', + assumed_by=iam.ServicePrincipal('lambda.amazonaws.com') + ) + + # Add CloudWatch Logs permissions + lambda_role.add_managed_policy( + iam.ManagedPolicy.from_aws_managed_policy_name( + 'service-role/AWSLambdaBasicExecutionRole' + ) + ) + + # Add DynamoDB permissions + lambda_role.add_to_policy( + iam.PolicyStatement( + actions=[ + 'dynamodb:Scan', + 'dynamodb:GetItem', + 'dynamodb:Query' + ], + resources=[ + products_table.table_arn, + stocks_table.table_arn + ] + ) + ) + #Creating Lambda functions + common_env = { + 'PRODUCTS_TABLE_NAME': products_table.table_name, + 'STOCKS_TABLE_NAME': stocks_table.table_name, + 'REGION' : Stack.of(self).region + } + + self.product_lambda = create_product_lambda( + self, + 'CreateProductFunction', + environment = common_env, + role = lambda_role + ) + + self.list_products_lambda = create_list_products_lambda( + self, + 'GetProductsFunction', + environment = common_env, + role = lambda_role + ) + + self.get_product_lambda = create_get_product_lambda( + self, + 'GetProductByIdFunction', + environment= common_env, + role = lambda_role + ) + + # Granting permissions to Lambda functions + products_table.grant_read_data(self.list_products_lambda) + stocks_table.grant_read_data(self.list_products_lambda) + products_table.grant_read_data(self.get_product_lambda) + stocks_table.grant_read_data(self.get_product_lambda) + products_table.grant_write_data(self.product_lambda) + stocks_table.grant_write_data(self.product_lambda) + + @property + def list_products_function(self): + return self.list_products_lambda + + @property + def get_product_function(self): + return self.get_product_lambda + + @property + def product_function(self): + return self.product_lambda + + + diff --git a/Product_Service2/product_service/populate_dynamoDB.py b/Product_Service2/product_service/populate_dynamoDB.py new file mode 100644 index 0000000..85cbbbd --- /dev/null +++ b/Product_Service2/product_service/populate_dynamoDB.py @@ -0,0 +1,99 @@ +import boto3 +import uuid +from botocore.exceptions import ClientError +from typing import List, Dict +import os +from dotenv import load_dotenv + +#Load enviroment variable from .env file +load_dotenv() + +# Get environment variables with fallback values +REGION = os.getenv('AWS_REGION', 'us-east-1') +PRODUCTS_TABLE = os.getenv('PRODUCTS_TABLE_NAME', 'products') +STOCKS_TABLE = os.getenv('STOCKS_TABLE_NAME', 'stocks') + +def get_dynamodb_resource(): + """Initialize DynamoDB resource with credentials from environment""" + return boto3.resource('dynamodb', region_name=REGION) + +def verify_tables(tables: List[str], dynamodb) -> bool: + """Verify if all required tables exist""" + existing_tables = list(dynamodb.tables.all()) + existing_table_names = [table.name for table in existing_tables] + + for table in tables: + if table not in existing_table_names: + print(f"āŒ Table {table} does not exist") + return False + return True + +def create_products_with_transactions(dynamodb, products): + """Create products and stocks using transactions""" + try: + for product in products: + product_id = str(uuid.uuid4()) + + # Create transaction items + transaction_items = [ + { + 'Put': { + 'TableName': PRODUCTS_TABLE, + 'Item': { + 'id': product_id, + 'title': product['title'], + 'description': product['description'], + 'price': product['price'] + } + } + }, + { + 'Put': { + 'TableName': STOCKS_TABLE, + 'Item': { + 'product_id': product_id, + 'count': 10 + } + } + } + ] + + # Execute transaction + dynamodb.meta.client.transact_write_items( + TransactItems=transaction_items + ) + print(f"āœ… Created product and stock for {product['title']}") + + except ClientError as e: + if e.response['Error']['Code'] == 'TransactionCanceledException': + print("āŒ Transaction cancelled:", e.response['CancellationReasons']) + raise + +def populate_tables(): + try: + dynamodb = get_dynamodb_resource() + + # Verify tables exist + if not verify_tables([PRODUCTS_TABLE, STOCKS_TABLE], dynamodb): + return + + # Sample product data + products = [ + {"title": "Vegan Protein Powder", "description": "Organic plant-based protein.", "price": 29}, + {"title": "Almond Butter", "description": "Smooth and natural almond butter.", "price": 12}, + {"title": "Quinoa Pasta", "description": "Gluten-free quinoa pasta.", "price": 8} + ] + + # Create products using transactions + create_products_with_transactions(dynamodb, products) + print("\nšŸŽ‰ All data inserted successfully!") + + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f"āŒ AWS Error ({error_code}): {error_message}") + except Exception as e: + print(f"āŒ Unexpected error: {str(e)}") + +if __name__ == "__main__": + populate_tables() diff --git a/Product_Service2/product_service/post_create_product.py b/Product_Service2/product_service/post_create_product.py new file mode 100644 index 0000000..66f50d2 --- /dev/null +++ b/Product_Service2/product_service/post_create_product.py @@ -0,0 +1,27 @@ +from aws_cdk import ( + aws_lambda as _lambda, + Stack +) +from constructs import Construct +from typing import Dict + +def create_product_lambda( + scope: Construct, id: str,environment: dict, role: None) -> _lambda.Function: + """ + Create the createProduct Lambda function + Args: + scope: CDK Construct scope + id: Construct ID + products_table_name: Name of the products table + stocks_table_name: Name of the stocks table + Returns: + _lambda.Function: The created Lambda function + """ + return _lambda.Function( + scope, + id, + runtime=_lambda.Runtime.PYTHON_3_9, + handler='create_product.handler', + code=_lambda.Code.from_asset('product_service/lambda_func'), + environment = environment or {} + ) diff --git a/product_service/product_service_stack.py b/Product_Service2/product_service/product_service_stack.py similarity index 100% rename from product_service/product_service_stack.py rename to Product_Service2/product_service/product_service_stack.py diff --git a/pytest.ini b/Product_Service2/pytest.ini similarity index 100% rename from pytest.ini rename to Product_Service2/pytest.ini diff --git a/requirements-dev.txt b/Product_Service2/requirements-dev.txt similarity index 100% rename from requirements-dev.txt rename to Product_Service2/requirements-dev.txt diff --git a/requirements.txt b/Product_Service2/requirements.txt similarity index 61% rename from requirements.txt rename to Product_Service2/requirements.txt index 4de807b..c7f5618 100644 --- a/requirements.txt +++ b/Product_Service2/requirements.txt @@ -1,7 +1,8 @@ -aws-cdk-lib==2.178.1 +aws-cdk-lib~=2.0.0 constructs>=10.0.0,<11.0.0 pytest>=6.0 pytest-cov # aws-cdk.aws-lambda # aws-cdk.aws-apigateway -# boto3 \ No newline at end of file +aws-lambda-powertools>=2.0.0 +boto3>=1.26.0 \ No newline at end of file diff --git a/source.bat b/Product_Service2/source.bat similarity index 100% rename from source.bat rename to Product_Service2/source.bat diff --git a/tests/__init__.py b/Product_Service2/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to Product_Service2/tests/__init__.py diff --git a/tests/conftest.py b/Product_Service2/tests/conftest.py similarity index 100% rename from tests/conftest.py rename to Product_Service2/tests/conftest.py diff --git a/tests/unit/__init__.py b/Product_Service2/tests/unit/__init__.py similarity index 100% rename from tests/unit/__init__.py rename to Product_Service2/tests/unit/__init__.py diff --git a/tests/unit/lambda_test/test_product_functions.py b/Product_Service2/tests/unit/lambda_test/test_product_functions.py similarity index 100% rename from tests/unit/lambda_test/test_product_functions.py rename to Product_Service2/tests/unit/lambda_test/test_product_functions.py diff --git a/tests/unit/test_product_servie_stack.py b/Product_Service2/tests/unit/test_product_servie_stack.py similarity index 100% rename from tests/unit/test_product_servie_stack.py rename to Product_Service2/tests/unit/test_product_servie_stack.py diff --git a/README.md b/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/product_service/api_gateway_stack.py b/product_service/api_gateway_stack.py deleted file mode 100644 index 1d813ca..0000000 --- a/product_service/api_gateway_stack.py +++ /dev/null @@ -1,77 +0,0 @@ -from aws_cdk import ( - Stack, - aws_apigateway as apigw, - CfnOutput -) -from constructs import Construct - -class ApiGatewayStack(Stack): - def __init__(self, scope: Construct, construct_id: str, lambda_stack, **kwargs) -> None: - super().__init__(scope, construct_id, **kwargs) - - # Create API Gateway with CORS - api = apigw.RestApi( - self, 'ProductsApi', - rest_api_name='Products Service', - default_cors_preflight_options=apigw.CorsOptions( - allow_origins=apigw.Cors.ALL_ORIGINS, - allow_methods=apigw.Cors.ALL_METHODS, - allow_headers=[ - 'Content-Type', - 'X-Amz-Date', - 'Authorization', - 'X-Api-Key', - 'X-Amz-Security-Token', - ], - ) - ) - - # Create API resources and methods - products = api.root.add_resource('products') - products.add_method( - 'GET', - apigw.LambdaIntegration( - lambda_stack.list_products_function, - proxy=True, - integration_responses=[{ - 'statusCode': '200', - 'responseParameters': { - 'method.response.header.Access-Control-Allow-Origin': "'*'" - } - }] - ), - method_responses=[{ - 'statusCode': '200', - 'responseParameters': { - 'method.response.header.Access-Control-Allow-Origin': True - } - }] - ) - - product = products.add_resource('{productId}') - product.add_method( - 'GET', - apigw.LambdaIntegration( - lambda_stack.get_product_function, - proxy=True, - integration_responses=[{ - 'statusCode': '200', - 'responseParameters': { - 'method.response.header.Access-Control-Allow-Origin': "'*'" - } - }] - ), - method_responses=[{ - 'statusCode': '200', - 'responseParameters': { - 'method.response.header.Access-Control-Allow-Origin': True - } - }] - ) - - # Output the API URL - CfnOutput( - self, "APIGatewayURL", - value=f"{api.url}products", - description="API Gateway endpoint URL" - ) diff --git a/product_service/lambda_func/product_by_id.py b/product_service/lambda_func/product_by_id.py deleted file mode 100644 index 0a545c7..0000000 --- a/product_service/lambda_func/product_by_id.py +++ /dev/null @@ -1,30 +0,0 @@ -from product_service.lambda_func.products_mock import products -import json - -def handler(event, context): - product_id = event['pathParameters']['productId'] - - product = next( - (item for item in products if item["id"] == product_id), - None - ) - - headers = { - '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': 'OPTIONS,GET' - } - - if not product: - return { - 'statusCode': 404, - 'headers': headers, - 'body': json.dumps({'message': 'Product not found'}) - } - - return { - 'statusCode': 200, - 'headers': headers, - 'body': json.dumps(product) - } diff --git a/product_service/lambda_func/product_list.py b/product_service/lambda_func/product_list.py deleted file mode 100644 index f796c46..0000000 --- a/product_service/lambda_func/product_list.py +++ /dev/null @@ -1,14 +0,0 @@ -from product_service.lambda_func.products_mock import products -import json - -def handler(event, context): - return { - 'statusCode': 200, - 'headers': { - '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': 'OPTIONS,GET' - }, - 'body': json.dumps(products) - } diff --git a/product_service/lambda_stack.py b/product_service/lambda_stack.py deleted file mode 100644 index c07311c..0000000 --- a/product_service/lambda_stack.py +++ /dev/null @@ -1,26 +0,0 @@ -from aws_cdk import Stack -from constructs import Construct -from product_service.get_products import create_list_products_lambda -from product_service.get_product_by_id import create_get_product_lambda - -class LambdaStack(Stack): - def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: - super().__init__(scope, construct_id, **kwargs) - - self.list_products_lambda = create_list_products_lambda( - self, - 'GetProductsFunction' - ) - - self.get_product_lambda = create_get_product_lambda( - self, - 'GetProductByIdFunction' - ) - - @property - def list_products_function(self): - return self.list_products_lambda - - @property - def get_product_function(self): - return self.get_product_lambda