diff --git a/.gitignore b/.gitignore index dbed2ff1f..7cbd27360 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +temp_pictures/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -101,4 +103,4 @@ ENV/ .mypy_cache/ # pycharm files -.idea/ \ No newline at end of file +.idea/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..53d8ec25d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "venv/bin/python3.7" +} \ No newline at end of file diff --git a/README.md b/README.md index 77a804e7f..cffe43817 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # wswp-ask-mate Web and SQL with Python / 1st TW week / Ask Mate project + +please run >>>>>>>>>>>>>>>>>> pip install -r requirements.txt + +Presentation: 1st week https://docs.google.com/presentation/d/1su5RmQHdyfDgrQiKhYdD0PO9AavsXH64DfdY59Jnf3A/edit?usp=sharing diff --git a/connection.py b/connection.py new file mode 100644 index 000000000..c9bda939a --- /dev/null +++ b/connection.py @@ -0,0 +1,51 @@ +# Creates a decorator to handle the database connection/cursor opening/closing. +# Creates the cursor with RealDictCursor, thus it returns real dictionaries, where the column names are the keys. +import os +import psycopg2 +import psycopg2.extras + + +def get_connection_string(): + # setup connection string + # to do this, please define these environment variables first + user_name = os.environ.get('PSQL_USER_NAME') + password = os.environ.get('PSQL_PASSWORD') + host = os.environ.get('PSQL_HOST') + database_name = os.environ.get('PSQL_DB_NAME') + + env_variables_defined = user_name and password and host and database_name + + if env_variables_defined: + # this string describes all info for psycopg2 to connect to the database + return 'postgresql://{user_name}:{password}@{host}/{database_name}'.format( + user_name=user_name, + password=password, + host=host, + database_name=database_name + ) + else: + raise KeyError('Some necessary environment variable(s) are not defined') + + +def open_database(): + try: + connection_string = get_connection_string() + connection = psycopg2.connect(connection_string) + connection.autocommit = True + except psycopg2.DatabaseError as exception: + print('Database connection problem') + raise exception + return connection + + +def connection_handler(function): + def wrapper(*args, **kwargs): + connection = open_database() + # we set the cursor_factory parameter to return with a RealDictCursor cursor (cursor which provide dictionaries) + dict_cur = connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + ret_value = function(dict_cur, *args, **kwargs) + dict_cur.close() + connection.close() + return ret_value + + return wrapper diff --git a/data_manager.py b/data_manager.py new file mode 100644 index 000000000..dd0784782 --- /dev/null +++ b/data_manager.py @@ -0,0 +1,751 @@ +import csv +import os + +from psycopg2 import sql, errorcodes, errors + +import connection +import psycopg2 +import connection +from datetime import datetime + + +@connection.connection_handler +def get_author_by_question_id(cursor, question_id): + cursor.execute(""" + SELECT user_name FROM question + WHERE id = %(q_id)s + """, + {"q_id": question_id}) + return cursor.fetchone() + + +@connection.connection_handler +def get_author_by_answer_id(cursor, answer_id): + cursor.execute(""" + SELECT user_name FROM answer + WHERE id = %(answer_id)s; + """, + {"answer_id": answer_id}) + + return cursor.fetchone() + + +def calculate_reputation(type, direction, original): + repchart_up = {"question": 5, "answer": 10, "accepted": 15} + repchart_down = {"question": -2, "answer": -2, "accepted": 0} + if direction == "vote_up": + rep = repchart_up + else: + rep = repchart_down + original = original['reputation'] + int(rep[type]) + return original + + +def annul_calc_reputation(type, direction, original): + repchart_up = {"question": -5, "answer": -10, "accepted": -15} + repchart_down = {"question": 2, "answer": 2, "accepted": 0} + if direction == "vote_up": + rep = repchart_down + else: + rep = repchart_up + original = original['reputation'] + rep[type] + return original + +@connection.connection_handler +def get_reputation(cursor, username): + cursor.execute(""" + SELECT reputation FROM users + WHERE name = %(user)s; + """, + {"user": username}) + return cursor.fetchone() + + +@connection.connection_handler +def update_user_reputation(cursor, username, value): + cursor.execute(""" + UPDATE users + SET reputation = %(reputation)s + WHERE name = %(user)s + """, + {"user": username, "reputation": value}) + + +@connection.connection_handler +def get_all_questions(cursor, sortby, order): + order = 'DESC' if order == 'DESC' else 'ASC' + cursor.execute(sql.SQL(""" + SELECT * from question + ORDER BY {0} {1}""").format(sql.Identifier(sortby), + sql.SQL(order))) # careful with this, no userinput allowed to go into here + data = cursor.fetchall() + return data + + +@connection.connection_handler +def get_latest_questions(cursor): + cursor.execute(""" + SELECT * from question + ORDER BY submission_time DESC + LIMIT 5; + """) + data = cursor.fetchall() + return data + + +@connection.connection_handler +def modify_view_number(cursor, question_id): + cursor.execute(""" + UPDATE question + SET view_number = view_number + 1 + WHERE id = %(question_id)s + """, {'question_id': question_id}); + + +@connection.connection_handler +def delete_answer(cursor, answer_id): + cursor.execute(""" + DELETE FROM comment + WHERE answer_id = %(answer_id)s""", + {'answer_id': answer_id}); + + cursor.execute(""" + UPDATE question + SET accepted_answer = NULL + WHERE accepted_answer = %(answer_id)s""", + {"answer_id": answer_id}); + + cursor.execute(""" + DELETE FROM votes + WHERE answer_id = %(answer_id)s""", + {'answer_id': answer_id}) + + cursor.execute(""" + DELETE FROM answer + WHERE id = %(answer_id)s""", + {'answer_id': answer_id}); + + +@connection.connection_handler +def delete_question(cursor, question_id): + cursor.execute(""" + DELETE FROM comment + WHERE question_id = %(question_id)s + """, + {'question_id': question_id} + ); + + cursor.execute(""" + UPDATE question + SET accepted_answer = NULL + WHERE id = %(question_id)s""", + {"question_id": question_id}); + + cursor.execute(""" + DELETE FROM votes + WHERE question_id = %(question_id)s + """, + {'question_id': question_id}) + + answers = get_answers_by_question_id(question_id) + print(answers) + for answer in answers: + delete_answer(answer['id']) + + cursor.execute(""" + DELETE FROM answer + WHERE question_id = %(question_id)s + """, + {'question_id': question_id} + ); + + cursor.execute(""" + DELETE FROM question + WHERE id = %(question_id)s + """, + {'question_id': question_id} + ); + + +def allowed_image(filename, extensions): + """checks if filename falls within the restrictions""" + if not "." in filename: + return False + + ext = filename.rsplit(".", 1)[1] + + if ext.upper() in extensions: + return True + else: + return False + + +@connection.connection_handler +def upload_image_to_question(cursor, question_id, image_name): + """ appends the image_name to the 'image' column at the question_id + ARGS: + question_id(string): this is the ID that the image_name appends to + image_name(string): validation is not happening here + """ + cursor.execute(""" + UPDATE question + SET image = %(image_name)s + WHERE id = %(question_id)s; + """, + {'image_name': image_name, + 'question_id': question_id}); + + +@connection.connection_handler +def write_new_question_to_database(cursor, new_question): + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + cursor.execute(""" + INSERT INTO question (submission_time, view_number, vote_number, title, message, image, user_name) + VALUES (%(time)s, %(view_number)s, %(vote_number)s, %(title)s, %(message)s, %(image)s, %(user_name)s); + """, + {"time": dt, + "view_number": 0, + "vote_number": 0, + "title": new_question['title'], + "message": new_question['message'], + "image": new_question["image"], + "user_name": new_question["user_name"] + }) + + +@connection.connection_handler +def write_new_answer_to_database(cursor, question_id, answer): + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + new_answer = answer["message"] + new_image = answer["image"] + user_name = answer["user_name"] + cursor.execute(""" + INSERT INTO answer (submission_time, vote_number, question_id, message, image, user_name) + VALUES (%(time)s, %(vote_number)s, %(question_id)s, %(message)s, %(image)s, %(user_name)s) + """, + { + "time": dt, + "vote_number": 0, + "question_id": question_id, + "message": new_answer, + "image": new_image, + "user_name": user_name + }) + + +@connection.connection_handler +def write_new_comment_to_database(cursor, data): + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + try: + if data["answer_id"]: + pass + except KeyError: + data.update({"answer_id": None}) + + cursor.execute(""" + INSERT INTO comment (question_id, answer_id, message, submission_time, edited_count, user_name) + VALUES (%(question_id)s, %(answer_id)s, %(message)s, %(time)s, %(edit)s, %(user_name)s); + """, + {"question_id": data["question_id"], + "answer_id": data["answer_id"], + "message": data["message"], + "time": dt, + "edit": 0, + "user_name": data["user_name"]}) + + +@connection.connection_handler +def edit_comment(cursor, comment_id, message): + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + cursor.execute(""" + UPDATE comment + SET message = %(message)s, edited_count = edited_count + 1, submission_time = %(submission_time)s + WHERE id = %(comment_id)s; + """, + {'comment_id': comment_id, + 'submission_time': dt, + 'message': message}) + + +@connection.connection_handler +def delete_comment(cursor, comment_id): + cursor.execute(""" + DELETE from comment + WHERE id = %(comment_id)s; + """, + {'comment_id': comment_id}) + + +@connection.connection_handler +def get_question_by_id(cursor, question_id): + cursor.execute(""" + SELECT question.id, + question.submission_time, + question.view_number, + question.vote_number, + question.title, + question.message, + question.image, + question.user_name, + question.accepted_answer, + users.id as user_id + FROM question + LEFT JOIN users ON question.user_name = users.name + WHERE question.id = %(question_id)s; + """, + {'question_id': question_id}) + + question = cursor.fetchone() + return question + + +@connection.connection_handler +def get_answers_by_question_id(cursor, question_id): + cursor.execute(""" + SELECT + CASE + WHEN question.accepted_answer = answer.id THEN 1 ELSE 0 + END as accepted, + answer.id, + answer.user_name, + users.reputation, + answer.submission_time, + answer.vote_number, + answer.message, + answer.question_id, + answer.image, + CASE + when votes.vote_method = -1 THEN -1 + when votes.vote_method = 1 then 1 ELSE 0 + END as vote_method + FROM answer + LEFT JOIN question ON answer.question_id = question.id + LEFT JOIN users ON answer.user_name = users.name + LEFT JOIN votes ON answer.id = votes.answer_id + WHERE answer.question_id = %(question_id)s + ORDER BY accepted DESC, vote_number DESC, submission_time ASC; + """, + {'question_id': question_id}) + + answers = cursor.fetchall() + return answers + + +@connection.connection_handler +def update_question(cursor, question_id, updated_question): + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + cursor.execute(""" + UPDATE question + SET submission_time = %(time)s, title = %(title)s, message = %(message)s, image = %(new_image)s + WHERE id = %(question_id)s; + """, + {'time': dt, + 'title': updated_question['title'], + 'message': updated_question['message'], + 'new_image': updated_question['image'], + 'question_id': question_id}); + + +@connection.connection_handler +def check_if_user_voted_on_question(cursor, user, question): + cursor.execute(""" + SELECT * FROM votes + WHERE user_name = %(user)s AND question_id = %(question)s; + """, + { + "user": user, + "question": question + }) + + result = cursor.fetchone() + return result + + +@connection.connection_handler +def vote_question(cursor, direction, question_id): + if direction == "vote_up": + cursor.execute(""" + UPDATE question + SET vote_number = vote_number + 1 + WHERE id = %(question_id)s + """, {'question_id': question_id}); + else: + cursor.execute(""" + UPDATE question + SET vote_number = vote_number - 1 + WHERE id = %(question_id)s + """, {'question_id': question_id}); + + +@connection.connection_handler +def create_vote_on_question_in_votes_db(cursor, question_id, user): + vote = -1 if user["vote_method"] == "vote_down" else 1 + + cursor.execute(""" + INSERT INTO votes (user_id, user_name, question_id, vote_method) + VALUES (%(user_id)s, %(user_name)s, %(question_id)s, %(vote_method)s); + """,{ + "question_id": question_id, + "user_id": user['id'], + "user_name": user['user_name'], + "vote_method": vote + }) + +@connection.connection_handler +def delete_vote_on_question_from_votes_db(cursor, vote_data, vote_method): + vote_value = -1 if vote_method == "vote_down" else 1 + result_vote = vote_data['vote_method'] + vote_value + + if result_vote == 0: + cursor.execute(""" + DELETE FROM votes + WHERE question_id = %(question_id)s + """, {'question_id': vote_data['question_id']}) + + return True + else: + return False + + +@connection.connection_handler +def search_question(cursor, search_phrase): + cursor.execute(""" + SELECT * FROM question + WHERE LOWER(title) LIKE %(search_phrase)s OR LOWER(message) LIKE %(search_phrase)s + """, + {'search_phrase': '%' + search_phrase + '%'}) + + question_result = cursor.fetchall() + + cursor.execute(""" + SELECT * FROM answer + WHERE LOWER(message) LIKE %(search_phrase)s + """, + {'search_phrase': '%' + search_phrase + '%'}) + answer_result = cursor.fetchall() + + for result in answer_result: + result["title"] = "Answer to question" + result["id"] = result["question_id"] + + search_result = question_result + answer_result + if not search_result: + return None + return search_result + + +@connection.connection_handler +def check_if_user_voted_on_answer(cursor, user, answer): + cursor.execute(""" + SELECT * FROM votes + WHERE user_name = %(user)s AND answer_id = %(answer)s; + """, + { + "user": user, + "answer": answer + }) + + result = cursor.fetchone() + return result + + +@connection.connection_handler +def vote_answer(cursor, direction, answer_id): + if direction == "vote_up": + cursor.execute(""" + UPDATE answer + SET vote_number = vote_number + 1 + WHERE id = %(answer_id)s + """, {'answer_id': answer_id}); + else: + cursor.execute(""" + UPDATE answer + SET vote_number = vote_number - 1 + WHERE id = %(answer_id)s + """, {'answer_id': answer_id}); + + +@connection.connection_handler +def create_vote_on_answer_in_votes_db(cursor, answer_id, user): + vote = -1 if user["vote_method"] == "vote_down" else 1 + + cursor.execute(""" + INSERT INTO votes (user_id, user_name, answer_id, vote_method) + VALUES (%(user_id)s, %(user_name)s, %(answer_id)s, %(vote_method)s); + """,{ + "answer_id": answer_id, + "user_id": user['id'], + "user_name": user['user_name'], + "vote_method": vote + }) + + +@connection.connection_handler +def delete_vote_on_answer_from_votes_db(cursor, vote_data, vote_method): + vote_value = -1 if vote_method == "vote_down" else 1 + result_vote = vote_data['vote_method'] + vote_value + + if result_vote == 0: + cursor.execute(""" + DELETE FROM votes + WHERE answer_id = %(answer_id)s + """, {'answer_id': vote_data['answer_id']}) + # van-e itt egyáltalán answer_id??? + return True + else: + return False + + +@connection.connection_handler +def get_answer_by_answer_id(cursor, answer_id): + cursor.execute(""" + SELECT * from answer + WHERE id = %(answer_id)s""", + {'answer_id': answer_id} + ) + data = cursor.fetchone() + return data + + +@connection.connection_handler +def get_comment_by_comment_id(cursor, comment_id): + cursor.execute(""" + SELECT * FROM comment + WHERE id = %(comment_id)s + """, + {'comment_id': comment_id}) + data = cursor.fetchone() + return data + + +@connection.connection_handler +def update_answer(cursor, answer_id, update_answer): + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + message = update_answer['message'] + cursor.execute(""" + UPDATE answer + SET submission_time = %(time)s, message = %(message)s, image=%(new_image)s + WHERE id = %(answer_id)s; + """, + {'time': dt, + 'message': message, + 'new_image': update_answer['image'], + 'answer_id': answer_id}) + + +@connection.connection_handler +def find_comments(cursor, question_id): + cursor.execute(""" + SELECT comment.id, + comment.question_id, + comment.answer_id, + comment.message, + comment.submission_time, + comment.edited_count, + comment.user_name, + users.reputation FROM comment + LEFT JOIN users ON comment.user_name = users.name + WHERE comment.question_id = %(question_id)s + ORDER BY comment.id;""", + {'question_id': question_id}) + + comments = cursor.fetchall() + return comments + + +@connection.connection_handler +def create_user(cursor, username, password): + dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + try: + cursor.execute(""" + INSERT INTO users + VALUES (DEFAULT , %(username)s, %(password)s, %(registration_time)s, DEFAULT); + """, {'username': username, + 'password': password, + 'registration_time': dt}) + except psycopg2.errors.UniqueViolation: + return False + return True + + +@connection.connection_handler +def get_user_password(cursor, username): + cursor.execute(""" + SELECT password FROM users + WHERE name = %(username)s; + """, {'username': username}) + password = cursor.fetchone() + return password + + +@connection.connection_handler +def sort_questions(cursor, order_by, order_direction): + cursor.execute(sql.SQL(""" + SELECT * FROM question + ORDER BY {0} {1};""").format(sql.Identifier(order_by), sql.SQL(order_direction))) + + data = cursor.fetchall() + return data + + +@connection.connection_handler +def get_user(cursor, username): + cursor.execute(""" + SELECT name, password FROM users + WHERE name = %(username)s + """, + {'username': username}) + + user = cursor.fetchone() + return user + + +@connection.connection_handler +def get_user_list(cursor): + cursor.execute(""" +SELECT u.id, u.reputation, u.name, date(u.registration_date) as member_since, + count(DISTINCT q.id) as question, +count(DISTINCT a.id) as answer, +count(DISTINCT c.id) as comment +from users as u +left outer join question q on u.name = q.user_name +left outer join answer a on u.name = a.user_name +left outer join comment c on u.name = c.user_name +GROUP BY u.id""") + + all_user_attribute = cursor.fetchall() + return all_user_attribute + + + +@connection.connection_handler +def get_user_id(cursor, username): + cursor.execute(""" + SELECT id from users + WHERE name = %(username)s; + """, + {'username': username}) + + user = cursor.fetchone() + + return user['id'] + + + +@connection.connection_handler +def get_user_name(cursor, user_id): + cursor.execute(""" + SELECT name from users + WHERE id = %(user_id)s; + """, + {'user_id': user_id}) + + user_name = cursor.fetchone() + return user_name + + +@connection.connection_handler +def get_user_attributes(cursor, user_id): + cursor.execute(""" + SELECT q.* + from question q + left outer join users u on u.name = q.user_name + left outer join answer a on u.name = a.user_name + left outer join comment c on u.name = c.user_name + WHERE u.id = %(user_id)s + GROUP BY u.id, a.id, q.id, c.id; + """, + {'user_id': user_id}) + user_attributes = cursor.fetchall() + return user_attributes + + +@connection.connection_handler +def get_user_questions(cursor, user_id): + cursor.execute(""" + SELECT q.* + from question as q + join users u on u.name = q.user_name + WHERE u.id = %(user_id)s; + """, + {'user_id': user_id}) + user_questions = cursor.fetchall() + return user_questions + + +@connection.connection_handler +def get_user_answers(cursor, user_id): + cursor.execute(""" + SELECT q.* + from question as q + left outer join answer a on a.question_id = q.id + join users u on a.user_name = u.name + WHERE u.id = %(user_id)s; + """, + {'user_id': user_id}) + user_answers = cursor.fetchall() + return user_answers + + +@connection.connection_handler +def get_user_comments(cursor, user_id): + cursor.execute(""" + SELECT q.* + from question as q + + left outer join comment c on c.question_id = q.id + left outer join answer a on c.answer_id = a.id + join users u on a.user_name = u.name OR q.user_name = u.name + WHERE u.id = %(user_id)s; + """, + {'user_id': user_id}) + user_comments = cursor.fetchall() + return user_comments + + +@connection.connection_handler +def get_user_id_by_name(cursor, username): + cursor.execute(""" + SELECT id FROM users + WHERE name = %(username)s + """, + {"username": username}) + + user_id = cursor.fetchone() + return user_id + + +@connection.connection_handler +def set_new_accepted_answer(cursor, question_id, accepted_answer_id): + author_id = get_accepted_author_id(question_id) + if get_author_by_answer_id(author_id) is not None: + author = get_author_by_answer_id(author_id)["user_name"] + author_repu = get_reputation(author) + new_repu = annul_calc_reputation("accepted", "vote_down", author_repu) + update_user_reputation(author, new_repu) + + cursor.execute(""" + UPDATE question + SET accepted_answer = %(answer)s + WHERE id = %(qid)s; + """, + {"answer":accepted_answer_id, "qid": question_id} + ) + + author_id = get_accepted_author_id(question_id) + author = get_author_by_answer_id(author_id)["user_name"] + author_repu = get_reputation(author) + new_repu = calculate_reputation("accepted", "vote_up", author_repu) + update_user_reputation(author, new_repu) + + +@connection.connection_handler +def get_accepted_author_id(cursor, question_id): + cursor.execute(""" + SELECT accepted_answer + FROM question + WHERE id = %(qid)s""", + {'qid': question_id}) + author_id = cursor.fetchone() + + return author_id['accepted_answer'] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..a6d2d3aa7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +Click==7.0 +Flask==1.1.1 +itsdangerous==1.1.0 +Jinja2==2.10.3 +MarkupSafe==1.1.1 +Werkzeug==0.16.0 diff --git a/sample_data/answer.csv b/sample_data/answer.csv index 3ddfa1ffb..ff6b00e76 100644 --- a/sample_data/answer.csv +++ b/sample_data/answer.csv @@ -1,3 +1,9 @@ -id,submission_time,vote_number,question_id,message,image -0,1493398154,4,0,"You need to use brackets: my_list = []", -1,1493088154,35,0,"Look it up in the Python docs", +id,submission_time,vote_number,question_id,message,image +0,1493398154,4,0,You need to use brackets: my_list = [], +1,1493088154,35,0,Look it up in the Python docs, +2,1573804434,2,3,"Yes, really. + +Codecool is about asking.", +3,1573804448,11,3,You know that there are no stupid questions..., +4,1573804458,0,3,(Maybe that's not completely true...), +5,1573804552,0,4,"You could, but why would you?", diff --git a/sample_data/askmatepart2-sample-data.sql b/sample_data/askmatepart2-sample-data.sql new file mode 100644 index 000000000..08748ef29 --- /dev/null +++ b/sample_data/askmatepart2-sample-data.sql @@ -0,0 +1,191 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 9.5.6 +-- Dumped by pg_dump version 9.5.6 + +ALTER TABLE IF EXISTS ONLY public.question DROP CONSTRAINT IF EXISTS pk_question_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.answer DROP CONSTRAINT IF EXISTS pk_answer_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.answer DROP CONSTRAINT IF EXISTS fk_question_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.comment DROP CONSTRAINT IF EXISTS pk_comment_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.comment DROP CONSTRAINT IF EXISTS fk_question_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.comment DROP CONSTRAINT IF EXISTS fk_answer_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.question_tag DROP CONSTRAINT IF EXISTS pk_question_tag_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.question_tag DROP CONSTRAINT IF EXISTS fk_question_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.tag DROP CONSTRAINT IF EXISTS pk_tag_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.question_tag DROP CONSTRAINT IF EXISTS fk_tag_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.users DROP CONSTRAINT IF EXISTS pk_user_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.question DROP CONSTRAINT IF EXISTS fk_answer_id CASCADE; + +DROP TABLE IF EXISTS public.question; +DROP SEQUENCE IF EXISTS public.question_id_seq; +CREATE TABLE question ( + id serial NOT NULL, + submission_time timestamp without time zone, + view_number integer, + vote_number integer, + title text, + message text, + image text, + user_name varchar(50), + accepted_answer integer +); + +DROP TABLE IF EXISTS public.answer; +DROP SEQUENCE IF EXISTS public.answer_id_seq; +CREATE TABLE answer ( + id serial NOT NULL, + submission_time timestamp without time zone, + vote_number integer, + question_id integer, + message text, + image text, + user_name varchar(50) +); + +DROP TABLE IF EXISTS public.comment; +DROP SEQUENCE IF EXISTS public.comment_id_seq; +CREATE TABLE comment ( + id serial NOT NULL, + question_id integer, + answer_id integer, + message text, + submission_time timestamp without time zone, + edited_count integer, + user_name varchar(50) +); + + +DROP TABLE IF EXISTS public.question_tag; +CREATE TABLE question_tag ( + question_id integer NOT NULL, + tag_id integer NOT NULL +); + +DROP TABLE IF EXISTS public.tag; +DROP SEQUENCE IF EXISTS public.tag_id_seq; +CREATE TABLE tag ( + id serial NOT NULL, + name text +); + +DROP TABLE IF EXISTS public.users; +DROP SEQUENCE IF EXISTS public.users_id_seq; +CREATE TABLE users ( + id serial NOT NULL, + name varchar(50) UNIQUE, + password varchar(60), + registration_date timestamp without time zone, + reputation integer +); + +DROP TABLE IF EXISTS public.votes; +CREATE TABLE votes ( + id serial NOT NULL, + user_id integer NOT NULL, + user_name varchar(50) NOT NULL, + question_id integer, + answer_id integer, + vote_method int NOT NULL check (vote_method between -1 and 1) +); + +ALTER TABLE ONLY users + ADD CONSTRAINT pk_user_id PRIMARY KEY (id); + +ALTER TABLE ONLY answer + ADD CONSTRAINT pk_answer_id PRIMARY KEY (id); + +ALTER TABLE ONLY comment + ADD CONSTRAINT pk_comment_id PRIMARY KEY (id); + +ALTER TABLE ONLY question + ADD CONSTRAINT pk_question_id PRIMARY KEY (id); + +ALTER TABLE ONLY question + ADD CONSTRAINT fk_user_name FOREIGN KEY (user_name) REFERENCES users(name); + +ALTER TABLE ONLY question_tag + ADD CONSTRAINT pk_question_tag_id PRIMARY KEY (question_id, tag_id); + +ALTER TABLE ONLY tag + ADD CONSTRAINT pk_tag_id PRIMARY KEY (id); + +ALTER TABLE ONLY votes + ADD CONSTRAINT pk_votes_id PRIMARY KEY (id); + +ALTER TABLE ONLY comment + ADD CONSTRAINT fk_answer_id FOREIGN KEY (answer_id) REFERENCES answer(id); + +ALTER TABLE ONLY comment + ADD CONSTRAINT fk_user_name FOREIGN KEY (user_name) REFERENCES users(name); + +ALTER TABLE ONLY answer + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES question(id); + +ALTER TABLE ONLY answer + ADD CONSTRAINT fk_user_name FOREIGN KEY (user_name) REFERENCES users(name); + +ALTER TABLE ONLY question_tag + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES question(id); + +ALTER TABLE ONLY comment + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES question(id); + +ALTER TABLE ONLY question_tag + ADD CONSTRAINT fk_tag_id FOREIGN KEY (tag_id) REFERENCES tag(id); + +ALTER TABLE ONLY votes + ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id); + +ALTER TABLE ONLY votes + ADD CONSTRAINT fk_user_name FOREIGN KEY (user_name) REFERENCES users(name); + +ALTER TABLE ONLY votes + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES question(id); + +ALTER TABLE ONLY votes + ADD CONSTRAINT fk_answer_id FOREIGN KEY (answer_id) REFERENCES answer(id); + +ALTER TABLE ONLY question + ADD CONSTRAINT fk_answer_id FOREIGN KEY (accepted_answer) REFERENCES answer(id); + +ALTER TABLE ONLY users +ALTER COLUMN reputation + SET DEFAULT 0; + +INSERT INTO question VALUES (0, '2017-04-28 08:29:00', 29, 7, 'How to make lists in Python?', 'I am totally new to this, any hints?', NULL, 'admin'); +INSERT INTO question VALUES (1, '2017-04-29 09:19:00', 15, 9, 'Wordpress loading multiple jQuery Versions', 'I developed a plugin that uses the jquery booklet plugin (http://builtbywill.com/booklet/#/) this plugin binds a function to $ so I cann call $(".myBook").booklet(); +' || + '' || + ' +I could easy managing the loading order with wp_enqueue_script so first I load jquery then I load booklet so everything is fine. + +BUT in my theme i also using jquery via webpack so the loading order is now following: + +jquery +booklet +app.js (bundled file with webpack, including jquery)', 'image1.png', 'admin'); +INSERT INTO question VALUES (2, '2017-05-01 10:41:00', 1364, 57, 'Drawing canvas with an image picked with Cordova Camera Plugin', 'I''m getting an image from device and drawing a canvas with filters using Pixi JS. It works all well using computer to get an image. But when I''m on IOS, it throws errors such as cross origin issue, or that I''m trying to use an unknown format. +', NULL, 'admin'); +SELECT pg_catalog.setval('question_id_seq', 2, true); + +INSERT INTO answer VALUES (1, '2017-04-28 16:49:00', 4, 0, 'You need to use brackets: my_list = []', NULL); +INSERT INTO answer VALUES (2, '2017-04-25 14:42:00', 35, 0, 'Look it up in the Python docs', 'image2.jpg'); +SELECT pg_catalog.setval('answer_id_seq', 2, true); + +INSERT INTO comment VALUES (1, 0, NULL, 'Please clarify the question as it is too vague!', '2017-05-01 05:49:00', 0, 'admin'); +INSERT INTO comment VALUES (2, NULL, 1, 'I think you could use my_list = list() as well.', '2017-05-02 16:55:00', 0, 'admin'); +SELECT pg_catalog.setval('comment_id_seq', 2, true); + +INSERT INTO tag VALUES (1, 'python'); +INSERT INTO tag VALUES (2, 'sql'); +INSERT INTO tag VALUES (3, 'css'); +SELECT pg_catalog.setval('tag_id_seq', 3, true); + +INSERT INTO question_tag VALUES (0, 1); +INSERT INTO question_tag VALUES (1, 3); +INSERT INTO question_tag VALUES (2, 3); + +INSERT INTO users VALUES(0, 'admin', '$2b$12$8rdtcWmFIfRzpEspyjJBReFoNF463wpCy2UFKZiuepVXdMOy8Eva6' ,'2019-12-01 08:00:00', 0); + diff --git a/sample_data/question.csv b/sample_data/question.csv index e65825dc3..6d21de2c1 100644 --- a/sample_data/question.csv +++ b/sample_data/question.csv @@ -1,14 +1,10 @@ -id,submission_time,view_number,vote_number,title,message,image -0,1493368154,29,7,"How to make lists in Python?","I am totally new to this, any hints?", -1,1493068124,15,9,"Wordpress loading multiple jQuery Versions","I developed a plugin that uses the jquery booklet plugin (http://builtbywill.com/booklet/#/) this plugin binds a function to $ so I cann call $('.myBook').booklet(); - -I could easy managing the loading order with wp_enqueue_script so first I load jquery then I load booklet so everything is fine. - -BUT in my theme i also using jquery via webpack so the loading order is now following: - -jquery -booklet -app.js (bundled file with webpack, including jquery)","images/image1.png" -2,1493015432,1364,57,"Drawing canvas with an image picked with Cordova Camera Plugin","I'm getting an image from device and drawing a canvas with filters using Pixi JS. It works all well using computer to get an image. But when I'm on IOS, it throws errors such as cross origin issue, or that I'm trying to use an unknown format. - -This is the code I'm using to draw the image (that works on web/desktop but not cordova built ios app)", +id,submission_time,view_number,vote_number,title,message,image +0,1493368154,64,7,How to make lists in Python?,"I am totally new to this, any hints?", +1,1574708949,61,9,hello,bello,image1.png +2,1574708719,1435,57,hello,"bello + +This is the code I'm using to draw the image (that works on web/desktop but not cordova built ios app)", +3,1573804413,24,3,Can I ask anything?,Anything? Really?,ask_anything.jpg +4,1573804612,4,0,Xmas,Can I wish you a merry Christmas in November?,xmas_november.jpg +5,1574690071,1,0,asdfasdfa,asdfsadf, +6,1574690077,0,0,asfasdfsadfasdfas,asdfdfdfff, diff --git a/server.py b/server.py new file mode 100644 index 000000000..6349fc23e --- /dev/null +++ b/server.py @@ -0,0 +1,358 @@ +from flask import Flask, render_template, redirect, request, url_for, g, session +from werkzeug.utils import secure_filename +import util +import os +import data_manager + +app = Flask(__name__) +app.secret_key = os.urandom(24) +app.config["IMAGE_UPLOADS"] = "./static/images" +app.config["ALLOWED_IMAGE_EXTENSIONS"] = ["JPEG", "JPG", "PNG", "GIF"] +app.config["MAX_IMAGE_FILESIZE"] = 0.5 * 1024 * 1024 + +QUESTION_HEADERS = ["id", "submission_time", "view_number", "vote_number", "title", "message", "image"] +ANSWER_HEADERS = ["id", "submission_time", "vote_number", "question_id", "message", "image"] + + +@app.before_request +def before_request(): + g.user = None + if 'user' in session: + g.user = session['user'] + g.user_id = session['user_id'] + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'GET': + return render_template('login.html') + session.pop('user', None) + hashed_password = data_manager.get_user_password(request.form.get('username')) + check_password = util.verify_password(request.form.get('password'), + hashed_password['password'] if hashed_password else None) + if hashed_password is None or check_password is False: + return render_template('login.html', error=True) + session['user'] = request.form.get('username') + session['user_id'] = data_manager.get_user_id(session['user']) + + return redirect(url_for('index')) + + +@app.route('/logout') +def logout(): + session.pop('user', None) + return redirect(url_for("login")) + + +@app.route('/registration', methods=['GET', 'POST']) +def registration(): + if request.method == 'GET': + return render_template('register.html') + if request.form.get('password') != request.form.get('confirm-password'): + return render_template('register.html', error="Password and Confirm password doesn't match!") + password = util.hash_password(request.form.get('password')) + username = request.form.get('username') + if data_manager.get_user(username): + return render_template('register.html', error='This username already exists!') + user = data_manager.create_user(username, password) + if user is False: + return render_template('register.html', error='This username already exists!') + return redirect(url_for('login')) + + +@app.route('/', methods=['GET', 'POST']) +def index(): + if g.user: + data = data_manager.get_latest_questions() + return render_template("list.html", all_questions=data) + return redirect(url_for('login')) + + +@app.route('/list') +def sort(): + if request.args.get('order_by') is None: + data = data_manager.get_all_questions("submission_time", "DESC") + else: + data = data_manager.sort_questions(request.args.get('order_by'), request.args.get('order_direction')) + return render_template('list.html', + all_questions=data) + + +@app.route('/add-questions', methods=['GET', 'POST']) +def add_new_question(): + if request.method == 'POST': + new_question = dict(request.form) + image = request.files['image'] + if image.filename == "" or image.filename is None: + new_question['image'] = "" + else: + image.save(os.path.join(app.config['IMAGE_UPLOADS'], image.filename)) + new_question["image"] = image.filename + username = session['user'] + new_question.update({"user_name": username}) + data_manager.write_new_question_to_database(new_question) + return redirect('/') + return render_template('list.html') + + +@app.route('/question//new-comment', methods=['GET', 'POST']) +@app.route('/answer///new-comment', methods=['GET', 'POST']) +def write_new_comment(question_id, answer_id=None): + if request.method == 'POST': + comment = request.form.to_dict() + username = session['user'] + comment.update({"question_id": question_id, "user_name": username}) + data_manager.write_new_comment_to_database(comment) + return redirect(url_for("manage_questions", question_id=question_id)) + + if answer_id: + id_type = "answer_id" + id = answer_id + route = url_for('write_new_comment', question_id=question_id, answer_id=answer_id) + labelaction = "Add new comment for the answer" + else: + id_type = "question_id" + id = question_id + route = url_for('write_new_comment', question_id=question_id, answer_id=None) + labelaction = "Add new comment for the question" + return render_template("comment.html", + id_type=id_type, + id=id, + sending_route=route, + labelaction=labelaction, + method="POST") + + +@app.route('/questions/') +def manage_questions(question_id): + if request.args.getlist('addinganswer'): + addinganswer = True + else: + addinganswer = False + + current_question = data_manager.get_question_by_id(question_id) + answers_to_question = data_manager.get_answers_by_question_id(question_id) + reputation = data_manager.get_reputation(current_question['user_name']) + current_question['reputation'] = reputation['reputation'] + comments = data_manager.find_comments(question_id) + + if 'user' in session: + question_vote = data_manager.check_if_user_voted_on_question(session['user'], question_id) + else: + question_vote = [] + + return render_template("question-child.html", + question_id=int(question_id), + comments=comments, + question=current_question, + answers=answers_to_question, + addinganswer=addinganswer, + question_headers=QUESTION_HEADERS, + answer_headers=ANSWER_HEADERS, + question_vote=question_vote) + + +@app.route('/modify_view/') +def modify_view(question_id): + data_manager.modify_view_number(question_id) + return redirect(url_for('manage_questions', question_id=question_id)) + + +@app.route('/question//edit', methods=['GET', 'POST']) +def edit_question(question_id): + current_question = data_manager.get_question_by_id(question_id) + + if request.method == 'POST': + updated_question = dict(request.form) + image = request.files['image'] + if image.filename == "" or image.filename is None: + updated_question['image'] = current_question["image"] + else: + image.save(os.path.join(app.config['IMAGE_UPLOADS'], image.filename)) + updated_question["image"] = image.filename + data_manager.update_question(question_id, updated_question) + return redirect("/") + return render_template("add_question_or_answer.html", + question_id=question_id, + question=current_question) + + +@app.route('/answer//edit', methods=['GET', 'POST']) +def edit_answer(answer_id): + current_answer = data_manager.get_answer_by_answer_id(answer_id) + question_id = current_answer['question_id'] + if request.method == "POST": + update_answer = dict(request.form) + data_manager.update_answer(answer_id, update_answer) + return redirect(f'/questions/{question_id}') + + return render_template("edit-answer.html", + answer_id=answer_id, + answer=current_answer) + + +@app.route('/comment//edit', methods=['GET', 'POST']) +def edit_comment(comment_id): + if request.method == "POST": + message = request.form.get("message") + question_id = request.form.get("question_id") + data_manager.edit_comment(comment_id, message) + + return redirect(url_for('manage_questions', question_id=question_id)) + + commentdata = data_manager.get_comment_by_comment_id(comment_id) + + return render_template("comment.html", + id_type="question_id", + id=commentdata["question_id"], # need this for the post request redirection + sending_route=f'/comment/{comment_id}/edit', + labelaction='Edit comment', + method="POST", + message=commentdata["message"], ) + + +@app.route('/accept//') +def accept_answer(question_id, accepted_answer_id): + data_manager.set_new_accepted_answer(question_id, accepted_answer_id) + return redirect(url_for('manage_questions', question_id=question_id)) + + +@app.route('/question//') +def vote_questions(vote_method, question_id): + user_name = session["user"] + user = data_manager.get_user_id_by_name(user_name) + user.update({"user_name": user_name, "vote_method": vote_method}) + + if data_manager.check_if_user_voted_on_question(user_name, question_id): + result = data_manager.check_if_user_voted_on_question(user_name, question_id) + voted = data_manager.delete_vote_on_question_from_votes_db(result, vote_method) + if voted: + data_manager.vote_question(vote_method, question_id) + + author = data_manager.get_author_by_question_id(question_id)["user_name"] + author_repu = data_manager.get_reputation(author) + new_repu = data_manager.annul_calc_reputation("question", vote_method, author_repu) + data_manager.update_user_reputation(author, new_repu) + return redirect(url_for("manage_questions", question_id=question_id)) + else: + author = data_manager.get_author_by_question_id(question_id)["user_name"] + author_repu = data_manager.get_reputation(author) + new_repu = data_manager.calculate_reputation("question", vote_method, author_repu) + data_manager.update_user_reputation(author, new_repu) + + data_manager.create_vote_on_question_in_votes_db(question_id, user) + data_manager.vote_question(vote_method, question_id) + return redirect(url_for("manage_questions", question_id=question_id)) + + +@app.route('/answer//') +def vote_answers(vote_method, answer_id): + answer = data_manager.get_answer_by_answer_id(answer_id) + question_id = answer["question_id"] + + user_name = session["user"] + user = data_manager.get_user_id_by_name(user_name) + user.update({"user_name": user_name, "vote_method": vote_method}) + + if data_manager.check_if_user_voted_on_answer(user_name, answer_id): + result = data_manager.check_if_user_voted_on_answer(user_name, answer_id) + voted = data_manager.delete_vote_on_answer_from_votes_db(result, vote_method) + if voted: + data_manager.vote_answer(vote_method, answer_id) + + author = data_manager.get_author_by_answer_id(answer_id)["user_name"] + author_repu = data_manager.get_reputation(author) + new_repu = data_manager.annul_calc_reputation("answer", vote_method, author_repu) + data_manager.update_user_reputation(author, new_repu) + return redirect(f'/questions/{question_id}') + else: + author = data_manager.get_author_by_answer_id(answer_id)["user_name"] + author_repu = data_manager.get_reputation(author) + new_repu = data_manager.calculate_reputation("answer", vote_method, author_repu) + data_manager.update_user_reputation(author, new_repu) + + data_manager.create_vote_on_answer_in_votes_db(answer_id, user) + data_manager.vote_answer(vote_method, answer_id) + return redirect(url_for("manage_questions", question_id=question_id)) + + +@app.route('/answer//delete') +def delete_answer(answer_id): + answer = data_manager.get_answer_by_answer_id(answer_id) + question_id = answer["question_id"] + data_manager.delete_answer(answer_id) + return redirect(url_for("manage_questions", question_id=question_id)) + + +@app.route('///delete') +def delete_comment(question_id, comment_id): + data_manager.delete_comment(comment_id) + return redirect(url_for("manage_questions", question_id=question_id)) + + +@app.route('/question//delete') +def delete_question(question_id): + data_manager.delete_question(question_id) + return redirect(url_for('index')) + + +@app.route('/question//new-answer', methods=['GET', 'POST']) +def add_new_answer_with_image(question_id): + if request.method == "POST": + if request.files: + image = request.files["image"] + if image.filename != "": + if data_manager.allowed_image(image.filename, app.config["ALLOWED_IMAGE_EXTENSIONS"]): + filename = secure_filename(image.filename) + image.save(os.path.join(app.config["IMAGE_UPLOADS"], filename)) + print("Image saved") + + else: + print("not allowed image") + return redirect(request.referrer) + + new_answer = dict(request.form) + if image.filename: + new_answer.update({"image": filename}) # ugly solution, a band-aid + else: + new_answer.update({"image": ""}) + + username = session['user'] + new_answer.update({"user_name": username}) + + data_manager.write_new_answer_to_database(question_id, new_answer) + return redirect(url_for("manage_questions", question_id=question_id)) + + +@app.route('/search') +def search_question(): + labels = ["submission_time", "view_number", "vote_number", "title", "message"] + search_phrase = request.args.get('q') + search_results = data_manager.search_question(search_phrase.lower()) + return render_template("list.html", + all_questions=search_results, + file_labels=labels) + + +@app.route('/user', methods=['GET', 'POST']) +def list_users(): + if request.method == 'GET': + all_users = data_manager.get_user_list() + return render_template('users.html', all_users=all_users) + + +@app.route('/user/') +def get_user_attributes(user_id): + user_questions = data_manager.get_user_questions(user_id) + user_answers = data_manager.get_user_answers(user_id) + user_comments = data_manager.get_user_comments(user_id) + return render_template('user_info.html', user_questions=user_questions, user_answers=user_answers, + user_comments=user_comments) + + +if __name__ == '__main__': + app.run( + host='0.0.0.0', + port=5000, + debug=True, + ) diff --git a/static/comment.css b/static/comment.css new file mode 100644 index 000000000..27f41548a --- /dev/null +++ b/static/comment.css @@ -0,0 +1,40 @@ + + +title { + color: #f7f7f7f7; + background-color: #327594; +} + +body { + background-color: #0B2329; + + } + +form { + padding-top: 2em; +} + +body form label { + color: #f7f7f7f7; + background-color: #327594; + margin: 2em 1em; + padding: 10px 10px; + border-radius: 3px; +} + +body form textarea { + margin: 2em 1em; +} + +body form button { + margin-left: 20em; +} + +.block { + background-color: #F7F7F7F7; + overflow: hidden; + margin: 30px; + padding: 20px 20px; + border-radius: 10px; + +} \ No newline at end of file diff --git a/static/edit-question-form.css b/static/edit-question-form.css new file mode 100644 index 000000000..f30854247 --- /dev/null +++ b/static/edit-question-form.css @@ -0,0 +1,117 @@ +*{ + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body{ + + height: 60vh; + display: flex; + justify-content: space-around; + align-items: center; + flex-direction: column; + background-color: #0B2329; +} + +h1{ + text-align: center; + padding-top: 50px; + padding-bottom: 50px; + color: white; + font-family: 'Permanent Marker', cursive; + font-size: 50px; +} + +button{ + margin-top: 20px; + padding: 10px 30px; + font-size: 15px; + border-radius: 3px; + cursor: pointer; + background-color: white; + font-weight: bold; + transition: 0.8s; + border: none; +} + +button:hover{ + background-color: black; + color: white; +} + +.text-container{ + width: 100%; + position: relative; + height: 50px; +} + +.text-container input{ + height: 100%; + padding-top: 20px; + width: 100%; + border: none; + background-color: #0B2329; + color: white; +} + +.text-container label{ + color: white; + font-weight: bold; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + outline: none; + border-bottom: #f7f7f7f7 2px solid; +} + +.text-container label::after{ + content: ""; + position: absolute; + height: 100%; + width: 100%; +} + +.title-span{ + position: absolute; + bottom: 5px; + left: 0; + transition: all 0.3s ease; +} + +.text-container input:focus + .label-title .title-span, +.text-container input:valid + .label-title .title-span{ + transform: translateY(-150%); + opacity: 0.3; +} + +.textarea-container{ + padding-top: 30px; +} + +.textarea-container label{ + color: white; + font-weight: bold; + display: block; + padding-bottom: 10px; +} + +.textarea-container textarea{ + border-radius: 3px; + box-shadow: 0 5px 25px 0 rgba(0,0,0, .25); +} + +.image-container{ + color: white; + font-weight: bold; + padding-bottom: 20px; +} + +.image-container label{ + display: block; + padding-top: 25px; + padding-bottom: 10px; +} \ No newline at end of file diff --git a/static/form.css b/static/form.css new file mode 100644 index 000000000..2651d3a7f --- /dev/null +++ b/static/form.css @@ -0,0 +1,171 @@ +*{ + margin: 0; + padding: 0; +} + +.form-container{ + height: 60vh; + display: flex; + justify-content: space-around; + align-items: center; + flex-direction: column; + background-color: #285369; + text-align: center; + + +} + +#go-back{ + text-align: center; + display: flex; + float: right; + color: #f7f7f7f7; + font-size: 25px; +} + +#go-back:hover{ + opacity: 0.8; +} + +#form-title{ + color: #f7f7f7; + font-family: 'Permanent Marker', cursive; + font-size: 30px; + text-align: center; + padding: 0; +} + +#add-question-title{ + color: #f7f7f7; + font-family: 'Permanent Marker', cursive; + font-size: 30px; + text-align: center; + padding: 0; + margin-right: 200px; +} + + +.text-container{ + width: 690px; + position: relative; + height: 50px; + padding-top: 30px; + margin-left: 130px; +} + +.text-container input{ + height: 100%; + padding-top: 10px; + width: 80%; + border: none; + background-color: #285369; + color: #f7f7f7; + display: block; +} + +.text-container label{ + font-weight: bold; + position: absolute; + bottom: 0; + left: 0; + width: 80%; + height: 100%; + pointer-events: none; + outline: none; + border-bottom: #f7f7f7 2px solid; +} + +.text-container label::after{ + content: ""; + position: absolute; + height: 100%; + width: 100%; +} + +.title-span{ + position: absolute; + bottom: 5px; + left: 0; + transition: all 0.3s ease; +} + +.text-container input:focus + .label-title .title-span, +.text-container input:valid + .label-title .title-span{ + transform: translateY(-150%); + opacity: 0.3; +} + +#message-label, +.textarea-container{ + padding-top: 15px; +} + +#message-label, +.textarea-container label{ + font-weight: bold; + display: block; + padding-bottom: 10px; +} + +#message, +.textarea-container textarea{ + border-radius: 3px; + box-shadow: 0 5px 25px 0 rgba(0,0,0, .25); + background-color: #285369; + color: #f7f7f7; + border: 2px solid #f7f7f7f7; + width: 550px; + height: 200px; +} + +.image-container{ + color: #f7f7f7; + font-weight: bold; + padding-bottom: 20px; +} + +.image-container label{ + display: block; + padding-top: 25px; + padding-bottom: 10px; +} + +#image{ + font-family: "Open Sans"; +} + +#post-form{ + background-color: #f7f7f7f7; + border: none; + padding: 10px 20px; + border-radius: 4px; + transition: 0.2s; + font-family: "Open Sans"; + font-weight: bold; + cursor: pointer; +} + +input[type="file"] { + display: none; +} + +.image-container { + display: inline-block; + border-radius: 4px; +} + +#image-btn{ + cursor: pointer; + background-color: #f7f7f7f7; + padding: 10px 20px; + color: #222; + font-family: "Open Sans"; + font-size: 15px; + border-radius: 4px; + transition: 0.3s; +} + +#image-btn:hover, +#post-form:hover{ + background-color: #DC5947; +} \ No newline at end of file diff --git a/static/image1.png b/static/image1.png new file mode 100644 index 000000000..828890a83 Binary files /dev/null and b/static/image1.png differ diff --git a/static/image2.jpg b/static/image2.jpg new file mode 100644 index 000000000..41baea740 Binary files /dev/null and b/static/image2.jpg differ diff --git a/static/images.css b/static/images.css new file mode 100644 index 000000000..f1247681b --- /dev/null +++ b/static/images.css @@ -0,0 +1,4 @@ +.imgthumb { + width: 200px; + height: auto; +} \ No newline at end of file diff --git a/static/images/0_RXlZHWuivkapvRYe.jpeg b/static/images/0_RXlZHWuivkapvRYe.jpeg new file mode 100644 index 000000000..c865898de Binary files /dev/null and b/static/images/0_RXlZHWuivkapvRYe.jpeg differ diff --git a/static/images/26012951.jpg b/static/images/26012951.jpg new file mode 100644 index 000000000..0c8ed3081 Binary files /dev/null and b/static/images/26012951.jpg differ diff --git a/static/images/40622080013_a02650a46c_o (1).jpg b/static/images/40622080013_a02650a46c_o (1).jpg new file mode 100644 index 000000000..e2c2211ed Binary files /dev/null and b/static/images/40622080013_a02650a46c_o (1).jpg differ diff --git a/static/images/40622080013_a02650a46c_o.jpg b/static/images/40622080013_a02650a46c_o.jpg new file mode 100644 index 000000000..2ab7ed6a6 Binary files /dev/null and b/static/images/40622080013_a02650a46c_o.jpg differ diff --git a/static/images/46864167314_ebb417ae70_o.jpg b/static/images/46864167314_ebb417ae70_o.jpg new file mode 100644 index 000000000..a7ae8a69e Binary files /dev/null and b/static/images/46864167314_ebb417ae70_o.jpg differ diff --git a/static/images/Ybq0M0w.jpg b/static/images/Ybq0M0w.jpg new file mode 100644 index 000000000..38563b38c Binary files /dev/null and b/static/images/Ybq0M0w.jpg differ diff --git a/static/images/arrow.png b/static/images/arrow.png new file mode 100644 index 000000000..cff3faca1 Binary files /dev/null and b/static/images/arrow.png differ diff --git a/static/images/arrow2.png b/static/images/arrow2.png new file mode 100644 index 000000000..09d77024a Binary files /dev/null and b/static/images/arrow2.png differ diff --git a/static/images/ask_anything.jpg b/static/images/ask_anything.jpg new file mode 100644 index 000000000..767391fa7 Binary files /dev/null and b/static/images/ask_anything.jpg differ diff --git a/static/images/basic.png b/static/images/basic.png new file mode 100644 index 000000000..daee7e987 Binary files /dev/null and b/static/images/basic.png differ diff --git a/static/images/downvote.png b/static/images/downvote.png new file mode 100644 index 000000000..4b8a4d542 Binary files /dev/null and b/static/images/downvote.png differ diff --git a/static/images/image1.png b/static/images/image1.png new file mode 100644 index 000000000..828890a83 Binary files /dev/null and b/static/images/image1.png differ diff --git a/static/images/image2.jpg b/static/images/image2.jpg new file mode 100644 index 000000000..41baea740 Binary files /dev/null and b/static/images/image2.jpg differ diff --git a/static/images/indexheader.jpg b/static/images/indexheader.jpg new file mode 100644 index 000000000..53cfc6621 Binary files /dev/null and b/static/images/indexheader.jpg differ diff --git a/static/images/jesus-maslow-and-a-squirrel.jpg b/static/images/jesus-maslow-and-a-squirrel.jpg new file mode 100644 index 000000000..8331cfcad Binary files /dev/null and b/static/images/jesus-maslow-and-a-squirrel.jpg differ diff --git a/static/images/mojave-day.jpg b/static/images/mojave-day.jpg new file mode 100644 index 000000000..d8d84278a Binary files /dev/null and b/static/images/mojave-day.jpg differ diff --git a/static/images/trees.jpg b/static/images/trees.jpg new file mode 100644 index 000000000..329d30b5f Binary files /dev/null and b/static/images/trees.jpg differ diff --git a/static/images/upvote.png b/static/images/upvote.png new file mode 100644 index 000000000..e96eafad4 Binary files /dev/null and b/static/images/upvote.png differ diff --git a/static/images/xmas_november.jpg b/static/images/xmas_november.jpg new file mode 100644 index 000000000..c2d79d43f Binary files /dev/null and b/static/images/xmas_november.jpg differ diff --git a/static/index.css b/static/index.css new file mode 100644 index 000000000..d1f0328ba --- /dev/null +++ b/static/index.css @@ -0,0 +1,210 @@ +*{ + margin: 0; + font-family: sans-serif; +} +body{ + width: 100%; + background-color: #0B2329; +} + +p{ + text-align: center; + margin-top: 10%; + font-size: 30px; + font-weight: bold; + color: #f7f7f7f7; +} + +.fa-times{ + color: #f7f7f7f7; + margin: 0 auto; + float: right; +} + +.last-nav-link{ + padding: 0 30px; +} + +.sorting-container{ + display: flex; + justify-content: center; +} + +.btn-group button{ + border: none; + background: none; + cursor: pointer; + display: block; +} + +.sorting-container label{ + background: none; + border: none; + color: #f7f7f7f7; + text-decoration: none; + font-family: 'Permanent Marker', cursive; + font-size: 20px; + font-weight: bold; + padding-top: 5px; +} + +.fa-sort-down{ + margin-top: -8px; +} + +.fa-sort-up{ + margin-bottom: -10px; + box-sizing: content-box; + background-clip: content-box; +} + +.fa-sort-up, .fa-sort-down{ + color: #f7f7f7f7; + margin-left: 20px; + font-size: 30px; + background-clip: content-box; + overflow: hidden; + transition: 0.2s; +} + +.fa-sort-up:hover, .fa-sort-down:hover{ + color: #DC5947; +} + +.img-container{ + background-image: url(images/indexheader.jpg); + height: 200px; + background-position-y: top; + background-position-x: left; + background-repeat: no-repeat; + background-size: cover; + position: relative; + padding-left: 15%; + padding-top: 2%; + +} +.inner-container a{ + text-decoration: none; + color: #f7f7f7f7; + font-family: 'Shadows Into Light', cursive; + font-size: 100px; + display: inline-block; + font-weight: bolder; + +} + +.inner-container a:hover{ + color: #DC5947; + opacity: 90%; +} + + +.navigator-container{ + padding: 10px 0; + border-bottom: 3px solid silver; + text-align: right; +} + +.navigator-container *{ + display: inline; + padding: 0; + margin: 0; +} + +.search-box { + display: inline; + position: absolute; + background-color: #f7f7f7f7; + height: 40px; + border-radius: 40px; + padding: 0; + left: 2%; +} + +.search-box:hover > .search-txt { + width: 240px; + padding: 0 6px; +} + +.search-box:hover > .search-btn { + background: #f7f7f7f7; + +} + +.search-btn { + float: right; + width: 40px; + height: 40px; + border-radius: 50%; + background: none; + display: flex; + justify-content: center; + align-items: center; + transition: 0.4s; + color: #f7f7f7f7; + cursor: pointer; + border:none; +} + +.search-btn > i { + font-size: 30px; +} + +.search-txt { + border: none; + background: none; + outline: none; + float: left; + padding: 0; + font-size: 16px; + transition: 0.4s; + line-height: 40px; + width: 0; +} + +.fa-search{ + color: #222; + padding-right: 3px; +} + +.search-txt:focus{ + width: 240px; + padding: 0 6px; +} + +.navigator-container nav a{ + color: #f7f7f7f7; + text-decoration: none; + font-weight: bold; + font-family: 'Permanent Marker', cursive; + font-size: 25px; + background-clip: content-box; + display: inline; + margin: 0 40px; + +} + +.navigator-container nav a:hover{ + color: #DC5947 +} + +.navigator-container form label{ + color: #f7f7f7f7; +} + +.navigator-container nav a:hover{ + background-clip: content-box; + opacity: 0.7; +} + +.navigator-float { + float: right; + display: flex; + margin-left: 20px; + margin-right: 50px; + padding: 0; +} + +#user-nav { + alignment: left; +} diff --git a/static/login-register.css b/static/login-register.css new file mode 100644 index 000000000..55df2da53 --- /dev/null +++ b/static/login-register.css @@ -0,0 +1,88 @@ +*{ + margin: 0; + padding: 0; + font-family: sans-serif; +} + +body{ + background-image: url(images/indexheader.jpg); +} + +.form-container h1{ + margin-bottom: 50px; + color: #f7f7f7f7; + text-transform: uppercase; + font-weight: 500; +} + +.error{ + color: #9C1A1C; + font-size: 20px; + text-align: center; +} + +.form-container{ + background: #0B2329; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: block; + padding: 60px; + text-align: center; + width: 300px; + height: 500px; + border-radius: 10px; + box-shadow: 1px 2px 10px #222; + +} + +.form-container input[type="text"], .form-container input[type="password"]{ + background: none; + margin: 25px auto; + display: block; + text-align: center; + padding: 14px 20px; + border: 2px solid #3498db; + width: 200px; + outline: none; + color: #f7f7f7f7; + border-radius: 24px; + transition: 0.25s; +} + +.form-container input[type="text"]:focus, .form-container input[type="password"]:focus{ + width: 250px; + border-color: #f7f7f7f7; +} + +.form-container button{ + background: none; + margin: 25px auto; + display: block; + text-align: center; + padding: 14px 30px; + border: 2px solid #f7f7f7f7; + outline: none; + color: #f7f7f7f7; + border-radius: 24px; + transition: 0.25s; + cursor: pointer; + font-weight: bold; + +} + +.form-container button:hover{ + background: #f7f7f7; + color: #222; +} + +.form-container p{ + color: #f7f7f7f7; + margin-bottom: 5px; +} + +.form-container a{ + color: #f7f7f7f7; + font-weight: bold; +} diff --git a/static/manage-question.css b/static/manage-question.css new file mode 100644 index 000000000..749b0e9c9 --- /dev/null +++ b/static/manage-question.css @@ -0,0 +1,185 @@ +html { + max-width: 1024px; + margin: 0 auto; +} + +*{ + margin: 0; + padding: 0; +} + +body{ + margin-top: 50px; + margin-bottom: 50px; + background: #285369; + font-family: sans-serif; +} + +ul {list-style-type: none;} +h1, h3, #no_answer {color: #f7f7f7;} +a {text-decoration: none; color: #f7f7f7;} +#bignum {font-size: 2em; color: #0B2329;} + +.title {display: flex; min-height: 200px; align-items: baseline; text-align: center; flex-direction: column;} + +.question-title{ + margin: 40px auto; +} + +h1{ + font-size: 45px; +} + +h3{ + margin-top: 40px; + font-size: 25px; +} + +#no_answer{ + text-align: center; + font-weight: bold; + font-size: 30px; + margin: 30px 0; +} + +.block { + border-radius: 10px; + background-color: #F7F7F7F7; + box-shadow: 0 5px 25px 0 rgba(0,0,0, .25); + /* overflow: hidden; */ + display:flex; + flex-direction:row; +} +.column { + display: flex; + flex-direction: column; + max-width: 25vh; align-items: center; + padding: 10px; + border-radius: 10px; +} +.content { + display: flex; + flex: 1 1 auto; + flex-direction: column; + border-left: 4px solid #285369; +} +.msg { + background: #f7f7f7; flex: 1 1 auto; + border-radius: 0 10px 0 0; + padding: 10px; + white-space: pre-line; +} +.navbar { + display: flex; + justify-content: right; + align-items: center; + background-color: #0B2329; + height: min-content; + border-radius: 0 0 10px 0; + color: white; + padding: 2px 0; +} + +#upvote-btn{ + color: #222; + font-size: 40px; + margin-bottom: -10px; + transition: 0.2s; +} + +#downvote-btn{ + color: #222; + font-size: 40px; + margin-top: -10px; + transition: 0.2s; +} + +#upvote-btn:hover, +#downvote-btn:hover{ + color: #DC5947; +} + +.navbar > * {margin-right: 10px;} + +.comments {padding-left: 25vh; margin-top: 10px;} +.comments > * {margin: 10px;} +.answerblock {margin: 20px;} +.comments > .content > .msg{ + background: #dededede; +} + +/**.message:nth-of-type(odd) { + background-color: #285369; + border: 2.5px solid lightgrey; + color: #f7f7f7f7; + width: 90%; + margin: 20px 20px 20px 100px; + border-radius: 10px; +} **/ + + + +.imgblock { + max-height: 300px; + max-width: 300px; +} + +.imgblock { + background-color: white; +} + +.imgblock:hover { + position: absolute; + transform: scale(3) translate(+50%, +40%); + transition: transform .25s ease; + z-index: 1; +} +.loginrequired { + visibility: hidden; + display: flex; + justify-content: center; +} +.loginrequired > a { + margin-left: 10px; +} + +#comment-btn, +#answer-btn{ + margin-left: 60px; + margin-top: 30px; + border: none; + background: #f7f7f7; + border-radius: 4px; + padding: 10px 30px; + color: #222; + font-weight: bold; + transition: 0.2s; + cursor: pointer; +} + +#back-to-questions{ + font-size: 20px; + font-weight: bold; + text-decoration: underline; +} + +#comment-btn:hover, +#answer-btn:hover{ + background: #DC5947; +} + +.accepted { + border: 4px solid darkgoldenrod; +} +.accepted .content { + border-left: 4px solid darkgoldenrod; +} +.accepted .content .navbar { + background: darkgoldenrod; +} +.admin { + display:flex; + visibility:hidden;} +.admin > * { + margin-left: 10px; +} \ No newline at end of file diff --git a/static/modal.css b/static/modal.css new file mode 100644 index 000000000..54bd75a99 --- /dev/null +++ b/static/modal.css @@ -0,0 +1,38 @@ +.modal-container{ + position: fixed; + background-color: #285369; + left: 50%; + transform: translate(-50%, -300%); + -ms-transform: translate(-50%, -300%); + -webkit-transform: translate(-50%, -300%); + padding: 20px; + border-radius: 5px; + width: 70%; + max-width: 700px; + transition: 200ms ease-out; +} + + +.modal:before{ + content: ""; + position: fixed; + background-color: rgb(0,0,0,.8); + display: none; + top: 0; + left: 0; + height: 100%; + width: 100%; +} + +.modal:target .modal-container{ + z-index: 1; + position: fixed; + top: 20%; + transform: translate(-50%, 0); + -ms-transform: translate(-50%, 0); + -webkit-transform: translate(-50%, 0); +} + +.modal:target:before{ + display: block; +} \ No newline at end of file diff --git a/static/questions-table.css b/static/questions-table.css new file mode 100644 index 000000000..f88bc3fa8 --- /dev/null +++ b/static/questions-table.css @@ -0,0 +1,50 @@ +.table-container{ + border-collapse: collapse; + text-align: center; + color: #f7f7f7f7; + overflow: hidden; + width: 100%; +} + +.table-container thead th{ + background-color: #0B2329; + padding: 5px 30px; + font-family: 'Permanent Marker', cursive; + font-size: 20px; +} + +.table-container tbody tr:nth-of-type(even){ + background-color: #1C3B4A; + font-size: large; + font-family: 'Open Sans', sans-serif; +} + +.table-container tbody tr:nth-of-type(odd){ + background-color: #285369; + font-size: large; + font-family: 'Open Sans', sans-serif; +} + +.table-container tbody tr td pre{ + font-size: large; + font-family: 'Open Sans', sans-serif; +} + +.table-container tbody tr:hover{ + background-color: #327594; +} + +.table-container tbody tr td{ + padding: 21px 20px; +} + +.table-container a{ + font-size: large; + color: #f7f7f7f7; + text-decoration: none; + font-weight: bolder; +} + +.table-container a:hover{ + color: #222; +} \ No newline at end of file diff --git a/static/xmas_november.jpg b/static/xmas_november.jpg new file mode 100644 index 000000000..c2d79d43f Binary files /dev/null and b/static/xmas_november.jpg differ diff --git a/templates/add_question_or_answer.html b/templates/add_question_or_answer.html new file mode 100644 index 000000000..0e55e8cb5 --- /dev/null +++ b/templates/add_question_or_answer.html @@ -0,0 +1,38 @@ + + + + + + + + +
+ {% if question_id %} +

