Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
402bc6e
Initial commit
vic-cortes Nov 6, 2025
8538bce
Adding uv.lock
vic-cortes Nov 6, 2025
c9e7ece
feat: add CSV ignore rule and enhance build tooling
vic-cortes Nov 6, 2025
a8b0c59
feat: add lambda status check and switch to uv for pip install
vic-cortes Nov 6, 2025
a628ad4
feat: add boto3 dependency and improve HTTP status handling
vic-cortes Nov 6, 2025
6a96bcb
feat: add SubscriptionTable class and refactor handler response
vic-cortes Nov 6, 2025
1ef9dbf
feat: add pydantic dependency for data validation
vic-cortes Nov 6, 2025
2c5567b
feat(db): add write method to DynamoFender and migrate schemas to Pyd…
vic-cortes Nov 6, 2025
4bc6e11
fix: update import path and add debug logging to handler
vic-cortes Nov 6, 2025
2e0b5e7
feat: move boto3 to dev dependencies and add ipython
vic-cortes Nov 6, 2025
cf410ef
feat: add subscription event schemas and type definitions
vic-cortes Nov 7, 2025
7bbaba4
build: add pytest to dev dependencies
vic-cortes Nov 7, 2025
703e725
feat(schemas): simplify PlanSchema status field and add subscription …
vic-cortes Nov 7, 2025
d6b211c
feat: add API Gateway event sample and improve response formatting
vic-cortes Nov 7, 2025
550ba30
feat: add HTTP method support and event schema validation
vic-cortes Nov 7, 2025
4e2b23a
feat: improve schema type safety and add event method validation
vic-cortes Nov 7, 2025
48b86e2
feat: enhance subscription status management with cancellation logic
vic-cortes Nov 7, 2025
6b03b45
feat: add PlanTable DynamoDB handler and enhance schema definitions
vic-cortes Nov 7, 2025
de004f5
refactor: enhance EventSchema and rename SubscriptionEvent model
vic-cortes Nov 7, 2025
74a1518
feat: implement router-based request handling in Lambda function
vic-cortes Nov 7, 2025
864cf78
refactor: move success_response import to routes module and implement…
vic-cortes Nov 7, 2025
9cf8d70
feat: add SubscriptionPlanModel and import SubscriptionEventPayload
vic-cortes Nov 7, 2025
2c2ad09
refactor: standardize AWS credential variable names and improve model…
vic-cortes Nov 8, 2025
8fa3ac1
feat: refactor DynamoDB models and enhance query capabilities
vic-cortes Nov 8, 2025
b96aa64
feat(models): implement subscription creation and processing logic
vic-cortes Nov 8, 2025
cb4759e
refactor: simplify SubscriptionAdapter and remove unused Subscription…
vic-cortes Nov 8, 2025
1f872ca
feat(models): implement PlanAdapter with faker data generation
vic-cortes Nov 8, 2025
82cd2c3
feat: enhance subscription and plan processing with validation and da…
vic-cortes Nov 8, 2025
f04b546
refactor: add validation wrapper and improve model methods
vic-cortes Nov 9, 2025
64bf1ce
feat: add DynamoDB float serialization and improve response handling
vic-cortes Nov 9, 2025
9544316
feat: refactor subscription handling and fix table reference
vic-cortes Nov 9, 2025
5354acd
refactor: improve subscription/plan adapters and add user retrieval e…
vic-cortes Nov 9, 2025
3f8e031
feat(models): refactor subscription status logic and enhance data models
vic-cortes Nov 9, 2025
f1e4a33
feat: improve process_user_id to return structured response with data
vic-cortes Nov 9, 2025
a9373e2
feat: add faker dependency and fix import compatibility for AWS Lambda
vic-cortes Nov 9, 2025
16c362d
feat(deploy): add pydantic v2 deployment guidance and uv usage
vic-cortes Nov 9, 2025
33e468c
refactor: improve subscription processing and error handling
vic-cortes Nov 9, 2025
77f1491
feat(subscription): add update functionality and refactor event proce…
vic-cortes Nov 10, 2025
5990515
feat: add sort key support and status tracking to DynamoDB operations
vic-cortes Nov 10, 2025
52ee440
fix: correct subscription status logic and improve error handling
vic-cortes Nov 10, 2025
781cb45
fix: change body from dict to string and add parse_body method
vic-cortes Nov 11, 2025
5205269
feat(db): add SubscriptionsAndPlansTable for combined data access
vic-cortes Nov 11, 2025
5fece21
refactor(models): update SubscriptionAdapter to use new table and key…
vic-cortes Nov 11, 2025
ddb4041
refactor: update subscription and plan models with unified table stru…
vic-cortes Nov 11, 2025
957f841
feat: refactor plan primary key format and table references
vic-cortes Nov 11, 2025
ab695a6
feat: extract subscription data transformation into separate schema c…
vic-cortes Nov 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ node_modules/
.env

