Skip to content

Commit c5c4f7b

Browse files
authored
Added more APIs for the vehicle microservice and update README (#79)
## Description of changes: Updated the Vehicle Inventory Microservice to support a bunch of new APIs as well as a new table called VehiclePurchaseHistory that will store date and price for a certain vehicle model. The new APIs include some GET/POST/DELETE APIs for the Vehicle model and VehiclePurchaseHistory model as well as an extra API that POSTs to the image service. ## Testing Tested all the following APIs and they are working as expected: 1. `GET /vehicle-inventory/heatlh-check/` 2. `GET /vehicle-inventory/` 3. `POST /vehicle-inventory/` 4. `GET /vehicle-inventory/<int>` 5. `DELETE /vehicle-inventory/<int>` 6. `GET /vehicle-inventory/name/<str>` 7. `GET /vehicle-inventory/<int>/image` 8. `GET /vehicle-inventory/image/<image_name>` 9. `POST /vehicle-inventory/image/<image_name>` 10. `GET /vehicle-inventory/history/` 11. `POST /vehicle-inventory/history/` 12. `GET /vehicle-inventory/history/<int>` 13. `DELETE /vehicle-inventory/history/<int>` 14. `GET /vehicle-inventory/history/<int>/vehicle` ``` asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/health-check/ Vehicle Inventory Service up and running!% asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/ {'id': 2, 'make': 'BMW', 'model': 'M340', 'year': 2022, 'image_name': 'newCar.jpg'}% asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/1 Vehicle with id=1 is not found% asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/2 {'id': 2, 'make': 'BMW', 'model': 'M340', 'year': 2022, 'image_name': 'newCar.jpg'}% asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/2/image {'ResponseMetadata': {'RequestId': '970QZM0ZRXA84CPN', 'HostId': 'UdimS4YvRKrpv2tv5r0LSXXdLC8SZehGWvCZA9EqQpT4QXqi0s3jF0uB+krjA9bCiWtKArQETZrLsqc8gLrLSUMpNnpBKw9z', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amz-id-2': 'UdimS4YvRKrpv2tv5r0LSXXdLC8SZehGWvCZA9EqQpT4QXqi0s3jF0uB+krjA9bCiWtKArQETZrLsqc8gLrLSUMpNnpBKw9z', 'x-amz-request-id': '970QZM0ZRXA84CPN', 'date': 'Tue, 27 Feb 2024 22:22:19 GMT', 'last-modified': 'Tue, 27 Feb 2024 22:06:50 GMT', 'etag': '"d41d8cd98f00b204e9800998ecf8427e"', 'x-amz-server-side-encryption': 'AES256', 'accept-ranges': 'bytes', 'content-type': 'binary/octet-stream', 'server': 'AmazonS3', 'content-length': '0'}, 'RetryAttempts': 0}, 'AcceptRanges': 'bytes', 'LastModified': datetime.datetime(2024, 2, 27, 22, 6, 50, tzinfo=tzutc()), 'ContentLength': 0, 'ETag': '"d41d8cd98f00b204e9800998ecf8427e"', 'ContentType': 'binary/octet-stream', 'ServerSideEncryption': 'AES256', 'Metadata': {}, 'Body': <botocore.response.StreamingBody object at 0x7f99808db940>}% asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/image/newCar.jpg {'ResponseMetadata': {'RequestId': '3HDD29P1QGH9HTB2', 'HostId': '0KYMPvXr2Xo4rx93XgdKdzYIpzQy4+ZWgeDxVvzxvkv0fnAQbHg0esgG5BRCWtMdwha6dqI7D+g=', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amz-id-2': '0KYMPvXr2Xo4rx93XgdKdzYIpzQy4+ZWgeDxVvzxvkv0fnAQbHg0esgG5BRCWtMdwha6dqI7D+g=', 'x-amz-request-id': '3HDD29P1QGH9HTB2', 'date': 'Tue, 27 Feb 2024 22:22:36 GMT', 'last-modified': 'Tue, 27 Feb 2024 22:06:50 GMT', 'etag': '"d41d8cd98f00b204e9800998ecf8427e"', 'x-amz-server-side-encryption': 'AES256', 'accept-ranges': 'bytes', 'content-type': 'binary/octet-stream', 'server': 'AmazonS3', 'content-length': '0'}, 'RetryAttempts': 0}, 'AcceptRanges': 'bytes', 'LastModified': datetime.datetime(2024, 2, 27, 22, 6, 50, tzinfo=tzutc()), 'ContentLength': 0, 'ETag': '"d41d8cd98f00b204e9800998ecf8427e"', 'ContentType': 'binary/octet-stream', 'ServerSideEncryption': 'AES256', 'Metadata': {}, 'Body': <botocore.response.StreamingBody object at 0x7f99808dbeb0>}% asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/history/ {'id': 5, 'vehicle_id': 2, 'purchase_date': datetime.date(2024, 2, 27), 'purchase_price': 66000}% asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/history/5 {'id': 5, 'vehicle_id': 2, 'purchase_date': datetime.date(2024, 2, 27), 'purchase_price': 66000}% asakem@88665a24d661 vehicle-dealership-sample-app % curl -X GET http://0.0.0.0:8001/vehicle-inventory/history/5/vehicle {'id': 2, 'make': 'BMW', 'model': 'M340', 'year': 2022, 'image_name': 'newCar.jpg'}% ``` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 1c752cc commit c5c4f7b

File tree

5 files changed

+158
-13
lines changed

5 files changed

+158
-13
lines changed

sample-applications/vehicle-dealership-sample-app/README.md

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ psql -h <rds_url> -d vehicle_inventory -U root
7070
7171
alter user djangouser with encrypted password '<password_of_your_choosing>';
7272
grant all privileges on database vehicle_inventory to djangouser;
73+
ALTER DATABASE vehicle_inventory OWNER TO djangouser;
7374
7475
aws s3 sync s3://<s3_bucket_that_has_python_code> .
7576
@@ -83,7 +84,7 @@ POSTGRES_DATABASE=vehicle_inventory
8384
POSTGRES_USER=djangouser
8485
POSTGRES_PASSWORD=<password_from_this_step>
8586
DB_SERVICE_HOST=<RDS_DB_endpoint>
86-
DB_SERVICE_PORT=3306
87+
DB_SERVICE_PORT=5432
8788
IMAGE_BACKEND_SERVICE_HOST=<image-service_ec2_public_IP>
8889
IMAGE_BACKEND_SERVICE_PORT=8000
8990
@@ -119,10 +120,27 @@ They should be accessible through `0.0.0.0:8000` for the image service and `0.0.
119120
## APIs
120121

121122
The following are the APIs and what they do:
122-
1. GET /vehicle-inventory/: returns all the vehicles entries for postgres db
123-
2. POST /vehicle-inventory/: puts vehicle into db. For example: `curl -X POST http://0.0.0.0:8001/vehicle-inventory/ -d '{"make": "BMW","model": "M340","year": 2022,"image_name": "newCar.jpg"}'`
124-
3. GET /vehicle-inventory/<int>: returns vehicle entry with id = <int>
125-
4. GET /vehicle-inventory/<int>/image: returns image file information from S3 for the specific vehicle by calling the image microservice
126-
5. GET /images/name/<image_name>: returns image information for <image_name> from S3 if present.
127-
6. POST /images/name/<image_name>: creates an empty file in S3. This is an async endpoint since it will put image name in an SQS queue and not wait for the file to be created in S3. Instead, a long running thread will poll SQS and then create the image file later.
128-
7. GET /image/remote-image: makes a remote http call to google.com.
123+
1. `GET /vehicle-inventory/heatlh-check/`: returns 200 if the vehicle service is up and running.
124+
2. `GET /vehicle-inventory/`: returns all the vehicles entries for postgres db
125+
3. `POST /vehicle-inventory/`: puts vehicle into db. For example: `curl -X POST http://0.0.0.0:8001/vehicle-inventory/
126+
-d '{"make": "BMW","model": "M340","year": 2022,"image_name": "newCar.jpg"}'`
127+
4. `GET /vehicle-inventory/<int>`: returns vehicle entry with id = <int>
128+
5. `DELETE /vehicle-inventory/<int>`: deletes vehicle entry with id = <int>
129+
6. `GET /vehicle-inventory/name/<str>`: returns vehicle entries with name = <str>
130+
7. `GET /vehicle-inventory/<int>/image`: returns image file information from S3 for the specific vehicle by calling
131+
the image microservice
132+
8. `GET /vehicle-inventory/image/<image_name>`: returns image information for <image_name> from S3 if present through
133+
the image service.
134+
9. `POST /vehicle-inventory/image/<image_name>`: Calls the image service API `POST /images/name/<image_name>`
135+
10. `GET /vehicle-inventory/history/`: returns all the vehicle purchase history entries from postgres db
136+
11. `POST /vehicle-inventory/history/`: puts vehicle purchase history into db. For example: `curl -X POST http://0.0.0.
137+
0:8001/vehicle-inventory/history/ -d '{"vehicle_id": "1","purchase_price": "66000"}'`
138+
12. `GET /vehicle-inventory/history/<int>`: returns vehicle purchase history entry with id = <int>
139+
13. `DELETE /vehicle-inventory/history/<int>`: deletes vehicle purchase history entry with id = <int>
140+
14. `GET /vehicle-inventory/history/<int>/vehicle`: returns vehicle entry that is linked to vehicle purchase history
141+
with id = <int>
142+
15. `GET /images/name/<image_name>`: returns image information for <image_name> from S3 if present.
143+
16. `POST /images/name/<image_name>`: creates an empty file in S3. This is an async endpoint since it will put image
144+
name in an SQS queue and not wait for the file to be created in S3. Instead, a long running thread will poll SQS
145+
and then create the image file later.
146+
17. `GET /image/remote-image`: makes a remote http call to google.com.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
# pylint: skip-file
4+
# Generated by Django 5.0 on 2024-02-27 21:52
5+
6+
import django.db.models.deletion
7+
from django.db import migrations, models
8+
9+
10+
class Migration(migrations.Migration):
11+
12+
dependencies = [
13+
("MainService", "0001_initial"),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name="VehiclePurchaseHistory",
19+
fields=[
20+
("id", models.AutoField(primary_key=True, serialize=False)),
21+
("purchase_date", models.DateField(auto_now_add=True)),
22+
("purchase_price", models.IntegerField()),
23+
("vehicle", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="MainService.vehicle")),
24+
],
25+
),
26+
]