Edit question

+ {% else %} +

Add new question

+ {% endif %} +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + \ No newline at end of file diff --git a/templates/comment.html b/templates/comment.html new file mode 100644 index 000000000..e34740c63 --- /dev/null +++ b/templates/comment.html @@ -0,0 +1,29 @@ + + + + + diff --git a/templates/edit-answer.html b/templates/edit-answer.html new file mode 100644 index 000000000..64d20cb98 --- /dev/null +++ b/templates/edit-answer.html @@ -0,0 +1,29 @@ + + + + + {{ 'Edit answer' if answer_id else 'Add new answer' }} + + + + +
+

{{ 'Edit answer' if answer_id else 'Add new answer' }}

+
+
+
+
+
+ + +
+
+ + +
+ +
+
+
+ + \ No newline at end of file diff --git a/templates/edit-question-or-answer.html b/templates/edit-question-or-answer.html new file mode 100644 index 000000000..53c3dfc0e --- /dev/null +++ b/templates/edit-question-or-answer.html @@ -0,0 +1,28 @@ + + + + + {{ page_title }} + + +

{{ header_title }}

+
+

+
+ +

+

+
+ +

+

+
+ + +

+

+ +

+
+ + \ No newline at end of file diff --git a/templates/form.html b/templates/form.html new file mode 100644 index 000000000..882a74ca4 --- /dev/null +++ b/templates/form.html @@ -0,0 +1,37 @@ + + + + + + {{ page_title }} + + +

