Skip to content

Commit

Permalink
refactor some functions into product service
Browse files Browse the repository at this point in the history
  • Loading branch information
patelnets committed Apr 26, 2024
1 parent 4fdf8bd commit aa0c7c7
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 20 deletions.
23 changes: 8 additions & 15 deletions backend/src/api/index.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import boto3
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from mangum import Mangum

from api.lib import dynamo
from api.lib.dynamo import ProductNotFoundError
from api.models import models
from api.services.products import delete_all_products as delete_all_products_service
from api.services.products import (
from api.services.product import ProductNotFoundError, ProductService
from api.services.product import delete_all_products as delete_all_products_service
from api.services.product import (
get_presigned_url_for_product,
get_product,
get_products,
Expand All @@ -31,8 +30,6 @@
allow_headers=["*"],
)

s3 = boto3.client("s3")


@app.get("/")
def get_root():
Expand All @@ -57,7 +54,7 @@ def post_product(payload: models.CreatePayload):
def get_product_route(product_id: str):
try:
return get_product(product_id)
except dynamo.ProductNotFoundError as err:
except ProductNotFoundError as err:
raise HTTPException(status_code=404, detail="Product not found") from err
except Exception as err:
print(err)
Expand All @@ -73,20 +70,16 @@ def update_product(product_id: str, payload: models.UpdatePayload):
name=payload.name,
categories=payload.categories,
)
except dynamo.ProductNotFoundError as err:
except ProductNotFoundError as err:
raise HTTPException(status_code=404, detail="Product not found") from err


@app.delete("/products/{product_id}", status_code=200)
def delete_product(product_id: str):
try:
# delete all the images from s3
product = get_product(product_id)
for image_id in product["images"]:
s3.delete_object(Bucket="froodom-frontend", Key=image_id)
dynamo.delete_product(product_id)
ProductService.delete_by_id(product_id=product_id)

except dynamo.ProductNotFoundError as err:
except ProductNotFoundError as err:
raise HTTPException(status_code=404, detail="Product not found") from err


Expand Down Expand Up @@ -125,7 +118,7 @@ def bulk_upload(payload: models.ProductsBulkUploadRequest):
name=product.name,
categories=product.categories,
)
except dynamo.ProductNotFoundError:
except ProductNotFoundError:
dynamo.create_product(
stores=product.stores,
name=product.name,
Expand Down
8 changes: 3 additions & 5 deletions backend/src/api/lib/dynamo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@
import boto3
from boto3.dynamodb.conditions import Attr, Key

from api.services.product import ProductNotFoundError

table = boto3.resource("dynamodb").Table(os.environ["table"])


class Error(Exception):
pass


class ProductNotFoundError(Error):
pass


def create_product(name: str, stores: list[str], categories: list[str]) -> dict:
print("Creating product")

Expand Down Expand Up @@ -68,7 +66,7 @@ def update_product(
stores: list[str] = None,
name: str = None,
categories: list[str] = None,
) -> dict:
):
expr = []
attr_values = {}
attr_names = {}
Expand Down
8 changes: 8 additions & 0 deletions backend/src/api/lib/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import boto3

# Best to instantiate at global level in lambdas https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html
s3 = boto3.client("s3")


def delete_object(bucket: str, key: str):
s3.delete_object(Bucket=bucket, Key=key)
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
import boto3

from api.lib import dynamo
from api.lib.storage import delete_object

s3 = boto3.client("s3")


class ProductNotFoundError(Exception):
pass


def get_all_images_for_product(product_id):
res = s3.list_objects_v2(
Bucket=os.environ["bucket"], Prefix=f"images/{product_id}/"
Expand Down Expand Up @@ -75,3 +80,12 @@ def delete_all_products():
s3.delete_object(Bucket="froodom-frontend", Key=image_id)
dynamo.delete_product(product["id"])
return res


class ProductService:
@staticmethod
def delete_by_id(product_id: str):
product = get_product(product_id)
for image_id in product["images"]:
delete_object(bucket="froodom-frontend", key=image_id)
dynamo.delete_product(product_id)
72 changes: 72 additions & 0 deletions backend/src/tests/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,75 @@ def test_get_single_product(dynamodb_client, s3_client):
}
assert response.json() == (expected | response.json())
assert isinstance(response.json()["id"], str)


@mock_aws()
def test_delete_single_product(dynamodb_client, s3_client):
dynamodb_client.create_table(
TableName="test-table",
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)

s3_client.create_bucket(
Bucket="test-bucket",
CreateBucketConfiguration={"LocationConstraint": "us-east-2"},
)

client = TestClient(app)
response = client.post(
"/products",
json={
"name": "Foo Bar",
"stores": ["store1", "store2"],
"categories": ["category1", "category2"],
},
)
assert response.status_code == 201
product_id = response.json()["id"]

response = client.delete(f"/products/{product_id}")
assert response.status_code == 200

response = client.get(f"/products/{product_id}")
assert response.status_code == 404


@mock_aws()
def test_get_pre_signed_url(dynamodb_client, s3_client):
dynamodb_client.create_table(
TableName="test-table",
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)

s3_client.create_bucket(
Bucket="test-bucket",
CreateBucketConfiguration={"LocationConstraint": "us-east-2"},
)

client = TestClient(app)
response = client.post(
"/products",
json={
"name": "Foo Bar",
"stores": ["store1", "store2"],
"categories": ["category1", "category2"],
},
)
assert response.status_code == 201
product_id = response.json()["id"]

response = client.get(f"/products/{product_id}/image-upload-pre-signed-url")
assert response.status_code == 200
assert response.json().get("url") is not None

0 comments on commit aa0c7c7

Please sign in to comment.