sample-applications/vehicle-dealership-sample-app/VehicleInventoryApp/MainService/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,10 @@ class Vehicle(models.Model):
99
model = models.CharField(max_length=255)
1010
year = models.IntegerField()
1111
image_name = models.TextField(max_length=255)
12+
13+
14+
class VehiclePurchaseHistory(models.Model):
15+
id = models.AutoField(primary_key=True)
16+
vehicle = models.ForeignKey("Vehicle", on_delete=models.CASCADE)
17+
purchase_date = models.DateField(auto_now_add=True)
18+
purchase_price = models.IntegerField()

sample-applications/vehicle-dealership-sample-app/VehicleInventoryApp/MainService/url.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,20 @@
66

77
urlpatterns = [
88
path("", views.vehicle, name="vehicle"),
9-
path("<int:vehicle_id>", views.get_vehicle_by_id, name="get_vehicle_by_id"),
9+
path("<int:vehicle_id>", views.vehicle_by_id, name="vehicle_by_id"),
10+
path("make/<str:vehicles_make>", views.get_vehicles_by_make, name="get_vehicles_by_make"),
1011
path("<int:vehicle_id>/image", views.get_vehicle_image, name="get_vehicle_image"),
11-
path("image/<str:image_name>", views.get_image_by_name, name="image_by_name"),
12+
path("image/<str:image_name>", views.image, name="image"),
13+
path("history/", views.vehicle_purchase_history, name="purchase_history"),
14+
path(
15+
"history/<int:vehicle_purchase_history_id>",
16+
views.vehicle_purchase_history_by_id,
17+
name="vehicle_purchase_history_by_id",
18+
),
19+
path(
20+
"history/<int:vehicle_purchase_history_id>/vehicle",
21+
views.get_vehicle_from_vehicle_history,
22+
name="get_vehicle_from_vehicle_history",
23+
),
24+
path("health-check/", views.health_check, name="health_check"),
1225
]