{{ header_title }}

+
+

+
+ +

+

+
+ +

+ +

+ +
+ + +

+

+ +

+
+
+
+ + + +
+ + \ No newline at end of file diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 000000000..974d07669 --- /dev/null +++ b/templates/header.html @@ -0,0 +1,25 @@ + + + + + Welcome to AskMate! + + + + + + + {% block head %} {% endblock %} + + + +
+
+
+ AskMate +
+
+
+ {% block body %} {% endblock %} + + \ No newline at end of file diff --git a/templates/list.html b/templates/list.html new file mode 100644 index 000000000..228edd2c9 --- /dev/null +++ b/templates/list.html @@ -0,0 +1,99 @@ +{% extends 'master_header.html' %} + +{% block main %} + + {% if all_questions %} + + + + + + + + + + + + + {% for question in all_questions %} + + + + + + + + + {% endfor %} + {% else %} +

No questions found

+ {% endif %} + +
+
+ + +
+ + +
+
+
+
+ + +
+ + +
+
+
+
+ + +
+ + +
+
+
+
+ + +
+ + +
+
+
+
+ + +
+ + +
+
+
Image
{{ question['submission_time'] }}{{ question['view_number'] }}{{ question['vote_number'] }}{{ question['title'] }}{{ (question['message'][:200] + '...') if question['message'] | length > 200 else question['message'] }}No image
+ +{% endblock %} \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 000000000..46a667e1a --- /dev/null +++ b/templates/login.html @@ -0,0 +1,29 @@ + + + + + Login + + + +
+

