From bdd2d7248cc4f1742cd463e518514c9c17290d31 Mon Sep 17 00:00:00 2001 From: Mohammed Mahmood Date: Sun, 7 Nov 2021 02:03:21 +0300 Subject: [PATCH 1/3] update address endpoints --- .idea/03-commerce.iml | 2 +- .idea/misc.xml | 2 +- account/authorization.py | 2 +- account/migrations/0002_alter_user_options.py | 17 ++ account/schemas.py | 5 + commerce/controllers.py | 207 ++++++++++++++++-- commerce/schemas.py | 19 +- 7 files changed, 227 insertions(+), 27 deletions(-) create mode 100644 account/migrations/0002_alter_user_options.py diff --git a/.idea/03-commerce.iml b/.idea/03-commerce.iml index f602895..c6d616b 100644 --- a/.idea/03-commerce.iml +++ b/.idea/03-commerce.iml @@ -16,7 +16,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 0c95c56..5876997 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/account/authorization.py b/account/authorization.py index 661ada1..83841aa 100644 --- a/account/authorization.py +++ b/account/authorization.py @@ -9,6 +9,7 @@ TIME_DELTA = timedelta(days=120) + class GlobalAuth(HttpBearer): def authenticate(self, request, token): try: @@ -24,4 +25,3 @@ def get_tokens_for_user(user): return { 'access': str(token), } - diff --git a/account/migrations/0002_alter_user_options.py b/account/migrations/0002_alter_user_options.py new file mode 100644 index 0000000..9163097 --- /dev/null +++ b/account/migrations/0002_alter_user_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.8 on 2021-11-06 20:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='user', + options={'verbose_name': 'user', 'verbose_name_plural': 'users'}, + ), + ] diff --git a/account/schemas.py b/account/schemas.py index 681457b..609550f 100644 --- a/account/schemas.py +++ b/account/schemas.py @@ -22,13 +22,16 @@ class AccountOut(Schema): company_name: str = None company_website: str = None + class TokenOut(Schema): access: str + class AuthOut(Schema): token: TokenOut account: AccountOut + class SigninSchema(Schema): email: EmailStr password: str @@ -48,3 +51,5 @@ class ChangePasswordSchema(Schema): old_password: str new_password1: str new_password2: str + + diff --git a/commerce/controllers.py b/commerce/controllers.py index a8a551a..d125a41 100644 --- a/commerce/controllers.py +++ b/commerce/controllers.py @@ -2,15 +2,16 @@ import string from typing import List -from django.contrib.auth import get_user_model from django.db.models import Q from django.shortcuts import get_object_or_404 from ninja import Router from pydantic import UUID4 +from django.contrib.auth import get_user_model from account.authorization import GlobalAuth -from commerce.models import Product, Category, City, Vendor, Item, Order, OrderStatus -from commerce.schemas import ProductOut, CitiesOut, CitySchema, VendorOut, ItemOut, ItemSchema, ItemCreate +from commerce.models import Product, Category, City, Vendor, Item, Order, OrderStatus, Address +from commerce.schemas import ProductOut, CitiesOut, CitySchema, VendorOut, ItemOut, ItemSchema, ItemCreate, \ + AddressSchema, AddressCreate, AddressOut, CheckoutSchema from config.utils.schemas import MessageOut products_controller = Router(tags=['products']) @@ -20,6 +21,7 @@ User = get_user_model() + @vendor_controller.get('', response=List[VendorOut]) def list_vendors(request): return Vendor.objects.all() @@ -87,7 +89,7 @@ def list_products( for p in products: print(p) - + for every product, we retrieve (Merchant, Label, Category, Vendor) records Merchant.objects.get(id=p.merchant_id) (select * from merchant where id = 'p.merchant_id') @@ -114,16 +116,113 @@ def list_products( """ -@address_controller.get('') +# address CRUD operations endpoints are here! +# All addresses require authentication because they're sensitive data +# ---- +@address_controller.get('', auth=GlobalAuth(), response={ + 200: List[AddressSchema], + 404: MessageOut, + 401: MessageOut +}) def list_addresses(request): - pass + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + # gets all the addresses for the given user + user_addresses = Address.objects.filter(user=user) + return user_addresses + + +@address_controller.get('/{id}', auth=GlobalAuth(), response={ + 200: AddressOut, + 404: MessageOut, + 401: MessageOut +}) +def retrieve_address(request, id: UUID4): + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + + user = User.objects.filter(id=request.auth['pk'])[0] + address = Address.objects.filter(id=id, user=user) + + # checks if the address belongs to the user or not + if not address: + return 401, {'detail': 'unauthorized'} + + # only returns address if it's the user's AND if it's a valid token + return 200, address[0] + + +@address_controller.post('', auth=GlobalAuth(), response={ + 201: AddressOut, + 400: MessageOut, + 401: MessageOut +}) +def create_address(request, address_in: AddressCreate): + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + + address = Address(**address_in.dict(), user=user) + address.save() + return 201, address + + +@address_controller.put('/{id}', auth=GlobalAuth(), response={ + 200: MessageOut, + 400: MessageOut, + 401: MessageOut +}) +def update_address(request, id: UUID4, address_in: AddressCreate): + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + + address = Address.objects.filter(id=id, user=user) + # if the user doesn't own this address, return 401 + if not address: + return 401, {'detail': 'unauthorized'} + + updated = address.update(**address_in.dict()) + if updated: + return 200, {"detail": "updated successfully"} + return 400, {"detail": "No address found"} + + +@address_controller.delete("/{id}", auth=GlobalAuth(), response={ + 204: MessageOut, + 404: MessageOut, + 401: MessageOut +}) +def delete_address(request, id: UUID4): + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + + address = Address.objects.filter(id=id, user=user) + # if the user doesn't own this address, return 401 + if not address: + return 401, {'detail': 'unauthorized'} + address.delete() + return 204, {"detail": ""} + +# ---- # @products_controller.get('categories', response=List[CategoryOut]) # def list_categories(request): # return Category.objects.all() - +# cities endpoints are here (Everyone can add, remove, and update cities.) @address_controller.get('cities', response={ 200: List[CitiesOut], 404: MessageOut @@ -175,12 +274,15 @@ def delete_city(request, id: UUID4): return 204, {'detail': ''} -@order_controller.get('cart', response={ +# --------------------- +# cart, order and checkout endpoints are here. + +@order_controller.get('cart', auth=GlobalAuth(), response={ 200: List[ItemOut], 404: MessageOut }) def view_cart(request): - cart_items = Item.objects.filter(user=User.objects.first(), ordered=False) + cart_items = Item.objects.filter(user=get_object_or_404(User, request.auth['pk']), ordered=False) if cart_items: return cart_items @@ -188,26 +290,26 @@ def view_cart(request): return 404, {'detail': 'Your cart is empty, go shop like crazy!'} -@order_controller.post('add-to-cart', response={ +@order_controller.post('add-to-cart', auth=GlobalAuth(), response={ 200: MessageOut, # 400: MessageOut }) def add_update_cart(request, item_in: ItemCreate): try: - item = Item.objects.get(product_id=item_in.product_id, user=User.objects.first()) + item = Item.objects.get(product_id=item_in.product_id, user=get_object_or_404(User, request.auth['pk'])) item.item_qty += 1 item.save() except Item.DoesNotExist: - Item.objects.create(**item_in.dict(), user=User.objects.first()) + Item.objects.create(**item_in.dict(), user=get_object_or_404(User, request.auth['pk'])) return 200, {'detail': 'Added to cart successfully'} -@order_controller.post('item/{id}/reduce-quantity', response={ +@order_controller.post('item/{id}/reduce-quantity', auth=GlobalAuth(), response={ 200: MessageOut, }) def reduce_item_quantity(request, id: UUID4): - item = get_object_or_404(Item, id=id, user=User.objects.first()) + item = get_object_or_404(Item, id=id, user=get_object_or_404(User, request.auth['pk'])) if item.item_qty <= 1: item.delete() return 200, {'detail': 'Item deleted!'} @@ -217,21 +319,36 @@ def reduce_item_quantity(request, id: UUID4): return 200, {'detail': 'Item quantity reduced successfully!'} -@order_controller.delete('item/{id}', response={ +@order_controller.delete('item/{id}', auth=GlobalAuth(), response={ 204: MessageOut }) def delete_item(request, id: UUID4): - item = get_object_or_404(Item, id=id, user=User.objects.first()) + item = get_object_or_404(Item, id=id, user=get_object_or_404(User, request.auth['pk'])) item.delete() return 204, {'detail': 'Item deleted!'} +@order_controller.put("item/{id}/increase-quantity", auth=GlobalAuth(), response={ + 200: MessageOut, + 404: MessageOut +}) +def increase_quantity(request, id: UUID4): + item = get_object_or_404(Item, id=id, user=get_object_or_404(User, request.auth['pk'])) + item.item_qty += 1 + item.save() + return 200, {"detail": f"Item increased from {item.item_qty - 1} to {item.item_qty} successfully."} + + def generate_ref_code(): return ''.join(random.sample(string.ascii_letters + string.digits, 6)) -@order_controller.post('create-order', auth=GlobalAuth(), response=MessageOut) +@order_controller.post('create-order', auth=GlobalAuth(), response={ + 201: MessageOut, + 400: MessageOut, + 403: MessageOut +}) def create_order(request): ''' * add items and mark (ordered) field as True @@ -239,19 +356,63 @@ def create_order(request): * add NEW status * calculate the total ''' + # First, checks whether there are items in the cart (items that are not ordered) + user_items = Item.objects.filter(user=get_object_or_404(User, request.auth['pk'])).filter(ordered=False) + if not user_items: + return 400, {'detail': 'Can not create an order of an empty cart.'} + ''' + Secondly, checks if there are already an active order. + If True, add the items then merge the duplicates + Else, make a new active order + ''' + order = Order.objects.filter(user=get_object_or_404(User, request.auth['pk']), ordered=False)[0] + if order: + ordered_products_id = list(Item.objects.filter(ordered=True).values('product_id')) + ordered_products_id = list(map(lambda x: x['product_id'], ordered_products_id)) + for item in user_items: + if item.product_id in ordered_products_id: + ordered_item = Item.objects.filter(product_id=item.product_id)[0] + ordered_item.item_qty += item.item_qty + ordered_item.save() + item.delete() + else: + order.items.add(item) + # calculate the total + order.total = order.order_total + # mark items as ordered (added to a user order) + user_items.update(ordered=True) + order.save() + + return 201, {"detail": "There was already an order and your cart items were merged."} + + # New order + # Create an order query set and we'll initially use 4 out of 8 attributes in an order order_qs = Order.objects.create( - user=User.objects.first(), - status=OrderStatus.objects.get(is_default=True), + user=get_object_or_404(User, request.auth['pk']), + status=OrderStatus.objects.get(is_default=True), # Which is 'NEW' ref_code=generate_ref_code(), ordered=False, ) - - user_items = Item.objects.filter(user=User.objects.first()).filter(ordered=False) - + # add them to the order order_qs.items.add(*user_items) + # calculate the total order_qs.total = order_qs.order_total + # mark items as ordered (added to a user order) user_items.update(ordered=True) order_qs.save() - return {'detail': 'order created successfully'} + return 201, {'detail': 'order created successfully'} + + +@order_controller.post("checkout", auth=GlobalAuth(), response={ + 200: MessageOut, + 404: MessageOut +}) +def checkout(request, checkout_data_in: CheckoutSchema): + current_order = Order.objects.filter(user=get_object_or_404(User, request.auth['pk'])).filter(ordered=False) + if current_order: + processing = OrderStatus.objects.get(title='PROCESSING') # proccessing must be added + current_order.update(**checkout_data_in.dict(), status=processing.id, ordered=True) + return 200, {'detail': 'Checkout was successful'} + return 404, {"detail": "No current order found"} diff --git a/commerce/schemas.py b/commerce/schemas.py index 5d68396..cc7b0d1 100644 --- a/commerce/schemas.py +++ b/commerce/schemas.py @@ -9,7 +9,6 @@ - class UUIDSchema(Schema): id: UUID4 @@ -91,3 +90,21 @@ class ItemOut(UUIDSchema, ItemSchema): pass +class AddressSchema(Schema): + work_address: bool = False + address1: str + address2: str = None + phone: str + + +class AddressOut(AddressSchema, UUIDSchema): + city: CitySchema + + +class AddressCreate(AddressSchema): + city_id: UUID4 + + +class CheckoutSchema(Schema): + address: UUID4 + note: str = None \ No newline at end of file From e3b10f69ad55b2fcc36422a46608ffbc935eb4bd Mon Sep 17 00:00:00 2001 From: Mohammed Mahmood Date: Sun, 7 Nov 2021 02:14:32 +0300 Subject: [PATCH 2/3] finish view_cart endpoint and going to sleep --- commerce/controllers.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/commerce/controllers.py b/commerce/controllers.py index d125a41..23db2fa 100644 --- a/commerce/controllers.py +++ b/commerce/controllers.py @@ -279,10 +279,18 @@ def delete_city(request, id: UUID4): @order_controller.get('cart', auth=GlobalAuth(), response={ 200: List[ItemOut], - 404: MessageOut + 404: MessageOut, + 401: MessageOut }) def view_cart(request): - cart_items = Item.objects.filter(user=get_object_or_404(User, request.auth['pk']), ordered=False) + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + + # gets the cart for the given user + cart_items = Item.objects.filter(user=user, ordered=False) if cart_items: return cart_items From c929df7a04a95d0412030415c04b5fd24810e2e4 Mon Sep 17 00:00:00 2001 From: Mohammed Mahmood Date: Sun, 7 Nov 2021 13:03:12 +0300 Subject: [PATCH 3/3] Secure the remaining endpoints and finished the task --- commerce/controllers.py | 135 +++++++++++++++++++++++++++------------- commerce/schemas.py | 4 +- 2 files changed, 92 insertions(+), 47 deletions(-) diff --git a/commerce/controllers.py b/commerce/controllers.py index 23db2fa..3bde334 100644 --- a/commerce/controllers.py +++ b/commerce/controllers.py @@ -300,24 +300,41 @@ def view_cart(request): @order_controller.post('add-to-cart', auth=GlobalAuth(), response={ 200: MessageOut, + 401: MessageOut # 400: MessageOut }) def add_update_cart(request, item_in: ItemCreate): + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + try: - item = Item.objects.get(product_id=item_in.product_id, user=get_object_or_404(User, request.auth['pk'])) - item.item_qty += 1 + # if the item already exists then add the qty amount to the current amount + item = Item.objects.get(product_id=item_in.product_id, user=user) + item.item_qty += item_in.item_qty item.save() except Item.DoesNotExist: - Item.objects.create(**item_in.dict(), user=get_object_or_404(User, request.auth['pk'])) + # if it doesn't exist just create it with the desired amount + Item.objects.create(**item_in.dict(), user=user) return 200, {'detail': 'Added to cart successfully'} @order_controller.post('item/{id}/reduce-quantity', auth=GlobalAuth(), response={ 200: MessageOut, + 401: MessageOut }) def reduce_item_quantity(request, id: UUID4): - item = get_object_or_404(Item, id=id, user=get_object_or_404(User, request.auth['pk'])) + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + + # if its the users item it will reduce the quantity, else, it will return 404 + item = get_object_or_404(Item, id=id, user=user) if item.item_qty <= 1: item.delete() return 200, {'detail': 'Item deleted!'} @@ -328,10 +345,17 @@ def reduce_item_quantity(request, id: UUID4): @order_controller.delete('item/{id}', auth=GlobalAuth(), response={ - 204: MessageOut + 204: MessageOut, + 401: MessageOut }) def delete_item(request, id: UUID4): - item = get_object_or_404(Item, id=id, user=get_object_or_404(User, request.auth['pk'])) + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + + # it will delete the item only if the item id and the product id are valid with the same item, else, 404 + item = get_object_or_404(Item, id=id, user=user) item.delete() return 204, {'detail': 'Item deleted!'} @@ -342,7 +366,13 @@ def delete_item(request, id: UUID4): 404: MessageOut }) def increase_quantity(request, id: UUID4): - item = get_object_or_404(Item, id=id, user=get_object_or_404(User, request.auth['pk'])) + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + + item = get_object_or_404(Item, id=id, user=user) item.item_qty += 1 item.save() return 200, {"detail": f"Item increased from {item.item_qty - 1} to {item.item_qty} successfully."} @@ -355,7 +385,8 @@ def generate_ref_code(): @order_controller.post('create-order', auth=GlobalAuth(), response={ 201: MessageOut, 400: MessageOut, - 403: MessageOut + 403: MessageOut, + 401: MessageOut }) def create_order(request): ''' @@ -364,61 +395,77 @@ def create_order(request): * add NEW status * calculate the total ''' + # checks if the token is valid or exists + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + # First, checks whether there are items in the cart (items that are not ordered) - user_items = Item.objects.filter(user=get_object_or_404(User, request.auth['pk'])).filter(ordered=False) + user_items = Item.objects.filter(user=user, ordered=False) if not user_items: return 400, {'detail': 'Can not create an order of an empty cart.'} ''' - Secondly, checks if there are already an active order. - If True, add the items then merge the duplicates - Else, make a new active order + Secondly, checks if there are no already active order. + If True, make a new active order + Else, add the items then merge the duplicates ''' - order = Order.objects.filter(user=get_object_or_404(User, request.auth['pk']), ordered=False)[0] - if order: - ordered_products_id = list(Item.objects.filter(ordered=True).values('product_id')) - ordered_products_id = list(map(lambda x: x['product_id'], ordered_products_id)) - for item in user_items: - if item.product_id in ordered_products_id: - ordered_item = Item.objects.filter(product_id=item.product_id)[0] - ordered_item.item_qty += item.item_qty - ordered_item.save() - item.delete() - else: - order.items.add(item) + order = Order.objects.filter(user=user, ordered=False) + if not order: + # New order + # Create an order query set and we'll initially use 4 out of 8 attributes in an order + order_qs = Order.objects.create( + user=user, + status=OrderStatus.objects.get(is_default=True), # Which is 'NEW' (must be added in the database) + ref_code=generate_ref_code(), + ordered=False, + ) + # add them to the order + order_qs.items.add(*user_items) # calculate the total - order.total = order.order_total + order_qs.total = order_qs.order_total # mark items as ordered (added to a user order) user_items.update(ordered=True) - order.save() - - return 201, {"detail": "There was already an order and your cart items were merged."} - - # New order - # Create an order query set and we'll initially use 4 out of 8 attributes in an order - order_qs = Order.objects.create( - user=get_object_or_404(User, request.auth['pk']), - status=OrderStatus.objects.get(is_default=True), # Which is 'NEW' - ref_code=generate_ref_code(), - ordered=False, - ) - # add them to the order - order_qs.items.add(*user_items) + order_qs.save() + + return 201, {'detail': 'order created successfully'} + + # get the order from query set to start working on it + order = order[0] + + ordered_products_id = list(Item.objects.filter(ordered=True).values('product_id')) + ordered_products_id = list(map(lambda x: x['product_id'], ordered_products_id)) + for item in user_items: + if item.product_id in ordered_products_id: + ordered_item = Item.objects.filter(product_id=item.product_id)[0] + ordered_item.item_qty += item.item_qty + ordered_item.save() + # after merge delete item + item.delete() + else: + order.items.add(item) # calculate the total - order_qs.total = order_qs.order_total + order.total = order.order_total # mark items as ordered (added to a user order) user_items.update(ordered=True) - order_qs.save() + order.save() - return 201, {'detail': 'order created successfully'} + return 201, {"detail": "There was already an order and your cart items were merged."} @order_controller.post("checkout", auth=GlobalAuth(), response={ 200: MessageOut, - 404: MessageOut + 404: MessageOut, + 401: MessageOut }) def checkout(request, checkout_data_in: CheckoutSchema): - current_order = Order.objects.filter(user=get_object_or_404(User, request.auth['pk'])).filter(ordered=False) + if 'pk' not in request.auth: + return 401, {'detail': 'unauthorized'} + # gets the user from pk + user = User.objects.filter(id=request.auth['pk'])[0] + + current_order = Order.objects.filter(user=user, ordered=False) if current_order: processing = OrderStatus.objects.get(title='PROCESSING') # proccessing must be added current_order.update(**checkout_data_in.dict(), status=processing.id, ordered=True) diff --git a/commerce/schemas.py b/commerce/schemas.py index cc7b0d1..084e6b8 100644 --- a/commerce/schemas.py +++ b/commerce/schemas.py @@ -7,8 +7,6 @@ from commerce.models import Product, Merchant - - class UUIDSchema(Schema): id: UUID4 @@ -107,4 +105,4 @@ class AddressCreate(AddressSchema): class CheckoutSchema(Schema): address: UUID4 - note: str = None \ No newline at end of file + note: str = None