sample-applications/vehicle-dealership-sample-app/VehicleInventoryApp/MainService/views.py

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@
88
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, HttpResponseNotFound
99
from django.views.decorators.csrf import csrf_exempt
1010
from dotenv import load_dotenv
11-
from MainService.models import Vehicle
11+
from MainService.models import Vehicle, VehiclePurchaseHistory
1212

1313
load_dotenv()
1414

1515

16+
def health_check(request):
17+
return HttpResponse("Vehicle Inventory Service up and running!")
18+
19+
1620
def get_image_endpoint():
1721
load_dotenv()
1822
return "http://" + os.environ.get("IMAGE_BACKEND_SERVICE_HOST") + ":" + os.environ.get("IMAGE_BACKEND_SERVICE_PORT")
@@ -38,7 +42,8 @@ def vehicle(request):
3842
return HttpResponseNotAllowed("Only GET/POST requests are allowed!")
3943

4044

41-
def get_vehicle_by_id(request, vehicle_id):
45+
@csrf_exempt
46+
def vehicle_by_id(request, vehicle_id):
4247
if request.method == "GET":
4348
throttle_time = request.GET.get("throttle")
4449
if throttle_time:
@@ -49,6 +54,22 @@ def get_vehicle_by_id(request, vehicle_id):
4954
if not vehicle_objects:
5055
return HttpResponseNotFound("Vehicle with id=" + str(vehicle_id) + " is not found")
5156
return HttpResponse(vehicle_objects)
57+
if request.method == "DELETE":
58+
vehicle_objects = Vehicle.objects.filter(id=vehicle_id)
59+
vehicle_objects_values = Vehicle.objects.filter(id=vehicle_id).values()
60+
if not vehicle_objects_values:
61+
return HttpResponseNotFound("Vehicle with id=" + str(vehicle_id) + " is not found")
62+
vehicle_objects.delete()
63+
return HttpResponse("Vehicle with id=" + str(vehicle_id) + " has been deleted")
64+
return HttpResponseNotAllowed("Only GET/DELETE requests are allowed!")
65+
66+
67+
def get_vehicles_by_make(request, vehicles_make):
68+
if request.method == "GET":
69+
vehicles_objects = Vehicle.objects.filter(make=vehicles_make).values()
70+
if not vehicles_objects:
71+
return HttpResponseNotFound("Couldn't find any vehicle with make=" + str(vehicles_make))
72+
return HttpResponse(vehicles_objects)
5273
return HttpResponseNotAllowed("Only GET requests are allowed!")
5374