Login

+ {% if error %} +

Invalid username or password!

+ {% endif %} +
+ + + + + +
+

Don't have an AskMate account?

+ Sign Up +


+

Don't want to register?

+ Continue without Logging In +
+
+ + \ No newline at end of file diff --git a/templates/master_header.html b/templates/master_header.html new file mode 100644 index 000000000..683d8f9e3 --- /dev/null +++ b/templates/master_header.html @@ -0,0 +1,61 @@ + + + + + + Welcome to AskMate! + + + + + + + + + + +
+
+
+ AskMate +
+
+
+ + +
+ {% block main %} {% endblock %} +
+ + diff --git a/templates/question-child.html b/templates/question-child.html new file mode 100644 index 000000000..e75e92959 --- /dev/null +++ b/templates/question-child.html @@ -0,0 +1,122 @@ +{% extends "question.html" %} + + +{% block answer %} +
+ +
+
+ {% if answer['vote_method'] == 1 %} + + {% else %} + + {% endif %} +

{{ answer["vote_number"] }}

+ {% if answer['vote_method'] == -1 %} + + {% else %} + + {% endif %} +
+
+ +
+
+ {{ answer["message"] }} + {% if answer['image'] %} +
+ {% endif %} +
+ +
+
+ +
+ {% for comment in comments %} + {% if comment["answer_id"] == answer["id"] %} +
+
{{ comment["message"] }}
+ +
+ {% endif %} + {% endfor %} +
+ +
+ +{% endblock %} + +{% block givinganswer %} +
+ +
+ + + + + + + +
+ +
+ +{% endblock %} diff --git a/templates/question.html b/templates/question.html new file mode 100644 index 000000000..272edf40f --- /dev/null +++ b/templates/question.html @@ -0,0 +1,190 @@ + + + + + + + + + {{ question['title'] }} + {% block head %} {% endblock %} + + +
+ No image +