vendor/
.idea/
.idea/
*.csv
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
18 changes: 18 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "[PYTEST] Debugger",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["app/python/src/tests/main.py"],
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"purpose": ["debug-test"]
}
]
}
19 changes: 18 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

deploy-env:
scripts/deploy-env.sh > /dev/null

Expand All @@ -8,4 +9,20 @@ deploy-python:
scripts/deploy-python.sh > /dev/null

deploy-node:
scripts/deploy-node.sh > /dev/null
scripts/deploy-node.sh > /dev/null

set_env_variables:
@echo "Setting AWS Credentials..."
export $(grep -v '^#' .env)

# Python only
export_serverless_requirements:
@echo "Exporting requirements..."
uv pip compile pyproject.toml -o app/python/requirements.txt

check_lambda_status:
@echo "Checking Lambda function status..."
aws lambda get-function-configuration \
--profile "fender" \
--function-name fender_digital_code_exercise \
--query '{State:State, LastUpdateStatus:LastUpdateStatus, LastUpdateStatusReason:LastUpdateStatusReason}'
22 changes: 21 additions & 1 deletion app/python/requirements.txt
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
boto3
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml -o app/python/requirements.txt
annotated-types==0.7.0
# via pydantic
faker==37.12.0
# via fds-aws-coding-exercise (pyproject.toml)
pydantic==2.12.4
# via fds-aws-coding-exercise (pyproject.toml)
pydantic-core==2.41.5
# via pydantic
python-dotenv==1.2.1
# via fds-aws-coding-exercise (pyproject.toml)
typing-extensions==4.15.0
# via
# pydantic
# pydantic-core
# typing-inspection
typing-inspection==0.4.2
# via pydantic
tzdata==2025.2
# via faker
Empty file added app/python/src/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions app/python/src/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os

from dotenv import load_dotenv

load_dotenv(override=True)


class Config:
ENVIRONMENT = os.getenv("ENVIRONMENT", "staging")
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID", "")
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY", "")
AWS_REGION_NAME = os.getenv("AWS_REGION_NAME", "us-east-1")


IS_DEVELOPMENT = Config.ENVIRONMENT == "development"
163 changes: 163 additions & 0 deletions app/python/src/db/dynamo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import json
from decimal import Decimal

import boto3
from boto3.dynamodb.conditions import And, Key

try:
# For local development
from ..config import IS_DEVELOPMENT, Config
except:
from config import IS_DEVELOPMENT, Config


def serialize_dynamo(dict):
return json.loads(json.dumps(dict, default=str_dynamo_data))


def str_dynamo_data(obj):
# Coerce every object into string
return str(obj)


def dynamo_write_serializer(dict: dict) -> dict:
"""
Serialize data before writing into DynamoDB
"""
for key, value in dict.items():
if isinstance(value, float):
dict[key] = Decimal(str(value))

return dict


PK_FIELD = "pk"
SK_FIELD = "sk"


class DynamoFender:
"""
DynamoDB connection handler for Fender application.
"""

