import os import sys import atexit import signal from typing import Union, Annotated from contextlib import asynccontextmanager from fastapi import FastAPI, Request, Header from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import dbHandler import userHandler import securityHandler import tokenHandler import blogHandler blogDir = "./blogs" debug: bool = True def debugPrint(msg: str) -> None: if debug: print("(MAIN) PRINT: " + msg) def debugInitPrint(msg: str) -> None: if debug: print("(MAIN INIT) PRINT: " + msg) dbConnection = dbHandler.connect("blorgdb", "172.20.0.10", "dev", "dev", "5432") def apiInit(): dbHandler.initTable(dbConnection, "Users", """ ID SERIAL PRIMARY KEY, Username VARCHAR(255), Email VARCHAR(255), FirstName VARCHAR(255), LastName VARCHAR(255), Description VARCHAR(255), Country VARCHAR(255), Theme VARCHAR(255), AccentColor VARCHAR(255), PasswordHash VARCHAR(255) """) dbHandler.initTable(dbConnection, "SignOns", """ ID SERIAL PRIMARY KEY, UserID INTEGER, LoginSuccess BOOLEAN, DateAttempted TIMESTAMP, IPLocationAttempted VARCHAR(255) """) dbHandler.initTable(dbConnection, "AuthTokens", """ ID SERIAL PRIMARY KEY, Token VARCHAR(2048), OwnerID INTEGER, DateCreated TIMESTAMP, DateExpiry TIMESTAMP, IPLocationCreated VARCHAR(255) """) dbHandler.initTable(dbConnection, "Blogs", """ ID SERIAL PRIMARY KEY, AuthorID INTEGER, CategoryID INTEGER, DatePosted TIMESTAMP """) dbHandler.initTable(dbConnection, "Categories", """ ID SERIAL PRIMARY KEY, Name VARCHAR(255) """) debugInitPrint("Creating blog data directory at " + blogDir + "...") if not os.path.exists(blogDir): os.mkdir(blogDir) else: debugInitPrint("Blog data directory already exists! Skipping...") # userHandler.createUser(dbConnection, "testuser", "Test", "User", "A test user", "TestCountry", "TestTheme", "TestColor", "testuser") def apiCleanup(): dbHandler.disconnect(dbConnection) @asynccontextmanager async def apiLifespan(app: FastAPI): # API Init apiInit() # API Clean up yield apiCleanup() app = FastAPI(lifespan=apiLifespan) origins = [ "http://localhost", "http://localhost:8080", ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/") def getroot(): return {"Hello": "World"} class ApiBody(BaseModel): username: str password: str @app.post("/api") def postapi(body: ApiBody): print(body.username) print(body.password) return body class postuserLoginBody(BaseModel): username: str password: str rememberMe: bool @app.post("/api/user/login") def postuserLogin(body: postuserLoginBody, request: Request): try: if userHandler.checkUserExistence(dbConnection, body.username): userID = userHandler.getIDByUsername(dbConnection, body.username) if securityHandler.handlePasswordVerification(dbConnection, body.password, userID): return {"success": True, "authToken": tokenHandler.createToken(dbConnection, userID, body.rememberMe, request.client.host), "message": "User login success!"} else: return {"success": False, "authToken": None, "message": "User login failed! Please check your password."} else: return {"success": False, "authToken": None, "message": "User login failed! User does not exist."} except Exception as error: msg = "User login failed! Unexpected server error. " + repr(error) print(msg) return {"success": False, "authToken": None, "message": msg} @app.get("/api/user/IDByAuthToken") def getuserIDByAuthToken(authToken: Annotated[str | None, Header()] = None): try: if tokenHandler.validateTokenExistence(dbConnection, authToken): userID = userHandler.getIDByAuthToken(dbConnection, authToken) return {"success": True, "userID": userID, "message": "Get userID by authToken succeeded!"} else: return {"success": False, "userID": None, "message": "Get userID by authToken failed! authToken provided is not valid."} except Exception as error: msg = "Get userID by authToken failed! Unexpected server error. " + repr(error) print(msg) return {"success": False, "authToken": None, "message": msg} @app.get("/api/user/publicInfo/{userID}") def getuserPublicInfo(userID: int): try: if userHandler.checkIDExistence(dbConnection, userID): return { "success": True, "username": userHandler.getUserInfoByID(dbConnection, userID, "username"), "firstName": userHandler.getUserInfoByID(dbConnection, userID, "firstname"), "lastName": userHandler.getUserInfoByID(dbConnection, userID, "lastname"), "message": "Get public info succeeded!" } else: return { "success": False, "username": None, "firstName": None, "lastName": None, "message": "Get public info failed! UserID provided does not exist." } except Exception as error: msg = "Get public info failed! Unexpected server error. " + repr(error) print(msg) return { "success": False, "username": None, "firstName": None, "lastName": None, "message": msg } @app.get("/api/user/settings/account") def getuserSettingsAccount(authToken: Annotated[str | None, Header()] = None): try: if tokenHandler.validateTokenExistence(dbConnection, authToken): userID = userHandler.getIDByAuthToken(dbConnection, authToken) return { "success": True, "username": userHandler.getUserInfoByID(dbConnection, userID, "username"), "firstName": userHandler.getUserInfoByID(dbConnection, userID, "firstname"), "lastName": userHandler.getUserInfoByID(dbConnection, userID, "lastname"), "message": "Get user settings succeeded!" } else: return { "success": False, "username": None, "firstName": None, "lastName": None, "message": "Get user settings failed! authToken provided is not valid." } except Exception as error: msg = "Get user settings failed! Unexpected server error. " + repr(error) print(msg) return { "success": False, "username": None, "firstName": None, "lastName": None, "message": msg } class postblogCreateBody(BaseModel): authToken: str orgContents: str @app.post("/api/blog/create") def postblogCreate(body: postblogCreateBody): try: if tokenHandler.validateTokenExistence(dbConnection, body.authToken): userID = userHandler.getIDByAuthToken(dbConnection, body.authToken) newBlog = blogHandler.uploadBlog(dbConnection, userID, body.orgContents) if newBlog: return { "success": True, "blogID": newBlog, "message": "Create blog succeeded!" } else: return { "success": False, "blogID": newBlog, "message": "Create blog failed! Unknown error parsing org data." } else: return { "success": False, "blogID": None, "message": "Create blog failed! authToken provided is not valid." } except Exception as error: msg = "Create blog failed! Unexpected server error. " + repr(error) print(msg) return { "success": False, "blogID": None, "message": msg } # GET # /api/user/IDByAuthToken # - userID # Could be merged into a single endpoint which you can optionally query what you need from # /api/user/publicInfo/{userID} # - username # - firstname # - lastname # - profile picture # - location # - public email (For contact) # Could be merged into a single endpoint which you can optionally query what you need from # /api/user/privateInfo/{userID} # - private email (For authentication/login) # Could be merged into a single endpoint which you can optionally query what you need from # /api/blog/title/{blogID} # /api/blog/authorID/{blogID} # /api/blog/categoryID/{blogID} # /api/blog/pictureURL/{blogID} # /api/blog/description/{blogID} # /api/blog/datePosted/{blogID} # POST # /api/user/changeInfo/{infotype} # TODO: Need to overhaul this to accept an input file which we can read in chunks to ensure a too big file isn't sent, frontend will convert textbox/form data appropriately # https://github.com/steinnes/content-size-limit-asgi # https://github.com/tiangolo/fastapi/issues/362 # /api/blog/create # /api/blog/edit @app.get("/api") def getapi(): return {"Hello": "API!"}