{{ question['title'] }}

+
+ +
+
+ {% if question_vote['vote_method'] == 1 %} + + {% else %} + + {% endif %} +

{{ question['vote_number'] }}

+ {% if question_vote['vote_method'] == -1 %} + + {% else %} + + {% endif %} +

Viewed: {{ question['view_number'] }}

+ +
+
+
{{ question['message'] | safe }}
+ +
+
+ +
+ + {% for comment in comments %} + {% if comment["question_id"] == question_id and comment["answer_id"] == None %} +
+
+ {{ comment["message"] }} +
+ +
+ {% endif %} + {% endfor %} + +
+ + + {% if addinganswer %} + {% block givinganswer %}{% endblock %} + {% else %} +
+ + +
+ +
+
+ {% endif %} + + {% include 'comment.html' %} + +

+

Answers:

+ {% if answers %} + {% for answer in answers %} + {% block answer scoped %} {% endblock%} + {% endfor %} + {% else %} +

There are no answers to this question yet.

+ {% endif %} + + + + + + + diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 000000000..bd3d9d2b1 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,25 @@ + + + + + Sign up + + + +
+

Sign up

+

{{ error }}

+
+ + + + + + + +

Already have an AskMate account?

+ Sign In +
+
+ + \ No newline at end of file diff --git a/templates/user_info.html b/templates/user_info.html new file mode 100644 index 000000000..ab24de095 --- /dev/null +++ b/templates/user_info.html @@ -0,0 +1,117 @@ +{% extends 'master_header.html' %} + +{% block main %} + +
+ {% if user_questions %} + + + + + + + + + + + + + {% for question in user_questions %} + + + + + + + + + + {% endfor %} + {% else %} +