def __init__(self, tablename) -> None:
if IS_DEVELOPMENT:
auth_params = {
"aws_access_key_id": Config.AWS_ACCESS_KEY_ID,
"aws_secret_access_key": Config.AWS_SECRET_ACCESS_KEY,
"region_name": Config.AWS_REGION_NAME,
}
self.dynamodb = boto3.resource("dynamodb", **auth_params)
self.client = boto3.client("dynamodb", **auth_params)
else:
self.dynamodb = boto3.resource("dynamodb")
self.client = boto3.client("dynamodb")
try:
self.table = self.dynamodb.Table(tablename)
except Exception as error:
raise ValueError(
f"Could't make connection to `{tablename}` table due `{error}`"
)

def write(self, data: list | dict) -> bool:
"""
Writes data into table
"""

if isinstance(data, dict):
data = [data]

with self.table.batch_writer() as batch:
for values in data:
serialized_values = dynamo_write_serializer(values)
batch.put_item(Item=serialized_values)

return True

def _convert_updatable_dict(self, payload: dict) -> dict:
"""
Convert payload to corresponding format to update
"""

final_dict = {}

for key, value in payload.items():
if key == PK_FIELD or key == SK_FIELD:
continue

final_dict.update({key: {"Value": value}})

return final_dict

def update(self, data: dict) -> None:
"""
Updates DB in dynamo
"""
pk_value = data.get(PK_FIELD)
sk_value = data.get(SK_FIELD)

if PK_FIELD not in data.keys() or not pk_value:
raise ValueError(f"`pk` is mandatory")

if SK_FIELD not in data.keys() or not sk_value:
raise ValueError(f"`sk` is mandatory")

# Convert dict to value field
attributes = self._convert_updatable_dict(data)

return self.table.update_item(
Key={
PK_FIELD: pk_value,
SK_FIELD: sk_value,
},
AttributeUpdates=attributes,
)

def get_by_pk(self, pk: str) -> dict:
response = self.table.query(KeyConditionExpression=Key("pk").eq(pk))
return serialize_dynamo(response["Items"])

def get_or_create(self, pk: str, sk: str) -> dict:
response = self.table.query(
KeyConditionExpression=And(Key("pk").eq(pk), Key("sk").eq(sk))
)
items = serialize_dynamo(response["Items"])
if items:
return items[0]
return {}


class SubscriptionTable(DynamoFender):
"""
DynamoDB handler for Subscription table.
"""

__tablename__ = "FenderSubscriptions"

def __init__(self, tablename: str = None) -> None:
_tablename = tablename or self.__tablename__
super().__init__(_tablename)


class PlanTable(DynamoFender):
"""
DynamoDB handler for Plan table.
"""

__tablename__ = "FenderPlans"

def __init__(self, tablename: str = None) -> None:
_tablename = tablename or self.__tablename__
super().__init__(_tablename)


class SubscriptionsAndPlansTable(DynamoFender):
"""
DynamoDB handler for Plan table.
"""

__tablename__ = "fender_digital_code_exercise"

def __init__(self, tablename: str = None) -> None:
_tablename = tablename or self.__tablename__
super().__init__(_tablename)
12 changes: 12 additions & 0 deletions app/python/src/db/tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
try:
# For local development
from .dynamo import PlanTable, SubscriptionsAndPlansTable, SubscriptionTable
except:
# For AWS Lambda deployment
from dynamo import PlanTable, SubscriptionsAndPlansTable, SubscriptionTable


class DynamoFenderTables:
SUBSCRIPTION = SubscriptionTable()
PLAN = PlanTable()
SUBSCRIPTIONS_AND_PLANS = SubscriptionsAndPlansTable()
18 changes: 14 additions & 4 deletions app/python/src/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
try:
# For local development
from .routes import Router
from .schemas.schemas import EventSchema
except:
# For AWS Lambda deployment
from routes import Router
from schemas.schemas import EventSchema


def handler(event, context):
return {
'statusCode': 200,
'body': 'Hello from Python Lambda!'
}
event = EventSchema(**event)
router = Router(event=event)

return router.process_event()
Loading