From 107df32af9bfd4af2914e347472e7a291dd8a684 Mon Sep 17 00:00:00 2001 From: cspark Date: Wed, 8 May 2024 20:56:28 +0100 Subject: [PATCH] Implementing retrieving blog data --- blogHandler.py | 78 +++++++++++++++++++++++++++++++++++++++++++++- dbHandler.py | 53 ++++++++++++++++++++++++------- main.py | 67 +++++++++++++++++++++++++++++++++------ orgHandler.py | 29 ++--------------- securityHandler.py | 24 ++++++++------ 5 files changed, 191 insertions(+), 60 deletions(-) diff --git a/blogHandler.py b/blogHandler.py index ded565c..387ffcb 100644 --- a/blogHandler.py +++ b/blogHandler.py @@ -2,7 +2,9 @@ import os import datetime import psycopg2 +from orgparse import loads +import userHandler import orgHandler import dbHandler @@ -13,6 +15,70 @@ def debugPrint(msg: str) -> None: if debug: print("(BLOG HANDLER) PRINT: " + msg) +def checkIDExistence(dbConnection: psycopg2.extensions.connection, blogID: int) -> bool: + return dbHandler.checkFieldValueExistence(dbConnection, "blogs", "id", blogID) + +def getBlogCardRange(dbConnection: psycopg2.extensions.connection, rangeStart: int, rangeEnd: int, latestRecords = True) -> dict: + records = dbHandler.getRowRangeByID(dbConnection, "blogs", rangeStart, rangeEnd, latestRecords) + recordsDictionary = {} + for index, record in enumerate(records): + datePostedStr = record[3].strftime("%G-%m-%d %X") + # 0 index reserved for success/message status + recordsDictionary[index+1] = { + "blogID": record[0], + "authorInfo": { + "ID": record[1], + "username": userHandler.getUserInfoByID(dbConnection, record[1], "username"), + "firstName": userHandler.getUserInfoByID(dbConnection, record[1], "firstname"), + "lastName": userHandler.getUserInfoByID(dbConnection, record[1], "lastname") + }, + "categoryID": record[2], + "datePosted": datePostedStr, + "title": record[4], + "description": record[5] + } + return recordsDictionary + + +def getBlogTitle(blogID: int) -> str: + debugPrint("Attempting to retrieve blog title of blog ID " + str(blogID)) + try: + fileData = open(blogDir + "/" + str(blogID) + "/" + str(blogID) + ".org", 'r') + title = orgHandler.checkAndRetrieveMetadata(fileData, "TITLE") + if title: + return title + except Exception as error: + debugPrint("Error getting blog title! " + repr(error)) + +def getBlogDescription(blogID: int) -> str: + debugPrint("Attempting to retrieve blog description of blog ID " + str(blogID)) + try: + fileData = open(blogDir + "/" + str(blogID) + "/" + str(blogID) + ".org", 'r') + orgRoot = loads(fileData.read()) + + fileData.readline() + shortDescription = orgHandler.checkAndRetrieveMetadata(fileData, "DESCRIPTION") + if not shortDescription: + debugPrint("No valid description found, will generate a placeholder from the text itself...") + firstText = orgRoot[1].body + shortDescription = (firstText[:60] + "...") if len(firstText) > 60 else firstText + return shortDescription + except Exception as error: + debugPrint("Error getting blog description! Unexpected error: " + repr(error) + ".") + +def getBlogContentsHTML(blogID: int) -> str: + try: + debugPrint("Attempting to retrieve blog contents of blogID " + str(blogID) + "...") + FullBlogDir = blogDir + "/" + str(blogID) + HTMLFileData = open(FullBlogDir + "/" + str(blogID) + ".html", 'r') + HTMLFileContents = HTMLFileData.read() + HTMLFileData.close() + return HTMLFileContents + except Exception as error: + msg = "Retrieve blog contents of blogID " + str(blogID) + " failed! Unexpected error: " + repr(error) + "." + debugPrint(msg) + return msg + # Returns new Blog ID def createBlogEntry(dbConnection: psycopg2.extensions.connection, userID: int, categoryID: int) -> int: datePosted = datetime.datetime.now() @@ -24,6 +90,12 @@ def createBlogEntry(dbConnection: psycopg2.extensions.connection, userID: int, c [userID, categoryID, datePostedStr]) return newRow[0] +def generateBlogTitle(dbConnection: psycopg2.extensions.connection, blogID: int) -> tuple: + return dbHandler.changeFieldValueByID(dbConnection, "blogs", blogID, "title", getBlogTitle(blogID)) + +def generateBlogDescription(dbConnection: psycopg2.extensions.connection, blogID: int) -> tuple: + return dbHandler.changeFieldValueByID(dbConnection, "blogs", blogID, "description", getBlogDescription(blogID)) + def uploadBlog(dbConnection: psycopg2.extensions.connection, userID: int, orgRawIn: str): try: orgParsedOut = orgHandler.orgToHTML(orgRawIn) @@ -43,9 +115,13 @@ def uploadBlog(dbConnection: psycopg2.extensions.connection, userID: int, orgRaw HTMLFileData = open(newBlogDir + "/" + str(blogID) + ".html", 'w') HTMLFileData.write(orgParsedOut) HTMLFileData.close() + + generateBlogTitle(dbConnection, blogID) + generateBlogDescription(dbConnection, blogID) + return blogID else: return False except Exception as error: - debugPrint("Create blog failed! " + repr(error)) + debugPrint("Create blog failed! Unexpected error: " + repr(error) + ".") return False diff --git a/dbHandler.py b/dbHandler.py index 7c894a4..bff93d5 100644 --- a/dbHandler.py +++ b/dbHandler.py @@ -49,9 +49,9 @@ def initTable(dbConnection: psycopg2.extensions.connection, tableName: str, tabl FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '""" + tableName.lower() + """')) THEN - RAISE NOTICE '""" + tableName + """ Table does exist! Skipping creating table.'; + RAISE NOTICE '""" + tableName + """ Table already exists! Skipping table creation...'; ELSE - RAISE NOTICE '""" + tableName + """ Table does not exist! Creating table.'; + RAISE NOTICE '""" + tableName + """ Table does not exist! Creating table...'; CREATE TABLE """ + tableName.lower() + """ ( """ + tableFormat + """ ); @@ -64,28 +64,34 @@ def initTable(dbConnection: psycopg2.extensions.connection, tableName: str, tabl dbCursor.close() # These base functions should not be called directly as they perform no string query sanitisation (Therefore vulnerable to SQL injection attacks) -def _commitQuery(dbConnection: psycopg2.extensions.connection, query: sql.Composable) -> list: +def _commitQuery(dbConnection: psycopg2.extensions.connection, query: sql.Composable, fetchResults: bool = True): try: debugPrint("Commit query executing...") dbCursor = dbConnection.cursor() dbCursor.execute(query) dbConnection.commit() - dbResults = dbCursor.fetchall() - dbCursor.close() - return dbResults + if fetchResults: + dbResults = dbCursor.fetchall() + dbCursor.close() + return dbResults + else: + return True except Exception as error: - errorPrint("Commit query failed! " + repr(error)) + errorPrint("Commit query failed! Unexpected error: " + repr(error)) return None -def _execQuery(dbConnection: psycopg2.extensions.connection, query: sql.Composable) -> list: +def _execQuery(dbConnection: psycopg2.extensions.connection, query: sql.Composable, fetchResults: bool = True): try: debugPrint("Exec query executing...") dbCursor = dbConnection.cursor() dbCursor.execute(query) - dbResults = dbCursor.fetchall() - dbCursor.close() - return dbResults + if fetchResults: + dbResults = dbCursor.fetchall() + dbCursor.close() + return dbResults + else: + return True except Exception as error: - errorPrint("Exec query failed! " + repr(error)) + errorPrint("Exec query failed! Unexpected error: " + repr(error)) return None # Callable helper functions @@ -118,6 +124,18 @@ def getFieldValueByID(dbConnection: psycopg2.extensions.connection, tableName: s ) return str(_execQuery(dbConnection, sanitisedQuery)[0][0]) +def changeFieldValueByID(dbConnection: psycopg2.extensions.connection, tableName: str, RowID: int, tableField: str, newValue) -> str: + debugPrint("Attempting to change value of field name " + tableField + " in ID row " + str(RowID) + " in table name " + tableName + " to " + str(newValue) + "...") + sanitisedQuery = sql.SQL(""" + UPDATE {table} SET {field} = {value} WHERE "id" = {id} + """).format( + table=sql.Identifier(tableName), + field=sql.Identifier(tableField), + id=sql.Literal(RowID), + value=sql.Literal(newValue) + ) + return _commitQuery(dbConnection, sanitisedQuery, False) + def getRowByID(dbConnection: psycopg2.extensions.connection, tableName: str, RowID: int) -> tuple: debugPrint("Attempting to get row values by ID " + str(RowID) + " in table name " + tableName + "...") sanitisedQuery = sql.SQL(""" @@ -146,3 +164,14 @@ def checkFieldValueExistence(dbConnection: psycopg2.extensions.connection, table ) return bool(_execQuery(dbConnection, sanitisedQuery)[0][0]) +def getRowRangeByID(dbConnection: psycopg2.extensions.connection, tableName: str, rangeStart: int, rangeEnd: int, latestRecords = True) -> tuple: + debugPrint("Getting rows from table name " + tableName + " from range " + str(rangeStart) + "-" + str(rangeEnd) + "...") + sanitisedQuery = sql.SQL(""" + SELECT * FROM {table} WHERE id >= {start} AND id <= {end} ORDER BY id {order} + """).format( + table=sql.Identifier(tableName), + start=sql.Literal(rangeStart), + end=sql.Literal(rangeEnd), + order=sql.SQL("DESC" if latestRecords else "ASC") + ) + return tuple(_execQuery(dbConnection, sanitisedQuery)) diff --git a/main.py b/main.py index 4d22bf6..f4ec96c 100644 --- a/main.py +++ b/main.py @@ -66,17 +66,19 @@ def apiInit(): ID SERIAL PRIMARY KEY, AuthorID INTEGER, CategoryID INTEGER, - DatePosted TIMESTAMP + DatePosted TIMESTAMP, + Title VARCHAR(255), + Description VARCHAR(255) """) dbHandler.initTable(dbConnection, "Categories", """ ID SERIAL PRIMARY KEY, Name VARCHAR(255) """) - debugInitPrint("Creating blog data directory at " + blogDir + "...") if not os.path.exists(blogDir): + debugInitPrint("Blog data directory does not exist! Creating blog data directory at " + blogDir + "...") os.mkdir(blogDir) else: - debugInitPrint("Blog data directory already exists! Skipping...") + debugInitPrint("Blog data directory already exists! Skipping directory creation...") # userHandler.createUser(dbConnection, "testuser", "Test", "User", "A test user", "TestCountry", "TestTheme", "TestColor", "testuser") def apiCleanup(): @@ -115,8 +117,8 @@ class ApiBody(BaseModel): password: str @app.post("/api") def postapi(body: ApiBody): - print(body.username) - print(body.password) + debugPrint(body.username) + debugPrint(body.password) return body class postuserLoginBody(BaseModel): @@ -136,7 +138,7 @@ def postuserLogin(body: postuserLoginBody, request: Request): 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) + debugPrint(msg) return {"success": False, "authToken": None, "message": msg} @app.get("/api/user/IDByAuthToken") @@ -149,7 +151,7 @@ def getuserIDByAuthToken(authToken: Annotated[str | None, Header()] = None): 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) + debugPrint(msg) return {"success": False, "authToken": None, "message": msg} @app.get("/api/user/publicInfo/{userID}") @@ -173,7 +175,7 @@ def getuserPublicInfo(userID: int): } except Exception as error: msg = "Get public info failed! Unexpected server error. " + repr(error) - print(msg) + debugPrint(msg) return { "success": False, "username": None, @@ -204,7 +206,7 @@ def getuserSettingsAccount(authToken: Annotated[str | None, Header()] = None): } except Exception as error: msg = "Get user settings failed! Unexpected server error. " + repr(error) - print(msg) + debugPrint(msg) return { "success": False, "username": None, @@ -213,6 +215,49 @@ def getuserSettingsAccount(authToken: Annotated[str | None, Header()] = None): "message": msg } +@app.get("/api/user/blog/cardInfo/") +def getblogCardInfo(rangeStart: int = 1, rangeEnd: int = 25, sortByLatest: bool = True): + try: + blogCardData = blogHandler.getBlogCardRange(dbConnection, rangeStart, rangeEnd, sortByLatest) + blogCardData[0] = { + "success": True, + "message": "Get card info succeeded!" + } + return blogCardData + except Exception as error: + blogCardData = {} + msg = "Get card info failed! Unexpected server error. " + repr(error) + debugPrint(msg) + blogCardData[0] = { + "success": False, + "message": msg + } + return blogCardData + +@app.get("/api/blog/contents/HTML/{blogID}") +def getblogContentsHTML(blogID: int): + try: + if blogHandler.checkIDExistence(dbConnection, blogID): + return { + "success": True, + "contents": blogHandler.getBlogContentsHTML(blogID), + "message": "Get blog contents in HTML succeeded!" + } + else: + return { + "success": False, + "contents": None, + "message": "Get blog contents in HTML failed! BlogID provided does not exist." + } + except Exception as error: + msg = "Get blog contents in HTML failed! Unexpected server error. " + repr(error) + debugPrint(msg) + return { + "success": False, + "contents": None, + "message": msg + } + class postblogCreateBody(BaseModel): authToken: str @@ -243,7 +288,7 @@ def postblogCreate(body: postblogCreateBody): } except Exception as error: msg = "Create blog failed! Unexpected server error. " + repr(error) - print(msg) + debugPrint(msg) return { "success": False, "blogID": None, @@ -275,6 +320,8 @@ def postblogCreate(body: postblogCreateBody): # /api/blog/pictureURL/{blogID} # /api/blog/description/{blogID} # /api/blog/datePosted/{blogID} +# /api/blog/contents/HTML/{blogID} +# /api/blog/contents/org/{blogID} # POST # /api/user/changeInfo/{infotype} diff --git a/orgHandler.py b/orgHandler.py index 051e502..b57aadf 100644 --- a/orgHandler.py +++ b/orgHandler.py @@ -16,32 +16,7 @@ def checkAndRetrieveMetadata(fileData: str, metadataName: str): else: debugPrint("Could not find " + metadataFullName + " metadata field of document!") return False - -def getOrgTitle(blogID: int) -> str: - try: - fileData = open(blogDir + "/" + str(blogID) + "/" + str(blogID) + ".org", 'r') - title = checkAndRetrieveMetadata(fileData, "TITLE") - if title: - return title - except Exception as error: - debugPrint("Error getting blog title! " + repr(error)) - -def getOrgDescription(blogID: str) -> str: - try: - fileData = open(blogDir + "/" + str(blogID) + "/" + str(blogID) + ".org", 'r') - orgRoot = loads(fileData.read()) - - fileData.readline() - shortDescription = checkAndRetrieveMetadata(fileData, "DESCRIPTION") - if not shortDescription: - debugPrint("No valid description found, will generate a placeholder from the text itself...") - firstText = orgRoot[1].body - shortDescription = (firstText[:60] + "...") if len(firstText) > 60 else firstText - return shortDescription - except Exception as error: - debugPrint("Error getting org title! " + repr(error)) - def orgToHTML(orgData: str) -> str: try: orgRoot = loads(orgData) @@ -49,10 +24,10 @@ def orgToHTML(orgData: str) -> str: for node in orgRoot[1:]: if node.heading: - headingLevel = str(node.level) + headingLevel = str(node.level if node.level <= 6 else 6) parsedHTML += "" + node.heading + "" + "\n" if node.body: parsedHTML += node.body + "\n" return parsedHTML except Exception as error: - debugPrint("Error parsing org! " + repr(error)) + debugPrint("Error parsing org! Unexpected error: " + repr(error) + ".") diff --git a/securityHandler.py b/securityHandler.py index 6b8513f..f4efa89 100644 --- a/securityHandler.py +++ b/securityHandler.py @@ -35,14 +35,18 @@ def verifyRehash(hash: str) -> bool: return False def handlePasswordVerification(dbConnection: psycopg2.extensions.connection, password: str, userID: int) -> bool: - hash = userHandler.getHashValueByUserID(dbConnection, userID) - userIDstr = str(userID) - debugPrint("Now verifying password against hash for user ID " + userIDstr + "...") - if verifyPassword(password, hash): - debugPrint("(USER ID " + userIDstr + ") Password verification success!") - if verifyRehash(hash): - debugPrint("(USER ID " + userIDstr + ") Hash needs to be rehashed! Will now rehash...") - return True - else: - debugPrint("(USER ID " + userIDstr + ") Password verification failure!") + try: + hash = userHandler.getHashValueByUserID(dbConnection, userID) + userIDstr = str(userID) + debugPrint("Now verifying password against hash for user ID " + userIDstr + "...") + if verifyPassword(password, hash): + debugPrint("(USER ID " + userIDstr + ") Password verification success!") + if verifyRehash(hash): + debugPrint("(USER ID " + userIDstr + ") Hash needs to be rehashed! Will now rehash...") + return True + else: + debugPrint("(USER ID " + userIDstr + ") Password verification failure! Invalid password.") + return False + except Exception as error: + debugPrint("(USER ID " + userIDstr + ") Password verification failure! Unexpected error: " + repr(error) + ".") return False