diff --git a/ast/ast_with_tree_sitter.py b/ast/ast_with_tree_sitter.py new file mode 100644 index 0000000000000000000000000000000000000000..af27b278d892f3ceacf5f266f2282572dec79f40 --- /dev/null +++ b/ast/ast_with_tree_sitter.py @@ -0,0 +1,28 @@ +import tree_sitter_python as tspython +from tree_sitter import Language, Parser, Node + +def printASTNodeInfo(node: Node): + # print(f"Node type: {node.type}") + for child in node.children: + # print(f" - Child node type: {child.type}") + if len(child.children) > 0: + printASTNodeInfo(child) + else: + if (child.text) and child.text == b'session': + print(f"Node type: {node.type}") + print(f" - Child node type: {child.type} ({child.start_point.row}, {child.start_point.column}) ({child.end_point.row}, {child.end_point.column})") + print(f" - Text content: {child.text}") + +PY_LANGUAGE = Language(tspython.language()) + +parser = Parser(PY_LANGUAGE) + +file_path = "routes/editPost.py" +in_file = open(file_path, "rb") +data = in_file.read() + +tree = parser.parse(data) +rootNode = tree.root_node +in_file.close() + +printASTNodeInfo(rootNode) diff --git a/ast/routes/createPost.py b/ast/routes/createPost.py new file mode 100644 index 0000000000000000000000000000000000000000..17abc102c4637d16118022fb3fb195009081f47c --- /dev/null +++ b/ast/routes/createPost.py @@ -0,0 +1,172 @@ +# Import necessary modules and functions +from modules import ( + Log, # Logging module + flash, # Flash messaging module + abort, # Function for aborting requests + session, # Session management module + sqlite3, # SQLite database module + request, # Module for handling HTTP requests + redirect, # Function for redirecting requests + addPoints, # Function for adding points to a user + Blueprint, # Blueprint class for creating modular applications + RECAPTCHA, # Recaptcha module + requestsPost, # Module for making HTTP POST requests + DB_POSTS_ROOT, # Path to the posts database + CreatePostForm, # Form for creating a post + render_template, # Function for rendering templates + currentTimeStamp, # Function for getting current timestamp + RECAPTCHA_SITE_KEY, # Recaptcha site key + RECAPTCHA_VERIFY_URL, # Recaptcha verification URL + RECAPTCHA_SECRET_KEY, # Recaptcha secret key + RECAPTCHA_POST_CREATE, # Flag for enabling/disabling Recaptcha for post creation +) + +# Create a blueprint for the create post route +createPostBlueprint = Blueprint("createPost", __name__) + + +# Define a route for creating a post +@createPostBlueprint.route("/createpost", methods=["GET", "POST"]) +def createPost(): + """ + This function creates a new post for the user. + + Args: + request (Request): The request object from the user. + + Returns: + Response: The response object with the HTML template for the create post page. + + Raises: + 401: If the user is not authenticated. + """ + # Check if "userName" exists in session + match not "userName" in session: + case True: + # Create form instance + form = CreatePostForm(request.form) + # Check if the request method is POST + match request.method == "POST": + case True: + # Retrieve post data from the form + postTitle = request.form["postTitle"] + postTags = request.form["postTags"] + postContent = request.form["postContent"] + postBanner = request.files["postBanner"].read() + postCategory = request.form["postCategory"] + # Check if post content is empty + match postContent == "": + case True: + # Flash an error message + flash("Post content not be empty.", "error") + Log.danger( + f'User: "{session["userName"]}" tried to create a post with empty content', + ) + case False: + # Check Recaptcha if enabled + match RECAPTCHA and RECAPTCHA_POST_CREATE: + case True: + # Verify Recaptcha response + secretResponse = request.form[ + "g-recaptcha-response" + ] + verifyResponse = requestsPost( + url=f"{RECAPTCHA_VERIFY_URL}?secret={RECAPTCHA_SECRET_KEY}&response={secretResponse}" + ).json() + # Check Recaptcha verification result + match verifyResponse[ + "success" + ] == True or verifyResponse["score"] > 0.5: + case True: + # Log the reCAPTCHA verification result + Log.success( + f"Post create reCAPTCHA| verification: {verifyResponse['success']} | verification score: {verifyResponse['score']}", + ) + Log.sql( + f"Connecting to '{DB_POSTS_ROOT}' database" + ) # Log the database connection is started + # Insert new post into the database + connection = sqlite3.connect(DB_POSTS_ROOT) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + cursor.execute( + "insert into posts(title,tags,content,banner,author,views,timeStamp,lastEditTimeStamp,category) \ + values(?, ?, ?, ?, ?, ?, ?, ?, ?)", + ( + postTitle, + postTags, + postContent, + postBanner, + session["userName"], + 0, + currentTimeStamp(), + currentTimeStamp(), + postCategory, + ), + ) + connection.commit() + Log.success( + f'Post: "{postTitle}" posted by "{session["userName"]}"', + ) + # Award points to the user for posting + addPoints(20, session["userName"]) + flash( + "You earned 20 points by posting.", + "success", + ) + return redirect("/") + case False: + # Recaptcha verification failed + Log.danger( + f"Post create reCAPTCHA | verification: {verifyResponse['success']} | verification score: {verifyResponse['score']}", + ) + abort(401) + case False: + # Recaptcha not enabled + Log.sql( + f"Connecting to '{DB_POSTS_ROOT}' database" + ) # Log the database connection is started + connection = sqlite3.connect(DB_POSTS_ROOT) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + cursor.execute( + "insert into posts(title,tags,content,banner,author,views,timeStamp,lastEditTimeStamp,category) \ + values(?, ?, ?, ?, ?, ?, ?, ?, ?)", + ( + postTitle, + postTags, + postContent, + postBanner, + session["userName"], + 0, + currentTimeStamp(), + currentTimeStamp(), + postCategory, + ), + ) + connection.commit() + Log.success( + f'Post: "{postTitle}" posted by "{session["userName"]}"', + ) + # Award points to the user for posting + addPoints(20, session["userName"]) + flash("You earned 20 points by posting.", "success") + return redirect("/") + # Render the create post template + return render_template( + "createPost.html.jinja", + form=form, + siteKey=RECAPTCHA_SITE_KEY, + recaptcha=RECAPTCHA, + ) + case False: + # User is not logged in + Log.danger( + f"{request.remote_addr} tried to create a new post without login" + ) + flash("You need loin for create a post.", "error") + return redirect("/login/redirect=&createpost") diff --git a/ast/routes/editPost.py b/ast/routes/editPost.py new file mode 100644 index 0000000000000000000000000000000000000000..258825bfba3239ae830cbcce95af058b1d691e54 --- /dev/null +++ b/ast/routes/editPost.py @@ -0,0 +1,260 @@ +# Import necessary modules and functions +from modules import ( + Log, # Logging module + flash, # Flash messaging module + abort, # Function for aborting requests + session, # Session management module + sqlite3, # SQLite database module + request, # Module for handling HTTP requests + redirect, # Function for redirecting requests + Blueprint, # Blueprint class for creating modular applications + RECAPTCHA, # Recaptcha module + requestsPost, # Module for making HTTP POST requests + DB_POSTS_ROOT, # Path to the posts database + DB_USERS_ROOT, # Path to the users database + CreatePostForm, # Form for creating a post + render_template, # Function for rendering templates + currentTimeStamp, # Function for getting current timestamp + RECAPTCHA_SITE_KEY, # Recaptcha site key + RECAPTCHA_POST_EDIT, # # Flag for enabling/disabling Recaptcha for post editing + RECAPTCHA_VERIFY_URL, # Recaptcha verification URL + RECAPTCHA_SECRET_KEY, # Recaptcha secret key +) + +# Create a blueprint for the edit post route +editPostBlueprint = Blueprint("editPost", __name__) + + +# Define a route for editing a post +@editPostBlueprint.route("/editpost/<int:postID>", methods=["GET", "POST"]) +def editPost(postID): + """ + This function handles the edit post route. + + Args: + postID (int): the ID of the post to edit + + Returns: + The rendered edit post template or a redirect to the homepage if the user is not authorized to edit the post + + Raises: + abort(404): if the post does not exist + abort(401): if the user is not authorized to edit the post + """ + # Check if "userName" exists in session + match "userName" in session: + case True: + Log.sql( + f"Connecting to '{DB_POSTS_ROOT}' database" + ) # Log the database connection is started + # Connect to the posts database + connection = sqlite3.connect(DB_POSTS_ROOT) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + cursor.execute("select id from posts") + posts = str(cursor.fetchall()) + # Check if postID exists in posts + match str(postID) in posts: + case True: + Log.sql( + f"Connecting to '{DB_POSTS_ROOT}' database" + ) # Log the database connection is started + # Connect to the posts database + connection = sqlite3.connect(DB_POSTS_ROOT) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + cursor.execute( + """select * from posts where id = ? """, + [(postID)], + ) + post = cursor.fetchone() + Log.success(f'POST: "{postID}" FOUND') + Log.sql( + f"Connecting to '{DB_USERS_ROOT}' database" + ) # Log the database connection is started + # Connect to the users database + connection = sqlite3.connect(DB_USERS_ROOT) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + cursor.execute( + """select userName from users where userName = ? """, + [(session["userName"])], + ) + # Check if the user is authorized to edit the post + match post[5] == session["userName"] or session["userRole"] == "admin": + case True: + # Populate the form with post data + form = CreatePostForm(request.form) + form.postTitle.data = post[1] + form.postTags.data = post[2] + form.postContent.data = post[3] + form.postCategory.data = post[9] + # Check if the request method is POST + match request.method == "POST": + case True: + # Retrieve post data from the form + postTitle = request.form["postTitle"] + postTags = request.form["postTags"] + postContent = request.form["postContent"] + postCategory = request.form["postCategory"] + postBanner = request.files["postBanner"].read() + # Check if post content is empty + match postContent == "": + case True: + flash("Post content not be empty.", "error") + Log.danger( + f'User: "{session["userName"]}" tried to edit a post with empty content', + ) + case False: + # Check Recaptcha if enabled + match RECAPTCHA and RECAPTCHA_POST_EDIT: + case True: + # Verify Recaptcha response + secretResponse = request.form[ + "g-recaptcha-response" + ] + verifyResponse = requestsPost( + url=f"{RECAPTCHA_VERIFY_URL}?secret={RECAPTCHA_SECRET_KEY}&response={secretResponse}" + ).json() + # Check Recaptcha verification result + match verifyResponse[ + "success" + ] == True or verifyResponse[ + "score" + ] > 0.5: + case True: + # Log the reCAPTCHA verification result + Log.success( + f"Post edit reCAPTCHA| verification: {verifyResponse['success']} | verification score: {verifyResponse['score']}", + ) + # Update post data in the database + connection = ( + sqlite3.connect( + DB_POSTS_ROOT + ) + ) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + cursor.execute( + """update posts set title = ? where id = ? """, + (postTitle, post[0]), + ) + cursor.execute( + """update posts set tags = ? where id = ? """, + (postTags, post[0]), + ) + cursor.execute( + """update posts set content = ? where id = ? """, + (postContent, post[0]), + ) + cursor.execute( + """update posts set category = ? where id = ? """, + (postCategory, post[0]), + ) + cursor.execute( + """update posts set banner = ? where id = ? """, + (postBanner, post[0]), + ) + cursor.execute( + """update posts set lastEditTimeStamp = ? where id = ? """, + [ + ( + currentTimeStamp() + ), + (post[0]), + ], + ) + connection.commit() + Log.success( + f'Post: "{postTitle}" edited', + ) + flash( + "Post edited.", + "success", + ) + return redirect( + f"/post/{post[0]}" + ) + case False: + # Recaptcha verification failed + Log.danger( + f"Post edit reCAPTCHA | verification: {verifyResponse['success']} | verification score: {verifyResponse['score']}", + ) + abort(401) + case False: + # Recaptcha not enabled + connection = sqlite3.connect( + DB_POSTS_ROOT + ) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + cursor.execute( + """update posts set title = ? where id = ? """, + (postTitle, post[0]), + ) + cursor.execute( + """update posts set tags = ? where id = ? """, + (postTags, post[0]), + ) + cursor.execute( + """update posts set content = ? where id = ? """, + (postContent, post[0]), + ) + cursor.execute( + """update posts set category = ? where id = ? """, + (postCategory, post[0]), + ) + cursor.execute( + """update posts set banner = ? where id = ? """, + (postBanner, post[0]), + ) + cursor.execute( + """update posts set lastEditTimeStamp = ? where id = ? """, + [ + (currentTimeStamp()), + (post[0]), + ], + ) + connection.commit() + Log.success( + f'Post: "{postTitle}" edited', + ) + flash("Post edited.", "success") + return redirect(f"/post/{post[0]}") + # Render the edit post template + return render_template( + "/editPost.html.jinja", + id=post[0], + title=post[1], + tags=post[2], + content=post[3], + form=form, + siteKey=RECAPTCHA_SITE_KEY, + recaptcha=RECAPTCHA, + ) + case False: + # User is not authorized to edit the post + flash("This post is not yours.", "error") + Log.danger( + f'User: "{session["userName"]}" tried to edit another authors post', + ) + return redirect("/") + case False: + # Post with postID does not exist + Log.danger(f'Post: "{postID}" not found') + return render_template("notFound.html.jinja") + case False: + # User is not logged in + Log.danger(f"{request.remote_addr} tried to edit post without login") + flash("You need login for edit a post.", "error") + return redirect(f"/login/redirect=&editpost&{postID}") diff --git a/ast/routes/post.py b/ast/routes/post.py new file mode 100644 index 0000000000000000000000000000000000000000..98079491df7c2a9c094b65fdbc1abc372c14e39b --- /dev/null +++ b/ast/routes/post.py @@ -0,0 +1,170 @@ +# Import necessary modules and functions +from modules import ( + Log, # Custom logging module + flash, # Flash messaging module + Delete, # Module for deleting posts and comments + session, # Session management module + sqlite3, # SQLite database module + request, # Request handling module + url_for, # URL generation module + APP_NAME, # Application name + redirect, # Redirect function + addPoints, # Function to add points to user's score + Blueprint, # Blueprint for defining routes + CommentForm, # Form class for comments + DB_POSTS_ROOT, # Path to the posts database + currentTimeStamp, # Function to get current timestamp + DB_COMMENTS_ROOT, # Path to the comments database + render_template, # Template rendering function +) + +# Create a blueprint for the post route +postBlueprint = Blueprint("post", __name__) + + +# Define the route handler for individual posts +@postBlueprint.route("/post/<int:postID>", methods=["GET", "POST"]) +def post(postID): + # Create a comment form object from the request form + form = CommentForm(request.form) + + Log.sql( + f"Connecting to '{DB_POSTS_ROOT}' database" + ) # Log the database connection is started + # Connect to the posts database + connection = sqlite3.connect(DB_POSTS_ROOT) + connection.set_trace_callback(Log.sql) # Set the trace callback for the connection + cursor = connection.cursor() + + # Query the posts database for all post IDs + cursor.execute("select id from posts") + posts = str(cursor.fetchall()) + + # Check if the requested post ID exists in the posts database + match str(postID) in posts: + case True: + # Log a message indicating that the post is found + Log.success(f'post: "{postID}" loaded') + + Log.sql( + f"Connecting to '{DB_POSTS_ROOT}' database" + ) # Log the database connection is started + # Connect to the posts database + connection = sqlite3.connect(DB_POSTS_ROOT) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + + # Query the posts database for the post with the matching ID + cursor.execute( + """select * from posts where id = ? """, + [(postID)], + ) + post = cursor.fetchone() + + # Increment the views of the post by 1 in the posts database + cursor.execute( + """update posts set views = views+1 where id = ? """, + [(postID)], + ) + connection.commit() + + # Handle POST requests + match request.method == "POST": + case True: + # Check if the post delete button is clicked + match "postDeleteButton" in request.form: + case True: + # Delete the post from the database + Delete.post(postID) + # Redirect to the home page + return redirect(f"/") + + # Check if the comment delete button is clicked + match "commentDeleteButton" in request.form: + case True: + # Delete the comment from the database + Delete.comment(request.form["commentID"]) + # Redirect to the same route with a 301 status code + return redirect(url_for("post.post", postID=postID)), 301 + + # Get the comment from the form + comment = request.form["comment"] + + Log.sql( + f"Connecting to '{DB_COMMENTS_ROOT}' database" + ) # Log the database connection is started + # Connect to the comments database + connection = sqlite3.connect(DB_COMMENTS_ROOT) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + + # Insert the comment into the comments database with the post ID, comment, user name, date, and time + cursor.execute( + "insert into comments(post,comment,user,timeStamp) \ + values(?, ?, ?, ?)", + ( + postID, + comment, + session["userName"], + currentTimeStamp(), + ), + ) + connection.commit() + + # Log a message indicating that the user commented on the post + Log.success( + f'User: "{session["userName"]}" commented to post: "{postID}"', + ) + + # Add 5 points to the user's score + addPoints(5, session["userName"]) + + # Flash a success message to the user + flash("You earned 5 points by commenting.", "success") + + # Redirect to the same route with a 301 status code + return redirect(url_for("post.post", postID=postID)), 301 + + Log.sql( + f"Connecting to '{DB_COMMENTS_ROOT}' database" + ) # Log the database connection is started + # Connect to the comments database + connection = sqlite3.connect(DB_COMMENTS_ROOT) + connection.set_trace_callback( + Log.sql + ) # Set the trace callback for the connection + cursor = connection.cursor() + + # Query the comments database for the comments related to the post ID + cursor.execute( + """select * from comments where post = ? order by timeStamp desc""", + [(postID)], + ) + comments = cursor.fetchall() + + # Render the post template with the post and comments data, the form object, and the app name + return render_template( + "post.html.jinja", + id=post[0], + title=post[1], + tags=post[2], + content=post[3], + author=post[5], + views=post[6], + timeStamp=post[7], + lastEditTimeStamp=post[8], + form=form, + comments=comments, + appName=APP_NAME, + ) + + case False: + Log.danger( + f"{request.remote_addr} tried to reach unknown post" + ) # Log a message with level 1 indicating the post is not found + # Render the 404 template if the post ID does not exist + return render_template("notFound.html.jinja")