Uploading blog endpoint
This commit is contained in:
parent
770b17c4d1
commit
faa718c7d5
|
|
@ -1,3 +1,4 @@
|
|||
.venv
|
||||
__pycache__
|
||||
postgres-data
|
||||
blogs
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import os
|
||||
import datetime
|
||||
|
||||
import psycopg2
|
||||
|
||||
import orgHandler
|
||||
import dbHandler
|
||||
|
||||
blogDir: str = "./blogs"
|
||||
debug: bool = True
|
||||
|
||||
def debugPrint(msg: str) -> None:
|
||||
if debug:
|
||||
print("(BLOG HANDLER) PRINT: " + msg)
|
||||
|
||||
# Returns new Blog ID
|
||||
def createBlogEntry(dbConnection: psycopg2.extensions.connection, userID: int, categoryID: int) -> int:
|
||||
datePosted = datetime.datetime.now()
|
||||
datePostedStr = datePosted.strftime("%G-%m-%d %X")
|
||||
debugPrint("Now creating new user with following attributes : userID = " + str(userID) + ", categoryID = " + str(categoryID) + ", datePosted = " + datePostedStr)
|
||||
newRow = dbHandler.insertRow(dbConnection,
|
||||
'blogs',
|
||||
['authorID', 'categoryID', 'datePosted' ],
|
||||
[userID, categoryID, datePostedStr])
|
||||
return newRow[0]
|
||||
|
||||
def uploadBlog(dbConnection: psycopg2.extensions.connection, userID: int, orgRawIn: str):
|
||||
try:
|
||||
orgParsedOut = orgHandler.orgToHTML(orgRawIn)
|
||||
if orgParsedOut:
|
||||
blogID = createBlogEntry(dbConnection, userID, 1)
|
||||
|
||||
newBlogDir = blogDir + "/" + str(blogID)
|
||||
debugPrint("Attempting to create new blog directory " + newBlogDir + "...")
|
||||
os.mkdir(newBlogDir)
|
||||
|
||||
debugPrint("Attempting to write new blog file " + newBlogDir + ".org...")
|
||||
orgFileData = open(newBlogDir + "/" + str(blogID) + ".org", 'w')
|
||||
orgFileData.write(orgRawIn)
|
||||
orgFileData.close()
|
||||
|
||||
debugPrint("Attempting to write new blog file " + blogDir + "/" + str(blogID) + "/" + str(blogID) + ".html...")
|
||||
HTMLFileData = open(newBlogDir + "/" + str(blogID) + ".html", 'w')
|
||||
HTMLFileData.write(orgParsedOut)
|
||||
HTMLFileData.close()
|
||||
return blogID
|
||||
else:
|
||||
return False
|
||||
except Exception as error:
|
||||
debugPrint("Create blog failed! " + repr(error))
|
||||
return False
|
||||
24
dbHandler.py
24
dbHandler.py
|
|
@ -65,13 +65,17 @@ def initTable(dbConnection: psycopg2.extensions.connection, tableName: str, tabl
|
|||
|
||||
# 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:
|
||||
debugPrint("Commit query executing...")
|
||||
dbCursor = dbConnection.cursor()
|
||||
dbCursor.execute(query)
|
||||
dbConnection.commit()
|
||||
dbResults = dbCursor.fetchall()
|
||||
dbCursor.close()
|
||||
return dbResults
|
||||
try:
|
||||
debugPrint("Commit query executing...")
|
||||
dbCursor = dbConnection.cursor()
|
||||
dbCursor.execute(query)
|
||||
dbConnection.commit()
|
||||
dbResults = dbCursor.fetchall()
|
||||
dbCursor.close()
|
||||
return dbResults
|
||||
except Exception as error:
|
||||
errorPrint("Commit query failed! " + repr(error))
|
||||
return None
|
||||
def _execQuery(dbConnection: psycopg2.extensions.connection, query: sql.Composable) -> list:
|
||||
try:
|
||||
debugPrint("Exec query executing...")
|
||||
|
|
@ -85,7 +89,7 @@ def _execQuery(dbConnection: psycopg2.extensions.connection, query: sql.Composab
|
|||
return None
|
||||
|
||||
# Callable helper functions
|
||||
def insertRow(dbConnection: psycopg2.extensions.connection, tableName: str, tableFormat: list[str], tableValues: list):
|
||||
def insertRow(dbConnection: psycopg2.extensions.connection, tableName: str, tableFormat: list[str], tableValues: list) -> list:
|
||||
debugPrint("Attempting to insert new row (" + str(tableFormat) + ") VALUES (" + str(tableValues) + ") into table name " + tableName + "...")
|
||||
sanitisedQuery = sql.SQL("""
|
||||
INSERT INTO {table} ({format})
|
||||
|
|
@ -94,14 +98,14 @@ def insertRow(dbConnection: psycopg2.extensions.connection, tableName: str, tabl
|
|||
""").format(
|
||||
table=sql.Identifier(tableName),
|
||||
format=sql.SQL(", ").join(
|
||||
sql.Identifier(value) for value in tableFormat
|
||||
sql.Identifier(value.lower()) for value in tableFormat
|
||||
),
|
||||
values=sql.SQL(", ").join(
|
||||
sql.Literal(value) for value in tableValues
|
||||
)
|
||||
)
|
||||
debugPrint(sanitisedQuery.as_string(dbConnection))
|
||||
_commitQuery(dbConnection, sanitisedQuery)
|
||||
return _commitQuery(dbConnection, sanitisedQuery)[0]
|
||||
|
||||
def getFieldValueByID(dbConnection: psycopg2.extensions.connection, tableName: str, RowID: int, tableField: str) -> str:
|
||||
debugPrint("Attempting to get values of field name " + tableField + " in ID row " + str(RowID) + " in table name " + tableName + "...")
|
||||
|
|
|
|||
98
main.py
98
main.py
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import sys
|
||||
import atexit
|
||||
import signal
|
||||
|
|
@ -14,6 +15,19 @@ 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",
|
||||
|
|
@ -52,12 +66,17 @@ def apiInit():
|
|||
ID SERIAL PRIMARY KEY,
|
||||
AuthorID INTEGER,
|
||||
CategoryID INTEGER,
|
||||
DatePosted TIMESTAMP,
|
||||
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():
|
||||
|
|
@ -100,12 +119,12 @@ def postapi(body: ApiBody):
|
|||
print(body.password)
|
||||
return body
|
||||
|
||||
class loginBody(BaseModel):
|
||||
class postuserLoginBody(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
rememberMe: bool
|
||||
@app.post("/api/login")
|
||||
def postlogin(body: loginBody, request: Request):
|
||||
@app.post("/api/user/login")
|
||||
def postuserLogin(body: postuserLoginBody, request: Request):
|
||||
try:
|
||||
if userHandler.checkUserExistence(dbConnection, body.username):
|
||||
userID = userHandler.getIDByUsername(dbConnection, body.username)
|
||||
|
|
@ -153,12 +172,14 @@ def getuserPublicInfo(userID: int):
|
|||
"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": "Get public info failed! Unexpected server error. " + repr(error)
|
||||
"message": msg
|
||||
}
|
||||
|
||||
@app.get("/api/user/settings/account")
|
||||
|
|
@ -182,18 +203,59 @@ def getuserSettingsAccount(authToken: Annotated[str | None, Header()] = 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": "Get user settings failed! Unexpected server error. " + repr(error)
|
||||
"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/ByAuthToken
|
||||
# /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
|
||||
|
|
@ -201,19 +263,29 @@ def getuserSettingsAccount(authToken: Annotated[str | None, Header()] = None):
|
|||
# - 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)
|
||||
|
||||
# /api/blog/title
|
||||
# /api/blog/authorID
|
||||
# /api/blog/categoryID
|
||||
# /api/blog/pictureLocation
|
||||
# /api/blog/description
|
||||
# /api/blog/datePosted
|
||||
# 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!"}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
from orgparse import load
|
||||
from orgparse import loads
|
||||
|
||||
blogDir: str = "./blogs"
|
||||
debug: bool = True
|
||||
|
||||
def debugPrint(msg: str) -> None:
|
||||
if debug:
|
||||
print("(ORG HANDLER) PRINT: " + msg)
|
||||
|
||||
def checkAndRetrieveMetadata(fileData, metadataName: str):
|
||||
def checkAndRetrieveMetadata(fileData: str, metadataName: str):
|
||||
line = fileData.readline()
|
||||
metadataFullName = "#+" + metadataName + ":"
|
||||
if metadataFullName in line:
|
||||
|
|
@ -16,28 +17,42 @@ def checkAndRetrieveMetadata(fileData, metadataName: str):
|
|||
debugPrint("Could not find " + metadataFullName + " metadata field of document!")
|
||||
return False
|
||||
|
||||
|
||||
def orgToHTML(filePath: str):
|
||||
def getOrgTitle(blogID: int) -> str:
|
||||
try:
|
||||
fileData = open(filePath, 'r')
|
||||
orgRoot = load(filePath)
|
||||
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))
|
||||
|
||||
Title = checkAndRetrieveMetadata(fileData, "TITLE")
|
||||
if not Title:
|
||||
raise Exception("A valid #+TITLE: field is required as the first line of the org page.")
|
||||
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 blog itself...")
|
||||
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)
|
||||
parsedHTML = ""
|
||||
|
||||
for node in orgRoot[1:]:
|
||||
if node.heading:
|
||||
headingLevel = str(node.level)
|
||||
print("<h" + headingLevel + ">" + node.heading + "</h" + headingLevel + ">")
|
||||
parsedHTML += "<h" + headingLevel + ">" + node.heading + "</h" + headingLevel + ">" + "\n"
|
||||
if node.body:
|
||||
print(node.body)
|
||||
parsedHTML += node.body + "\n"
|
||||
return parsedHTML
|
||||
except Exception as error:
|
||||
debugPrint("Error parsing org! " + repr(error))
|
||||
|
||||
orgToHTML("./test.org")
|
||||
|
|
|
|||
|
|
@ -15,15 +15,17 @@ def validateTokenExistence(dbConnection: psycopg2.extensions.connection, authTok
|
|||
return dbHandler.checkFieldValueExistence(dbConnection, "authtokens", "token", authToken)
|
||||
|
||||
def createToken(dbConnection: psycopg2.extensions.connection, userID: int, rememberMe: bool, locationIP: str) -> str:
|
||||
debugPrint("Now initialising new token with following attributes : userID = " + str(userID) + ", rememberMe = " + str(rememberMe) + ", locationIP = " + locationIP + "...")
|
||||
randToken = secrets.token_hex(1023)
|
||||
dateCreated = datetime.datetime.now()
|
||||
if rememberMe:
|
||||
dateExpiry = dateCreated + datetime.timedelta(days=30)
|
||||
else:
|
||||
dateExpiry = dateCreated + datetime.timedelta(days=1)
|
||||
dateExpiryStr = dateExpiry.strftime("%G-%m-%d %X")
|
||||
dateCreatedStr = dateCreated.strftime("%G-%m-%d %X")
|
||||
debugPrint("Now initialising new token with following attributes : ownerID = " + str(userID) + ", rememberMe = " + str(rememberMe) + ", locationIP = " + locationIP + "...")
|
||||
dbHandler.insertRow(dbConnection,
|
||||
'authtokens',
|
||||
['token', 'ownerid', 'datecreated', 'dateexpiry', 'iplocationcreated'],
|
||||
[randToken, userID, dateCreated.strftime("%G-%m-%d %X"), dateExpiry.strftime("%G-%m-%d %X"), locationIP])
|
||||
['token', 'ownerid', 'datecreated', 'dateexpiry', 'iplocationcreated'],
|
||||
[randToken, userID, dateCreatedStr, dateCreatedStr, locationIP ])
|
||||
return randToken
|
||||
|
|
|
|||
Loading…
Reference in New Issue