5475

@@ -63,13 +84,73 @@ def get_vehicle_image(request, vehicle_id):
6384

6485

6586
@csrf_exempt
66-
def get_image_by_name(request, image_name):
87+
def image(request, image_name):
6788
print(image_name)
6889
if request.method == "GET":
6990
response = requests.get(build_image_url(image_name), timeout=10)
7091
if response.ok:
7192
return HttpResponse(response)
7293
return HttpResponseNotFound("Image with name: " + image_name + " is not found")
94+
if request.method == "POST":
95+
response = requests.post(build_image_url(image_name), timeout=10)
96+
if response.ok:
97+
return HttpResponse(response)
98+
return HttpResponseNotFound("Image with name: " + image_name + " failed to saved")
99+
return HttpResponseNotAllowed("Only GET/POST requests are allowed!")
100+
101+
102+
@csrf_exempt
103+
def vehicle_purchase_history(request):
104+
if request.method == "POST":
105+
body_unicode = request.body.decode("utf-8")
106+
body = json.loads(body_unicode)
107+
try:
108+
vehicle_purchase_history_object = VehiclePurchaseHistory(
109+
vehicle_id=body["vehicle_id"], purchase_price=body["purchase_price"]
110+
)
111+
vehicle_purchase_history_object.save()
112+
return HttpResponse("VehiclePurchaseHistoryId = " + str(vehicle_purchase_history_object.id))
113+
except KeyError as exception:
114+
return HttpResponseBadRequest("Missing key: " + str(exception))
115+
elif request.method == "GET":
116+
vehicle_purchase_history_object = VehiclePurchaseHistory.objects.all().values()
117+
return HttpResponse(vehicle_purchase_history_object)
118+
return HttpResponseNotAllowed("Only GET/POST requests are allowed!")
119+
120+
121+
@csrf_exempt
122+
def vehicle_purchase_history_by_id(request, vehicle_purchase_history_id):
123+
if request.method == "GET":
124+
vehicle_purchase_history_object = VehiclePurchaseHistory.objects.filter(id=vehicle_purchase_history_id).values()
125+
if not vehicle_purchase_history_object:
126+
return HttpResponseNotFound(
127+
"VehiclePurchaseHistory with id=" + str(vehicle_purchase_history_id) + " is not found"
128+
)
129+
return HttpResponse(vehicle_purchase_history_object)
130+
if request.method == "DELETE":
131+
vehicle_purchase_history_object = VehiclePurchaseHistory.objects.filter(id=vehicle_purchase_history_id)
132+
vehicle_purchase_history_object_values = Vehicle.objects.filter(id=vehicle_purchase_history_id).values()
133+
if not vehicle_purchase_history_object_values:
134+
return HttpResponseNotFound(
135+
"VehiclePurchaseHistory with id=" + str(vehicle_purchase_history_id) + " is not found"
136+
)
137+
vehicle_purchase_history_object.delete()
138+
return HttpResponse("VehiclePurchaseHistory with id=" + str(vehicle_purchase_history_id) + " has been deleted")
139+
return HttpResponseNotAllowed("Only GET/DELETE requests are allowed!")
140+
141+
142+
def get_vehicle_from_vehicle_history(request, vehicle_purchase_history_id):
143+
if request.method == "GET":
144+
vehicle_purchase_history_object = VehiclePurchaseHistory.objects.filter(id=vehicle_purchase_history_id).first()
145+
if not vehicle_purchase_history_object:
146+
return HttpResponseNotFound(
147+
"VehiclePurchaseHistory with id=" + str(vehicle_purchase_history_id) + " is not found"
148+
)
149+
vehicle_id = getattr(vehicle_purchase_history_object, "vehicle_id")
150+
vehicle_objects = Vehicle.objects.filter(id=vehicle_id).values()
151+
if not vehicle_objects:
152+
return HttpResponseNotFound("Vehicle with id=" + str(vehicle_id) + " is not found")
153+
return HttpResponse(vehicle_objects)
73154
return HttpResponseNotAllowed("Only GET requests are allowed!")
74155

75156

0 commit comments

Comments
 (0)