diff --git a/api.py b/api.py new file mode 100644 index 0000000..0b08dd3 --- /dev/null +++ b/api.py @@ -0,0 +1,68 @@ +# Imports +from contextlib import asynccontextmanager + +from fastapi import FastAPI, HTTPException + +from functions import * + + +# Create DB on startup +# noinspection PyUnusedLocal +@asynccontextmanager +async def lifespan(app: FastAPI): + create_db_and_tables() + yield # Code before the yield will run on startup, code after yield won't run until the program is over + +# Define the app +app = FastAPI(lifespan=lifespan) + +# Routes +@app.post("/heroes/create", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: SessionDep): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + +@app.get("/heroes/{id}", response_model=Hero) +def get_hero(id: int, session: SessionDep): + hero = session.get(Hero, id) + if not hero: + raise HTTPException(status_code=404, detail="User not found") + + return hero + +@app.patch("/heroes/update/{hero_id}", response_model=HeroPublic) +def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + +@app.delete("/heroes/delete/{id}") +def delete_hero(id: int, session: SessionDep): + hero = session.get(Hero, id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + + session.delete(hero) + session.commit() + return {"message": "Delete successful", "continue": True} + +@app.post("/verify") +def verify_user(email: str, password: str, session: SessionDep): + user = get_hero_by_email(email, session) + # check if the password is correct + authenticate_user = verify_password(password, user.hash, user.salt) + + if authenticate_user: + return {"message": "Authentication successful", "continue": True} + + return {"message": "Authentication failed", "continue": False} \ No newline at end of file diff --git a/functions.py b/functions.py index a3d06dd..c7d3c96 100644 --- a/functions.py +++ b/functions.py @@ -1,8 +1,22 @@ +# Imports from sql import * from sqlmodel import select -def get_hero_by_email(email: str, session) -> Hero | None: - statement = select(Hero).where(Hero.email == email) +import hashlib + +def hash_password(password: str, salt: int = None): + password = f"{password}{salt}" + return hashlib.sha256(password.encode()).hexdigest() + +def verify_password(password: str, hash: str, salt: int) -> bool: + hashed_pass = hash_password(password, salt) + if hash == hashed_pass: + return True + + return False + +def get_hero_by_email(email: str, session) -> HeroPublic | None: + statement = select(Hero.email).where(Hero.email == email) return session.exec(statement).first() def get_hero_by_id(id: str, session: SessionDep) -> Hero | None: diff --git a/main.py b/main.py index 914744d..8e225f7 100644 --- a/main.py +++ b/main.py @@ -1,47 +1,44 @@ -# Imports -from contextlib import asynccontextmanager -from fastapi import FastAPI, HTTPException -from sql import * -from functions import * +import requests +import json +import os -# Create DB on startup -# noinspection PyUnusedLocal -@asynccontextmanager -async def lifespan(app: FastAPI): - create_db_and_tables() - yield # Code before the yield will run on startup, code after yield won't run until the program is over +url="http://127.0.0.1:8000" -# Define the app -app = FastAPI(lifespan=lifespan) +# Does the user have an active account? +active_account = input("Do you have an account? (y/n): ") +if active_account == "y": + email = input("Enter your email: ") + password = input("Enter your password: ") -# Routes -@app.post("/heroes/create", response_model=HeroPublic) -def create_hero(hero: HeroCreate, session: SessionDep): - db_hero = Hero.model_validate(hero) - session.add(db_hero) - session.commit() - session.refresh(db_hero) - return db_hero + data = { + "email": email, + "password": password + } -@app.get("/heroes/{type}", response_model=HeroPublic) -def get_hero(type: str, session: SessionDep): - hero = get_hero_by_id(type, session) - if hero is None: - hero = get_hero_by_email(type, session) + response = requests.post(f"{url}/verify", json=data) - if not hero: - raise HTTPException(status_code=404, detail="User not found") + if response.status_code == 200: + print("Authentication Successful") + print(response.json()) + else: + print("Authentication Not Successful") +else: + create_account = input("Do you want to create an account? (y/n): ") + if create_account.lower() == "y": + name = input("Enter your name: ") + email = input("Enter your email: ") + password = input("Enter your password: ") - return hero + data = { + "name": name, + "email": email, + "password": password + } -@app.patch("/heroes/update/{hero_id}/", response_model=HeroPublic) -def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): - hero_db = session.get(Hero, hero_id) - if not hero_db: - raise HTTPException(status_code=404, detail="Hero not found") - hero_data = hero.model_dump(exclude_unset=True) - hero_db.sqlmodel_update(hero_data) - session.add(hero_db) - session.commit() - session.refresh(hero_db) - return hero_db + response = requests.post(f"{url}/heroes/create", json=data) + + if response.status_code == 200: + print("Authentication Successful") + print(response.json()) + else: + print("Authentication Unsuccessful") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f222136..0bbb578 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,3 +34,5 @@ typing_extensions==4.12.2 uvicorn==0.34.0 watchfiles==1.0.4 websockets==15.0 + +requests~=2.32.3 \ No newline at end of file diff --git a/sql.py b/sql.py index 76ae0d7..9447238 100644 --- a/sql.py +++ b/sql.py @@ -5,29 +5,33 @@ from sqlmodel import Field, Session, SQLModel, create_engine # The base hero class class HeroBase(SQLModel): - name: str = Field(index=True) - gold: int | None = 0 - health: int | None = 100 - email: str = Field(index=True) + name: str + gold: int | None = Field(default=0) + health: int | None = Field(default=100) -# The player +# The main hero class. It inherits the name, gold, and health class Hero(HeroBase, table=True): - id: int = Field(default=None, primary_key=True, index=True) + id: int | None = Field(default=None, primary_key=True, index=True) + email: str = Field(index=True) + password: str + salt: int -# This class will be returned to the public +# The class that will be returned to the public. It will return the id, name, gold, and health class HeroPublic(HeroBase): id: int -# The class used to create a hero +# The stuff required to create an account, you need an email, name, and password class HeroCreate(HeroBase): - secret_name: str + email: str + password: str + salt: int | None = Field(default=0) -# The class used to update a hero -class HeroUpdate(Hero): +# The stuff needed to update a user +class HeroUpdate(HeroBase): name: str | None = None - gold: int | None = 0 - health: int | None = 0 - email: str | None = None + email : str | None = None + gold: int | None = None + health: int | None = None # SQLModel initialization sqlite_file_name = "database.db" @@ -45,5 +49,5 @@ def get_session(): with Session(engine) as session: yield session -# Return the sessiondep +# Return the SessionDep SessionDep = Annotated[Session, Depends(get_session)]