Compare commits
No commits in common. "56bc0f2d69775054dd0f8c090308f820e26c5ebb" and "e6ed37ad5e4e1af760b194e80877e78128ca13f1" have entirely different histories.
56bc0f2d69
...
e6ed37ad5e
215
README.md
215
README.md
@ -1,215 +0,0 @@
|
||||
# SHOP API
|
||||
Simple API (still WIP)
|
||||
|
||||
# Routes
|
||||
|
||||
## Hello World
|
||||
### `GET /`
|
||||
- **Description:** A simple route that returns a JSON message saying 'Hello, Flask!'.
|
||||
- **Response:**
|
||||
- JSON with the following structure:
|
||||
```json
|
||||
{
|
||||
"message": "Hello, Flask!"
|
||||
}
|
||||
```
|
||||
- **Status Code:**
|
||||
- 200: Success.
|
||||
|
||||
|
||||
## Users
|
||||
### `POST /register`
|
||||
- **Description:** Register a new user.
|
||||
- **Request Body:**
|
||||
- JSON with the following fields:
|
||||
- `username` (string): User's username.
|
||||
- `displayname` (string): User's display name.
|
||||
- `email` (string): User's email address.
|
||||
- `password` (string): User's password.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the registration.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if any required field is missing).
|
||||
|
||||
### `POST /login`
|
||||
- **Description:** Log in a user.
|
||||
- **Request Body:**
|
||||
- JSON with the following fields:
|
||||
- `username` (string): User's username.
|
||||
- `password` (string): User's password.
|
||||
- **Response:**
|
||||
- JSON containing authentication token and user information.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if any required field is missing).
|
||||
|
||||
### `DELETE /logout`
|
||||
- **Description:** Log out a user by invalidating the JWT token.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the logout.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `PUT /update/username`
|
||||
- **Description:** Update the username of the authenticated user.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Request Body:**
|
||||
- JSON with the following field:
|
||||
- `new_username` (string): New username.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the username update.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if new_username is missing).
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `PUT /update/displayname`
|
||||
- **Description:** Update the display name of the authenticated user.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Request Body:**
|
||||
- JSON with the following field:
|
||||
- `new_displayname` (string): New display name.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the display name update.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if new_displayname is missing).
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `PUT /update/email`
|
||||
- **Description:** Update the email address of the authenticated user.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Request Body:**
|
||||
- JSON with the following field:
|
||||
- `new_email` (string): New email address.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the email update.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if new_email is missing).
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `PUT /update/password`
|
||||
- **Description:** Update the password of the authenticated user.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Request Body:**
|
||||
- JSON with the following field:
|
||||
- `new_password` (string): New password.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the password update.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if new_password is missing).
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `DELETE /delete`
|
||||
- **Description:** Delete the account of the authenticated user.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the account deletion.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
## Cart
|
||||
### `GET /`
|
||||
- **Description:** Retrieve the contents of the user's shopping cart.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Response:**
|
||||
- JSON containing a list of dictionaries representing the cart contents.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `PUT /add/<int:product_id>`
|
||||
- **Description:** Add a specified quantity of a product to the user's shopping cart.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Parameters:**
|
||||
- `count` (optional, int): Quantity of the product to add. Defaults to 1.
|
||||
- **Response:**
|
||||
- JSON indicating the success of adding the product to the cart.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if count is less than 1).
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `DELETE /remove/<int:product_id>`
|
||||
- **Description:** Remove a specific product from the user's shopping cart.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Response:**
|
||||
- JSON indicating the success of removing the product from the cart.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `PUT /update/<int:product_id>`
|
||||
- **Description:** Update the quantity of a product in the user's shopping cart.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Parameters:**
|
||||
- `count` (int): New quantity of the product.
|
||||
- **Response:**
|
||||
- JSON indicating the success of updating the product quantity in the cart.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if count is missing or not a valid integer).
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
### `GET /purchase`
|
||||
- **Description:** Complete a purchase, transferring items from the user's cart to the purchase history.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the purchase.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
||||
|
||||
## Products
|
||||
### `GET /get`
|
||||
- **Description:** Retrieve a paginated list of products.
|
||||
- **Parameters:**
|
||||
- `page` (optional, int): Page number for pagination. Defaults to 0.
|
||||
- **Response:**
|
||||
- JSON containing a list of products.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if the page is less than 0).
|
||||
|
||||
### `GET /<int:id>`
|
||||
- **Description:** Retrieve information about a specific product.
|
||||
- **Parameters:**
|
||||
- `id` (int): Product identifier.
|
||||
- `fields` (optional, string): Comma-separated list of fields to retrieve (e.g., 'name,price,image').
|
||||
- **Response:**
|
||||
- JSON containing information about the specified product.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if invalid fields are provided).
|
||||
|
||||
### `POST /create`
|
||||
- **Description:** Create a new product listing.
|
||||
- **Authentication:**
|
||||
- Requires a valid JWT token.
|
||||
- **Request Body:**
|
||||
- JSON with the following fields:
|
||||
- `name` (string): Product name.
|
||||
- `price` (float): Product price.
|
||||
- **Response:**
|
||||
- JSON indicating the success of the product listing creation.
|
||||
- **Status Codes:**
|
||||
- 200: Success.
|
||||
- 400: Bad Request (if name or price is missing or if price is not a valid float).
|
||||
- 401: Unauthorized (if JWT token is missing or invalid).
|
@ -1,10 +1,8 @@
|
||||
from flask import Flask
|
||||
from flask_jwt_extended import JWTManager
|
||||
from flask_mail import Mail
|
||||
|
||||
app = Flask(__name__)
|
||||
jwt_manager = JWTManager(app)
|
||||
mail = Mail(app)
|
||||
|
||||
def create_app():
|
||||
from app.api import bp, bp_errors, bp_product, bp_user, bp_cart
|
||||
|
@ -2,7 +2,7 @@ from flask import Blueprint
|
||||
|
||||
bp_errors = Blueprint('errors', __name__)
|
||||
bp = Blueprint('api', __name__)
|
||||
bp_product = Blueprint('products', __name__, url_prefix="/products")
|
||||
bp_product = Blueprint('product', __name__, url_prefix="/product")
|
||||
bp_user = Blueprint('user', __name__, url_prefix="/user")
|
||||
bp_cart = Blueprint('cart', __name__, url_prefix="/cart")
|
||||
|
||||
|
@ -1,52 +1,20 @@
|
||||
from flask import jsonify, abort, request
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
|
||||
from app.api import bp_product
|
||||
|
||||
from app.services.product_service import ProductService
|
||||
|
||||
@bp_product.route('/get', methods=['GET'])
|
||||
def get_products():
|
||||
page = request.args.get('page', default=0, type=int)
|
||||
|
||||
if page < 0:
|
||||
return abort(400)
|
||||
|
||||
result, status_code = ProductService.get_products(page)
|
||||
|
||||
return result, status_code
|
||||
|
||||
@bp_product.route('/<int:id>', methods=['GET'])
|
||||
def get_product_info(id: int):
|
||||
fields = ['name', 'price', 'image', 'image_name', 'seller']
|
||||
|
||||
fields_param = request.args.get('fields')
|
||||
requested_fields = []
|
||||
|
||||
fields_param_list = fields_param.split(',') if fields_param else fields
|
||||
|
||||
common_fields = list(set(fields) & set(fields_param_list))
|
||||
|
||||
result, status_code = ProductService.get_product_info(common_fields, id)
|
||||
|
||||
return result, status_code
|
||||
|
||||
return abort(501)
|
||||
|
||||
|
||||
@bp_product.route('/create', methods=['POST'])
|
||||
@jwt_required()
|
||||
def create_product_listing():
|
||||
user_id = get_jwt_identity()
|
||||
name = request.json.get('name')
|
||||
price = request.json.get('price')
|
||||
|
||||
if name is None or price is None:
|
||||
return abort(400)
|
||||
|
||||
float_price = float(price)
|
||||
|
||||
if not isinstance(float_price, float):
|
||||
return abort(400)
|
||||
|
||||
result, status_code = ProductService.create_listing(user_id, name, float_price)
|
||||
|
||||
return result, status_code
|
@ -20,9 +20,3 @@ class FlaskTesting:
|
||||
DEBUG = True
|
||||
JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY')
|
||||
SERVER_NAME = os.environ.get('HOST') + ':' + os.environ.get('PORT')
|
||||
|
||||
MAIL_SERVER = os.environ.get('MAIL_SERVER')
|
||||
MAIL_PORT = os.environ.get('MAIL_USERNAME')
|
||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS')
|
@ -12,6 +12,8 @@ db_connection = mysql.connector.connect(
|
||||
database=MySqlConfig.MYSQL_DATABASE,
|
||||
)
|
||||
|
||||
db_cursor = db_connection.cursor(dictionary=True)
|
||||
|
||||
jwt_redis_blocklist = redis.StrictRedis(
|
||||
host=RedisConfig.REDIS_HOST,
|
||||
port=RedisConfig.REDIS_PORT,
|
||||
|
@ -1,15 +0,0 @@
|
||||
from flask_mail import Message
|
||||
|
||||
from app import mail
|
||||
|
||||
|
||||
def send_mail(subject: str, recipient: str, body: str):
|
||||
msg = Message(subject, recipients=[recipient])
|
||||
msg.body = body
|
||||
|
||||
try:
|
||||
mail.send(msg)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Failed to send email. Error: {e}")
|
||||
return False
|
@ -7,17 +7,6 @@ class CartService:
|
||||
|
||||
@staticmethod
|
||||
def add_to_cart(user_id: str, product_id: int, count: int) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Adds a product to a user's cart.
|
||||
|
||||
:param user_id: User ID.
|
||||
:type user_id: str
|
||||
:param product_id: ID of product to be added.
|
||||
:type product_id: int
|
||||
:return: Tuple containing a dictionary with a token and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
with db_connection.cursor(dictionary=True) as cursor:
|
||||
cursor.execute("select count from cart_item where cart_id = %s and product_id = %s", (user_id, product_id))
|
||||
@ -37,19 +26,6 @@ class CartService:
|
||||
|
||||
@staticmethod
|
||||
def update_count(user_id: str, product_id: int, count: int) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Updates count of products in user's cart
|
||||
|
||||
:param user_id: User ID.
|
||||
:type user_id: str
|
||||
:param product_id: ID of product to be updated.
|
||||
:type product_id: int
|
||||
:param count: New count of products
|
||||
:type count: int
|
||||
:return: Tuple containing a dictionary with a token and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
if count <= 0:
|
||||
return CartService.delete_from_cart(user_id, product_id)
|
||||
@ -65,18 +41,6 @@ class CartService:
|
||||
|
||||
@staticmethod
|
||||
def delete_from_cart(user_id: str, product_id: int) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Completely deletes an item from a user's cart
|
||||
|
||||
:param user_id: User ID.
|
||||
:type user_id: str
|
||||
:param product_id: ID of product to be updated.
|
||||
:type product_id: int
|
||||
:return: Tuple containing a dictionary with a token and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
|
||||
try:
|
||||
with db_connection.cursor() as cursor:
|
||||
cursor.execute("delete from cart_item where cart_id = %s and product_id = %s", (user_id, product_id))
|
||||
@ -89,15 +53,6 @@ class CartService:
|
||||
|
||||
@staticmethod
|
||||
def show_cart(user_id: str) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Gives the user the content of their cart
|
||||
|
||||
:param user_id: User ID.
|
||||
:type user_id: str
|
||||
:return: Tuple containing a dictionary with a token and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
with db_connection.cursor(dictionary=True) as cursor:
|
||||
cursor.execute("select product.name as product_name, count, price_subtotal, date_added from cart_item inner join product on cart_item.product_id = product.id where cart_item.cart_id = %s", (user_id,))
|
||||
@ -123,15 +78,6 @@ class CartService:
|
||||
|
||||
@staticmethod
|
||||
def purchase(user_id: str) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
"Purchases" the contents of user's cart
|
||||
|
||||
:param user_id: User ID.
|
||||
:type user_id: str
|
||||
:return: Tuple containing a dictionary with a token and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
with db_connection.cursor(dictionary=True) as cursor:
|
||||
# get all cart items
|
||||
|
@ -3,125 +3,82 @@ import base64
|
||||
from flask import abort
|
||||
from mysql.connector import Error
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from app.extensions import db_connection
|
||||
|
||||
class ProductService:
|
||||
|
||||
@staticmethod
|
||||
def get_products(page: int):
|
||||
"""
|
||||
Fetches 10 products
|
||||
def get_name(product_id: int):
|
||||
db_cursor.execute(f"select name from product where product.id = {product_id}")
|
||||
|
||||
:param page: Page, aka offset of fetching
|
||||
:type page: int
|
||||
"""
|
||||
if db_cursor.rowcount != 1:
|
||||
return abort(404)
|
||||
|
||||
try:
|
||||
with db_connection.cursor(dictionary=True) as cursor:
|
||||
|
||||
offset = 10 * page
|
||||
cursor.execute("select product.id, user.displayname as seller, product.name, product.price_pc from product inner join user on user.id = product.seller_id order by product.id limit 10 offset %s", (offset,))
|
||||
results = cursor.fetchall()
|
||||
|
||||
if len(results) < 1:
|
||||
return {"Failed": "Failed to fetch products. You've probably selected too far with pages"}, 400
|
||||
|
||||
result_obj = []
|
||||
for row in results:
|
||||
mid_result = {
|
||||
"id": row['id'],
|
||||
"seller": row['seller'],
|
||||
"name": row['name'],
|
||||
"price": row['price_pc']
|
||||
}
|
||||
|
||||
result_obj.append(mid_result)
|
||||
|
||||
return result_obj, 200
|
||||
|
||||
except Error as e:
|
||||
return {"Failed": f"Failed to fetch products. Error: {e}"}, 500
|
||||
result = db_cursor.fetchone()
|
||||
return result['name']
|
||||
|
||||
@staticmethod
|
||||
def get_product_info(fields: list[str], product_id: int):
|
||||
"""
|
||||
Fetches specific product info
|
||||
def get_manufacturer(product_id: int):
|
||||
db_cursor.execute(f"select user.displayname as seller from product inner join user on product.seller_id = user.id where product.id = {product_id}")
|
||||
|
||||
:param fields: array of fields that can be fetched
|
||||
:type fields: list[str]
|
||||
:param product_id: ID of product to be updated.
|
||||
:type product_id: int
|
||||
"""
|
||||
if db_cursor.rowcount != 1:
|
||||
return abort(404)
|
||||
|
||||
try:
|
||||
with db_connection.cursor(dictionary=True) as cursor:
|
||||
fields_to_sql = {
|
||||
"name": "product.name",
|
||||
"price": "product.price_pc as price",
|
||||
"image": "product.image",
|
||||
"image_name": "product.image_name",
|
||||
"seller": "user.displayname as seller"
|
||||
}
|
||||
field_sql = []
|
||||
|
||||
for field in fields:
|
||||
field_sql.append(fields_to_sql[field])
|
||||
|
||||
select_params = ", ".join(field_sql)
|
||||
|
||||
if "seller" in fields:
|
||||
cursor.execute(f"select {select_params} from product inner join user on user.id = product.seller_id where product.id = %s", (product_id,))
|
||||
else:
|
||||
cursor.execute(f"select {select_params} from product where id = %s", (product_id,))
|
||||
|
||||
result = cursor.fetchone()
|
||||
|
||||
if cursor.rowcount != 1:
|
||||
return {"Failed": "Failed to fetch product. Product likely doesn't exist"}, 400
|
||||
|
||||
result_obj = {}
|
||||
|
||||
for field in fields:
|
||||
if field == "image": # Encode image into base64
|
||||
result_obj[field] = base64.b64encode(result[field]).decode('utf-8')
|
||||
else:
|
||||
result_obj[field] = result[field]
|
||||
|
||||
return result_obj, 200
|
||||
except Error as e:
|
||||
return {"Failed": f"Failed to fetch product info. Error: {e}"}, 500
|
||||
result = db_cursor.fetchone()
|
||||
return result['seller']
|
||||
|
||||
@staticmethod
|
||||
def create_listing(seller_id: str, name: str, price: float):
|
||||
"""
|
||||
Creates a new product listing
|
||||
def get_price(product_id: int):
|
||||
db_cursor.execute(f"select price_pc from product where product.id = {product_id}")
|
||||
|
||||
:param seller_id: User ID
|
||||
:type seller_id: str
|
||||
:param name: New product's name
|
||||
:type name: str
|
||||
:param price: New product's price
|
||||
:type price: float
|
||||
"""
|
||||
if db_cursor.rowcount != 1:
|
||||
return abort(404)
|
||||
|
||||
result = db_cursor.fetchone()
|
||||
return result['price_pc']
|
||||
|
||||
@staticmethod
|
||||
def get_image(product_id: int):
|
||||
db_cursor.execute(f"select image from product where product.id = {product_id}")
|
||||
|
||||
if db_cursor.rowcount != 1:
|
||||
return abort(404)
|
||||
|
||||
result = db_cursor.fetchone()
|
||||
return base64.b64encode(result['image']).decode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def get_image_name(product_id: int):
|
||||
db_cursor.execute(f"select image_name from product where product.id = {product_id}")
|
||||
|
||||
if db_cursor.rowcount != 1:
|
||||
return abort(404)
|
||||
|
||||
result = db_cursor.fetchone()
|
||||
return result['image_name']
|
||||
|
||||
@staticmethod
|
||||
def get_all_info(product_id: int):
|
||||
db_cursor.execute(f"select name, user.displayname as seller, price_pc, image_name, image from product inner join user on product.seller_id = user.id where product.id = {product_id}")
|
||||
|
||||
if db_cursor.rowcount != 1:
|
||||
return abort(404)
|
||||
|
||||
result = db_cursor.fetchone()
|
||||
return {
|
||||
"name": result['name'],
|
||||
"seller": result['seller'],
|
||||
"price": result['price_pc'],
|
||||
"image_name": result['image_name'],
|
||||
"image": base64.b64encode(result['image']).decode('utf-8')
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def create_listing(name: str, seller_id: str, price: float, image_name: str, image):
|
||||
try:
|
||||
with db_connection.cursor() as cursor:
|
||||
rounded_price = round(price, 2)
|
||||
|
||||
cursor.execute("insert into product(seller_id, name, price_pc) values (%s, %s, %s)", (seller_id, name, Decimal(str(rounded_price))))
|
||||
db_connection.commit()
|
||||
db_cursor.execute("insert into product(seller_id, name, price_pc, image, image_name) values (%s, %s, %s, %s, %s)", (seller_id, name, price, image, image_name))
|
||||
db_connection.commit()
|
||||
except Error as e:
|
||||
return {"Failed": f"Failed to create product. {e}"}, 400
|
||||
|
||||
return {"Success": "Successfully created new product listing"}, 200
|
||||
|
||||
@staticmethod
|
||||
def __is_base64_jpg(decoded_string):
|
||||
try:
|
||||
# Checking if the decoded data represents a valid JPEG image
|
||||
image_type = imghdr.what(None, decoded_data)
|
||||
return image_type == 'jpeg'
|
||||
except Exception:
|
||||
return False
|
||||
return {"Success", "Successfully created new product listing"}, 200
|
@ -6,11 +6,9 @@ from typing import Tuple, Union
|
||||
from mysql.connector import Error
|
||||
from flask_jwt_extended import create_access_token
|
||||
|
||||
from app.extensions import db_connection
|
||||
from app.extensions import db_cursor, db_connection
|
||||
from app.extensions import jwt_redis_blocklist
|
||||
|
||||
from app.mail_utils import send_mail
|
||||
|
||||
|
||||
class UserService:
|
||||
"""
|
||||
@ -40,33 +38,27 @@ class UserService:
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
if not UserService.__verify_username(username):
|
||||
return {"Failed": "Failed to verify username. Try another username"}, 400
|
||||
|
||||
if not UserService.__verify_displayname(displayname):
|
||||
return {"Failed": "Failed to verify display name. Try another name"}, 400
|
||||
|
||||
if not UserService.__verify_email(email):
|
||||
return {"Failed": "Failed to verify email. Try another email"}, 400
|
||||
|
||||
if not UserService.__verify_password(password):
|
||||
return {"Failed": "Failed to verify password. Try another (stronger) password"}, 400
|
||||
|
||||
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
|
||||
|
||||
try:
|
||||
if not UserService.__verify_username(username):
|
||||
return {"Failed": "Failed to verify username. Try another username"}, 400
|
||||
|
||||
if not UserService.__verify_displayname(displayname):
|
||||
return {"Failed": "Failed to verify display name. Try another name"}, 400
|
||||
|
||||
if not UserService.__verify_email(email):
|
||||
return {"Failed": "Failed to verify email. Try another email"}, 400
|
||||
|
||||
if not UserService.__verify_password(password):
|
||||
return {"Failed": "Failed to verify password. Try another (stronger) password"}, 400
|
||||
|
||||
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
|
||||
|
||||
with db_connection.cursor() as cursor:
|
||||
cursor.execute("insert into user (username, displayname, email, password) values (%s, %s, %s, %s)", (username, displayname, email, hashed_password))
|
||||
db_connection.commit()
|
||||
db_cursor.execute("insert into user (username, displayname, email, password) values (%s, %s, %s, %s)", (username, displayname, email, hashed_password))
|
||||
db_connection.commit()
|
||||
except Error as e:
|
||||
print(f"Error: {e}")
|
||||
return {"Failed": "Failed to insert into database. Username or email are likely in use already"}, 500
|
||||
|
||||
# TODO Implement mail sending
|
||||
# Currently throws error - connection refused
|
||||
|
||||
# send_mail("Successfully registered!", email, "Congratulations! Your account has been successfully created.\nThis mail also serves as a test that the email address is correct")
|
||||
|
||||
return {"Success": "User created successfully"}, 200
|
||||
|
||||
@staticmethod
|
||||
@ -81,29 +73,24 @@ class UserService:
|
||||
:return: Tuple containing a dictionary with a token and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
try:
|
||||
with db_connection.cursor(dictionary=True) as cursor:
|
||||
|
||||
cursor.execute("select id, password from user where username = %s", (username,))
|
||||
result = db_cursor.fetchone()
|
||||
db_cursor.execute("select id, password from user where username = %s", (username,))
|
||||
result = db_cursor.fetchone()
|
||||
|
||||
user_id = result['id']
|
||||
password_hash = result['password']
|
||||
user_id = result['id']
|
||||
password_hash = result['password']
|
||||
|
||||
if user_id is None:
|
||||
return {"Failed": "Username not found"}, 400
|
||||
if user_id is None:
|
||||
return {"Failed": "Username not found"}, 400
|
||||
|
||||
if not bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8')):
|
||||
return {"Failed": "Incorrect password"}, 401
|
||||
if not bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8')):
|
||||
return {"Failed": "Incorrect password"}, 401
|
||||
|
||||
expire = datetime.timedelta(hours=1)
|
||||
expire = datetime.timedelta(hours=1)
|
||||
|
||||
token = create_access_token(identity=user_id, expires_delta=expire)
|
||||
token = create_access_token(identity=user_id, expires_delta=expire)
|
||||
|
||||
return {"token": token}, 200
|
||||
|
||||
except Error as e:
|
||||
return {"Failed": f"Failed to login. Error: {e}"}, 500
|
||||
return {"token": token}, 200
|
||||
|
||||
@staticmethod
|
||||
def logout(jti, exp) -> Tuple[Union[dict, str], int]:
|
||||
@ -123,19 +110,9 @@ class UserService:
|
||||
|
||||
@staticmethod
|
||||
def delete_user(user_id: str) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Deletes a user account.
|
||||
|
||||
:param user_id: User ID.
|
||||
:type user_id: str
|
||||
:return: Tuple containing a dictionary and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
with db_connection.cursor() as cursor:
|
||||
cursor.execute("delete from user where id = %s", (user_id,))
|
||||
db_connection.commit()
|
||||
db_cursor.execute("delete from user where id = %s", (user_id,))
|
||||
db_connection.commit()
|
||||
except Error as e:
|
||||
return {"Failed": f"Failed to delete user. {e}"}, 500
|
||||
|
||||
@ -154,13 +131,12 @@ class UserService:
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
if not UserService.__verify_email(new_email):
|
||||
return {"Failed": "Failed to verify email. Try another email"}, 400
|
||||
if not UserService.__verify_email(new_email):
|
||||
return {"Failed": "Failed to verify email. Try another email"}, 400
|
||||
|
||||
with db_connection.cursor() as cursor:
|
||||
cursor.execute("update user set email = %s where id = %s", (new_email, user_id))
|
||||
db_connection.commit()
|
||||
try:
|
||||
db_cursor.execute("update user set email = %s where id = %s", (new_email, user_id))
|
||||
db_connection.commit()
|
||||
except Error as e:
|
||||
return {"Failed": f"Failed to update email. Email is likely in use already. Error: {e}"}, 500
|
||||
|
||||
@ -179,13 +155,12 @@ class UserService:
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
if not UserService.__verify_name(new_username):
|
||||
return {"Failed": "Failed to verify username. Try another one"}, 400
|
||||
if not UserService.__verify_name(new_username):
|
||||
return {"Failed": "Failed to verify username. Try another one"}, 400
|
||||
|
||||
with db_connection.cursor() as cursor:
|
||||
cursor.execute("update user set username = %s where id = %s", (new_username, user_id))
|
||||
db_connection.commit()
|
||||
try:
|
||||
db_cursor.execute("update user set username = %s where id = %s", (new_username, user_id))
|
||||
db_connection.commit()
|
||||
except Error as e:
|
||||
return {"Failed": f"Failed to update username. Username is likely in use already. Error: {e}"}, 500
|
||||
|
||||
@ -204,15 +179,14 @@ class UserService:
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
if not UserService.__verify_password(new_password):
|
||||
return {"Failed": "Failed to verify password. Try another (stronger) one"}, 400
|
||||
|
||||
hashed_password = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt())
|
||||
|
||||
try:
|
||||
if not UserService.__verify_password(new_password):
|
||||
return {"Failed": "Failed to verify password. Try another (stronger) one"}, 400
|
||||
|
||||
hashed_password = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt())
|
||||
|
||||
with db_connection.cursor() as cursor:
|
||||
cursor.execute("update user set password = %s where id = %s", (new_username, user_id))
|
||||
db_connection.commit()
|
||||
db_cursor.execute("update user set password = %s where id = %s", (new_username, user_id))
|
||||
db_connection.commit()
|
||||
except Error as e:
|
||||
return {"Failed": f"Failed to update password. Error: {e}"}, 500
|
||||
|
||||
|
@ -4,4 +4,3 @@ mysql-connector-python==8.3.0
|
||||
python-dotenv==1.0.1
|
||||
Flask-JWT-Extended==4.5.3
|
||||
PyJWT==2.8.0
|
||||
Flask-Mail==0.9.1
|
Loading…
x
Reference in New Issue
Block a user