From 5df6ff8a398957ccbba6822b360c0cd4954d23db Mon Sep 17 00:00:00 2001 From: Henry Arnold Date: Mon, 17 Nov 2025 14:32:13 +0300 Subject: [PATCH] fix: Debug the Apps services, enhance the farmer story --- .idea/misc.xml | 3 + app/auth/jwt_handler.py | 7 ++ app/db.py | 10 +- app/services/farmer_service.py | 203 +++++++++++++++++++-------------- app/services/user_service.py | 167 ++++++++++++++++----------- app/utils/token.py | 3 +- fcenv/bin/dotenv | 2 +- fcenv/bin/fastapi | 2 +- fcenv/bin/pip | 2 +- fcenv/bin/pip3 | 2 +- fcenv/bin/pip3.12 | 2 +- 11 files changed, 243 insertions(+), 160 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index ed30124..3f2af2c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,7 @@ + + \ No newline at end of file diff --git a/app/auth/jwt_handler.py b/app/auth/jwt_handler.py index ef77d43..b6a58b1 100644 --- a/app/auth/jwt_handler.py +++ b/app/auth/jwt_handler.py @@ -1,3 +1,5 @@ +from datetime import timedelta, datetime + import jwt import os import dotenv @@ -10,6 +12,11 @@ SECRET_KEY = os.getenv("SECRET_KEY") ALGORITHM = "HS256" +async def create_access_token(subject: dict, expires_delta: timedelta = timedelta(minutes=5)): + to_encode = subject.copy() + to_encode.update({"exp": datetime.utcnow() + expires_delta}) + + async def get_current_user(request: Request): token = request.headers.get("Authorization") if not token or not token.startswith("Bearer "): diff --git a/app/db.py b/app/db.py index 92a2d78..11bd6e3 100644 --- a/app/db.py +++ b/app/db.py @@ -10,11 +10,11 @@ async def init_db_pool(): db_pool = await asyncpg.create_pool( - user=os.getenv(""), - password=os.getenv(""), - database=os.getenv(""), - host=os.getenv(""), - port=os.getenv(""), + user=os.getenv("DB_USER"), + password=os.getenv("DB_PASSWORD"), + database=os.getenv("DB_NAME"), + host=os.getenv("DB_HOST"), + port=os.getenv("DB_PORT"), min_size=2, # minimum number of connections max_size=10, # maximum number of connections ) diff --git a/app/services/farmer_service.py b/app/services/farmer_service.py index 344e1bd..aa3dc72 100644 --- a/app/services/farmer_service.py +++ b/app/services/farmer_service.py @@ -1,99 +1,136 @@ # farmer service business logic +import asyncpg import uuid -from uuid import uuid4 -from datetime import datetime from fastapi import HTTPException, status -from app.db.connection import get_db_connection -from app.auth.jwt_handler import create_access_token -from app.auth.password_utils import hash_password, validate_password_strength, verify_password +from app.utils.token import create_access_token +from app.auth.password_utils import ( + hash_password, + validate_password_strength, + verify_password +) +import logging + +logger = logging.getLogger(__name__) class FarmerService: - def __init__(self): - self.conn = get_db_connection() - self.cursor = self.conn.cursor() + def __init__(self, db_pool: asyncpg.pool.Pool): + self.db_pool = db_pool - def register_farmer(self, email: str, password: str, username: str, role: str): - # Generate farmer_id + async def register_farmer(self, email: str, password: str, username: str, role: str): farmer_id = str(uuid.uuid4())[:8] hashed_password = hash_password(password) - # Check password strength if not validate_password_strength(password): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Password strength is too weak" ) - - # Check if the farmer already exists in the database - self.cursor.execute("SELECT id FROM farmers WHERE email = %s AND username = %s", (email, username)) - if self.cursor.fetchone(): - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="Farmer with this email or username already exists" - ) - - try: - # Insert the farmer into the database - self.cursor.execute( - "INSERT INTO farmers (id, username, email, password, role)", - (farmer_id, username, email, hashed_password, role) - ) - farmer_id = self.cursor.fetchone()[0] - self.conn.commit() - return {"message": "Farmer registered successfully", "farmer_id": farmer_id} - - except Exception as e: - self.conn.rollback() - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"An error occurred while registering the user: {str(e)}" - ) - finally: - self._cleanup() - - - def add_products(self, farmer_id, product_name, product_category, quantity, date=datetime.now()): - # method to add a product(s) to the database - # product must have a product id - product_id = str(uuid.uuid4())[:5] - - #check if product already exists, if yes then only add its quantity - self.cursor.execute("SELECT product_id FROM products WHERE farmer_id = %s", (farmer_id,)) - if self.cursor.fetchone(): - return {"message": "This product already exists!"} - - - #def add_product(self, farmer_id, product_name, product_category, quantity, date=datetime.now): - # # method to add a product(s) to the database - # # product must have a product id - # try: - # connection = get_db_connection() - # cursor = connection.cursor() - # - # connection.autocommit = False -# - # self.cursor.execute( - # "INSERT INTO products (farmer_id, product_name, product_category, quantity, date)", - # (farmer_id, product_name, product_category, quantity, date) - # ) - # self.connection.commit() - # except Exception as e: - # self.conn.rollback() - # raise HTTPException( - # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - # details=f"Error adding products to the database! {str(e)}" - # ) -# - # finally: - # self._cleanup() - - - - - def _cleanup(self): - if self.cursor: - self.cursor.close() - if self.conn: - self.conn.close() + async with self.db_pool.acquire() as conn: + try: + existing = await conn.fetchrow( + """ + SELECT id FROM farmers + WHERE email = $1 OR username = $2 + """, + email, + username + ) + + if existing: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Farmer with this email or username already exists" + ) + + await conn.execute( + """ + INSERT INTO farmers (id, username, email, password, role) + VALUES ($1, $2, $3, $4, $5) + """, + farmer_id, + username, + email, + hashed_password, + role + ) + + return {"message": "Farmer registered successfully", "farmer_id": farmer_id} + + except HTTPException: + raise + + except Exception as e: + logger.error( + f"Farmer Registration Request: Error occurred while registering farmer: {e}" + ) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An error occurred while registering the user." + ) + + async def get_farmer_by_email(self, email: str): + async with self.db_pool.acquire() as conn: + try: + farmer = await conn.fetchrow( + "SELECT * FROM farmers WHERE email = $1", + email + ) + + if not farmer: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Farmer with this email does not exist" + ) + + return dict(farmer) + + except HTTPException: + raise + + except Exception as e: + logger.error( + f"Farmer Fetch Request: Database error fetching farmer '{email}': {e}" + ) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An error occurred while getting the farmer." + ) + + async def farmer_login(self, email: str, password: str): + async with self.db_pool.acquire() as conn: + try: + farmer = await conn.fetchrow( + "SELECT id, password FROM farmers WHERE email = $1", + email + ) + + if not farmer: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Farmer with this email does not exist" + ) + + farmer_id = farmer["id"] + hashed_password = farmer["password"] + + if not verify_password(password, hashed_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect password" + ) + + token = create_access_token({"farmer_id": farmer_id}) + return {"access_token": token} + + except HTTPException: + raise + except Exception as e: + logger.error( + f"Farmer Login Request: Error occurred during login: {e}" + ) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An error occurred while trying to log in." + ) diff --git a/app/services/user_service.py b/app/services/user_service.py index dc4975d..0cbf4d5 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -1,98 +1,135 @@ # user service business logic import uuid -from uuid import uuid4 import logging import asyncpg -from typing import List -from datetime import datetime from fastapi import HTTPException, status -from app.db import get_db_pool from app.utils.token import create_access_token -from app.auth.password_utils import validate_password_strength, hash_password, verify_password +from app.auth.password_utils import ( + validate_password_strength, + hash_password, + verify_password +) -logging.basicConnfig(level=logging.INFO) +logger = logging.getLogger(__name__) class UserService: def __init__(self, db_pool: asyncpg.pool.Pool): self.db_pool = db_pool async def register_user(self, email: str, password: str, phone: str, username: str, role: str): - # Generate user_id and hash password user_id = str(uuid.uuid4())[:8] hashed_password = hash_password(password) - # Check password strength if not validate_password_strength(password): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Password is too weak!" ) - - existing = await self.conn.fetchrow( - "SELECT id FROM users WHERE email = $1 OR username = $2", email, username - ) - if existing: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="User already exists" - ) - - try: - row = await self.conn.fetchrow( - "INSERT INTO users (id, username, email, password, role) VALUES ($1, $2, $3, $4, $5) RETURNING id", - user_id, username, email, hashed_password, phone, role - ) - return {"message": "User registered Successfully", "user_id": row["id"]} - except Exception as e: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error registering user: {str(e)}" - ) - def get_user_by_email(self, email: str) -> None: - try: - self.cursor.execute("SELECT * FROM users WHERE email = %s", (email)) - user = self.cursor.fetchone() + async with self.db_pool.acquire() as conn: + try: + existing = await conn.fetchrow( + """ + SELECT id FROM users + WHERE email = $1 OR username = $2 + """, + email, + username + ) + + if existing: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="User already exists" + ) - if not user: + row = await conn.fetchrow( + """ + INSERT INTO users (id, username, email, password, phone, role) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id + """, + user_id, + username, + email, + hashed_password, + phone, + role + ) + + return { + "message": "User registered successfully", + "user_id": row["id"] + } + + except HTTPException: + raise + + except Exception as e: + logger.error(f"User Registration Request: Error registering user: {e}") raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="User not found" + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Error registering user" ) - email = user - - except Exception as e: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Search failed!: str{e}" - ) + async def get_user_by_email(self, email: str): + async with self.db_pool.acquire() as conn: + try: + user = await conn.fetchrow( + "SELECT * FROM users WHERE email = $1", + email + ) - def user_login(self, email: str, password: str): - try: - self.cursor.execute( - "SELECT id, password FROM users WHERE email = %s", (email,) - ) - user = self.cursor.fetchone() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + return dict(user) + + except HTTPException: + raise - if not user: + except Exception as e: + logger.error(f"User Fetch Request: Error fetching user by email '{email}': {e}") raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="User not found" + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Error fetching user" ) - - user_id, hashed_password = user - if not verify_password(password, hashed_password): + async def user_login(self, email: str, password: str): + async with self.db_pool.acquire() as conn: + try: + user = await conn.fetchrow( + "SELECT id, password FROM users WHERE email = $1", + email + ) + + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + user_id = user["id"] + hashed_password = user["password"] + + if not verify_password(password, hashed_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect password" + ) + + token = create_access_token({"user_id": user_id}) + return {"access_token": token} + + except HTTPException: + raise + + except Exception as e: + logger.error(f"Login failed: {e}") raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect password" + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Login failed" ) - token = create_access_token({"user_id": user_id}) - return {"access_token": token} - - except Exception as e: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Login failed: {str(e)}" - ) diff --git a/app/utils/token.py b/app/utils/token.py index cb9f60a..85cbc42 100644 --- a/app/utils/token.py +++ b/app/utils/token.py @@ -1,5 +1,4 @@ import jwt -from app.services.user_service import UserService from datetime import datetime, timedelta, timezone from jwt.exceptions import ExpiredSignatureError, InvalidTokenError @@ -7,7 +6,7 @@ ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES=30 -def create_access_token(data: dict, expires_delta: timedelta = None): +def create_access_token(data: dict, user_id: str, expires_delta: timedelta = None): if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: diff --git a/fcenv/bin/dotenv b/fcenv/bin/dotenv index 66b91bf..a4c17eb 100755 --- a/fcenv/bin/dotenv +++ b/fcenv/bin/dotenv @@ -1,4 +1,4 @@ -#!/home/henry-arnold/farmclient/farm-client/fcenv/bin/python3 +#!/home/henry-arnold/PycharmProjects/farm-client/fcenv/bin/python # -*- coding: utf-8 -*- import re import sys diff --git a/fcenv/bin/fastapi b/fcenv/bin/fastapi index 509f223..d5bfae5 100755 --- a/fcenv/bin/fastapi +++ b/fcenv/bin/fastapi @@ -1,4 +1,4 @@ -#!/home/henry-arnold/farmclient/farm-client/fcenv/bin/python3 +#!/home/henry-arnold/PycharmProjects/farm-client/fcenv/bin/python # -*- coding: utf-8 -*- import re import sys diff --git a/fcenv/bin/pip b/fcenv/bin/pip index a2631b0..52d242c 100755 --- a/fcenv/bin/pip +++ b/fcenv/bin/pip @@ -1,4 +1,4 @@ -#!/home/henry-arnold/farmclient/farm-client/fcenv/bin/python3 +#!/home/henry-arnold/PycharmProjects/farm-client/fcenv/bin/python # -*- coding: utf-8 -*- import re import sys diff --git a/fcenv/bin/pip3 b/fcenv/bin/pip3 index a2631b0..52d242c 100755 --- a/fcenv/bin/pip3 +++ b/fcenv/bin/pip3 @@ -1,4 +1,4 @@ -#!/home/henry-arnold/farmclient/farm-client/fcenv/bin/python3 +#!/home/henry-arnold/PycharmProjects/farm-client/fcenv/bin/python # -*- coding: utf-8 -*- import re import sys diff --git a/fcenv/bin/pip3.12 b/fcenv/bin/pip3.12 index a2631b0..52d242c 100755 --- a/fcenv/bin/pip3.12 +++ b/fcenv/bin/pip3.12 @@ -1,4 +1,4 @@ -#!/home/henry-arnold/farmclient/farm-client/fcenv/bin/python3 +#!/home/henry-arnold/PycharmProjects/farm-client/fcenv/bin/python # -*- coding: utf-8 -*- import re import sys