No questions found

+ {% endif %} + +
SubmittedViewedVotedTitleMessage
{{ question['submission_time'] }}{{ question['view_number'] }}{{ question['vote_number'] }}{{ question['title'] }}{{ (question['message'][:200] + '...') if question['message'] | length > 200 else question['message'] }}No image
+
+ +
+ {% if user_answers %} + + + + + + + + + + + + + {% for question in user_answers %} + + + + + + + + + + {% endfor %} + {% else %} +

No answers found

+ {% endif %} + +
SubmittedViewedVotedTitleMessage
{{ question['submission_time'] }}{{ question['view_number'] }}{{ question['vote_number'] }}{{ question['title'] }}{{ (question['message'][:200] + '...') if question['message'] | length > 200 else question['message'] }}No image
+ +
+ {% if user_comments %} + + + + + + + + + + + + + {% for question in user_comments %} + + + + + + + + + + {% endfor %} + {% else %} +

No comments found

+ {% endif %} + +
SubmittedViewedVotedTitleMessage
{{ question['submission_time'] }}{{ question['view_number'] }}{{ question['vote_number'] }}{{ question['title'] }}{{ (question['message'][:200] + '...') if question['message'] | length > 200 else question['message'] }}No image
+
+ {% endblock %} \ No newline at end of file diff --git a/templates/users.html b/templates/users.html new file mode 100644 index 000000000..ace83f675 --- /dev/null +++ b/templates/users.html @@ -0,0 +1,37 @@ +{% extends 'master_header.html' %} + +{% block main %} + + {% if all_users %} + + + + + + + + + + + + + + {% for user in all_users %} + + + + + + + + + + {% endfor %} + {% else %} +

No users found

+ {% endif %} + + +
ReputationUsernameMember sinceQuestions askedAnswers givenComments given
{{ user['reputation'] }}{{ user['name'] }}{{ user['member_since'] }}{{ user['question'] }}{{ user['answer'] }}{{ user['comment'] }}
+ +{% endblock %} \ No newline at end of file diff --git a/util.py b/util.py new file mode 100644 index 000000000..ec92f6dca --- /dev/null +++ b/util.py @@ -0,0 +1,15 @@ +import bcrypt + + +def hash_password(plain_text_password): + # By using bcrypt, the salt is saved into the hash itself + hashed_bytes = bcrypt.hashpw(plain_text_password.encode('utf-8'), bcrypt.gensalt()) + return hashed_bytes.decode('utf-8') + + +def verify_password(plain_text_password, hashed_password): + if hashed_password is None: + return False + hashed_bytes_password = hashed_password.encode('utf-8') + return bcrypt.checkpw(plain_text_password.encode('utf-8'), hashed_bytes_password) +