diff --git a/.gitignore b/.gitignore index 6a9dd3c..576f9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ constants.py assets -/**/__pycache__ +!assets/responses.org diff --git a/app/external_services.py b/app/external_services.py index fde34ff..fe610c7 100644 --- a/app/external_services.py +++ b/app/external_services.py @@ -1,8 +1,7 @@ -from secrets import randbits from twilio.rest import Client from constants import ACCOUNT_ID, SMS_SENDER, TOKEN -from database.crud import save_otp +from database.crud import fetch_otp def create_twilio_client(account_sid, auth_token): @@ -10,9 +9,8 @@ def create_twilio_client(account_sid, auth_token): return client -def send_otp(receiver): +def send_otp(data, db): client = create_twilio_client(account_sid=ACCOUNT_ID, auth_token=TOKEN) - code = randbits(k=16) + code = fetch_otp(access_key=data.access_key, db=db) message = "Your OTP code is {0}".format(code) - client.messages.create(to=receiver, from_=SMS_SENDER, body=message) - save_otp(receiver, code) + client.messages.create(to=data.mobile, from_=SMS_SENDER, body=message) diff --git a/app/routes.py b/app/routes.py index 09025e5..2fc1521 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,32 +1,28 @@ -from fastapi import APIRouter, Depends, HTTPException, Response +from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from app.schemas import * from app.external_services import send_otp +from app.schemas import * from database.crud import get_db, insert_data, verify_otp router = APIRouter() -@router.post("/register") -def create_user(request: UserCreate, db: Session = Depends(get_db)): - insert_data(model="Users", data=request, db=db) - send_otp(receiver=request.mobile) - return {"message": "User created, pending OTP verification"} +@router.post("/register", response_model=UserCreateResponse) +def create_user(data: UserCreate, db: Session = Depends(get_db)): + user = insert_data(model="Users", data=data, db=db) + send_otp(data=user, db=db) + return user # FIXME Use OAuth2 for verification -@router.post("/login") -async def log_in(request: UserLogin, response: Response, db: Session = Depends(get_db)): - return {"message": "Logged in successfully"} - # response.status_code = status.HTTP_400_BAD_REQUEST - # return {"message": "The email/password combination is not correct"} +@router.post("/login", response_model=UserLoginResponse) +def log_in(request: UserLogin, db: Session = Depends(get_db)): + pass -@router.post("/otpVerification") -async def validate_otp( - request: OTPVerify, response: Response, db: Session = Depends(get_db) -): +@router.post("/otpVerification", response_model=OTPVerifyResponse) +def validate_otp(request: OTPVerify, db: Session = Depends(get_db)): if verify_otp(data=request, db=db): return {"message": "The OTP has been verified successfully"} raise HTTPException(status_code=400, detail="The OTP is not correct") diff --git a/app/schemas.py b/app/schemas.py index 32c1144..c2fe6d7 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -1,6 +1,9 @@ +from datetime import datetime, timedelta +from secrets import randbits, token_hex +from typing import Optional + from fastapi import Query from pydantic import BaseModel, EmailStr -from typing import Optional class UserBase(BaseModel): @@ -18,6 +21,29 @@ class UserCreate(UserBase): user_image: Optional[str] = None device_type: int = Query(None, ge=1, le=2) city_id: int + access_key: str = token_hex() + otp: int = randbits(16) + otp_valid_time: datetime = datetime.now() + timedelta(minutes=10) + + class Config: + orm_mode = True + + +class UserCreateResponse(UserBase): + id: int + full_name: str + password: str + gender: int = Query(None, ge=1, le=3) + mobile: str = Query(None, min_length=8, max_length=13) + user_image: Optional[str] = None + device_type: int = Query(None, ge=1, le=2) + access_key: str + otp: int + otp_valid_time: datetime + status: int = Query(None, ge=0, le=1) + admin_status: int = Query(None, ge=0, le=1) + created: datetime + updated: datetime = Query(None) class Config: orm_mode = True @@ -30,6 +56,13 @@ class UserLogin(UserBase): orm_mode = True +class UserLoginResponse(UserBase): + pass + + class Config: + orm_mode = True + + class SocialLogin(UserBase): type: int = Query(None, ge=1, le=2) social_id: str @@ -41,21 +74,21 @@ class SocialLogin(UserBase): orm_mode = True -class CreateResponse(UserBase): - id: int - full_name: str - password: str - gender: int = Query(None, ge=1, le=3) - mobile: str = Query(None, min_length=8, max_length=13) - user_image: Optional[str] = None - device_type: int = Query(None, ge=1, le=2) - otp: int = Query(None, ge=6, le=6) +class OTPVerify(BaseModel): + access_key: str + otp: int + + class Config: + orm_mode = True + + +class OTPVerifyResponse(BaseModel): + pass class Config: orm_mode = True -class OTPVerify(BaseModel): access_key: str otp: int = Query(None, ge=6, le=6) diff --git a/database/__init__.py b/database/__init__.py index 5b860bf..c33457d 100644 --- a/database/__init__.py +++ b/database/__init__.py @@ -2,7 +2,7 @@ from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker -from constants import DB +from constants import TESTING_DB as DB engine = create_engine(DB, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/database/crud.py b/database/crud.py index 673db36..6a7814f 100644 --- a/database/crud.py +++ b/database/crud.py @@ -24,12 +24,13 @@ def insert_data(model, data, db): db.add(item) db.commit() db.refresh(item) + return item # FIXME db.id has to be replaced with the table's UID -def delete_data(schema, data, db): - model = schema.replace('"', "") - result = db.query(model).filter(model.email == data.email).delete() +def delete_data(model, data, db): + item = instantiate_model(model=model, data=data) + result = db.query(item).filter(item.email == data.email).delete() return result @@ -38,22 +39,14 @@ def fetch_user(data, db): return result -def fetch_otp(data: OTPVerify, db): - result = db.query(Users).filter(Users.access_key == data.access_key).first() - return result - - -def save_otp(data: OTPVerify, db): - db.query(Users).filter(Users.access_key == data.access_key).update( - {Users.otp: data.otp} - ) - db.commit() +def fetch_otp(access_key, db): + result = db.query(Users).filter(Users.access_key == access_key).first() + return result.otp def activate_account(data: OTPVerify, db): - timestamp = datetime.now() + timedelta(minutes=10) db.query(Users).filter(Users.access_key == data.access_key).update( - {Users.otp_valid_time: timestamp, Users.status: 1} + {Users.status: 1} ) db.commit() diff --git a/database/models.py b/database/models.py index 2dcfd64..66fa818 100644 --- a/database/models.py +++ b/database/models.py @@ -1,4 +1,3 @@ -from secrets import token_hex from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, String, Text, text from sqlalchemy.sql import func @@ -21,7 +20,7 @@ class Users(Base): user_type = Column(Integer) otp = Column(String(255)) otp_valid_time = Column(DateTime) - access_key = Column(Text, unique=True, default=token_hex) + access_key = Column(Text, unique=True) lang_type = Column(Integer) badge = Column(Integer, server_default=text("0")) status = Column(Integer, server_default=text("0"))