diff --git a/frontend/.env b/frontend/.env index f752464ca612fc26e9141015d53b0bcca01a332e..10e8a83f3e013f8471deb95602057993ec773606 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1 @@ -CONFIGURATION_SETUP="config.ProductionConfig" \ No newline at end of file +CONFIGURATION_SETUP="config.DevelopmentConfig" diff --git a/frontend/application/__init__.py b/frontend/application/__init__.py index a84f862921cb07236a382824e4c227470858314f..62853509280db22038f5339c1589d67989f900ad 100644 --- a/frontend/application/__init__.py +++ b/frontend/application/__init__.py @@ -6,6 +6,7 @@ from flask_login import LoginManager from flask_ckeditor import CKEditor login_manager = LoginManager() +UPLOAD_FOLDER = os.path.join('static', 'images') app = None def create_app(): @@ -16,6 +17,7 @@ def create_app(): return app app = Flask(__name__) + app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER # Load environment variables load_dotenv() diff --git a/frontend/application/frontend/api/PostClient.py b/frontend/application/frontend/api/PostClient.py index 6d95e67442700426907399045be107b630231763..1a7ec0d25ff1f76260b2a932174a385da7f08cad 100644 --- a/frontend/application/frontend/api/PostClient.py +++ b/frontend/application/frontend/api/PostClient.py @@ -44,7 +44,7 @@ class PostClient: def create_post(form): payload = {'title':form.title.data, 'category':form.category.data, - 'content':form.content.data, 'user_api':session['user_api_key']} + 'content':form.content.data, 'image':form.image_url, 'user_api':session['user_api_key']} url = 'http://' + PostClient.post_service + '/api/new-post' response = requests.request(method="POST", url=url, data=payload) diff --git a/frontend/application/frontend/api/UserClient.py b/frontend/application/frontend/api/UserClient.py index 09bfd18864b45c9f966fd2620ecc4588ffb27f52..b0cf86c77422e6a5c53e489d197742a4fc9be394 100644 --- a/frontend/application/frontend/api/UserClient.py +++ b/frontend/application/frontend/api/UserClient.py @@ -18,6 +18,15 @@ class UserClient: return False user = response.json() return user + + @staticmethod + def get_otheruser(user_id): + url = 'http://' + UserClient.user_service + '/api/user/' + str(user_id) + response = requests.request("GET", url=url) + if response.status_code == 404: + return None + user = response.json() + return user @staticmethod def does_exist(email): @@ -35,7 +44,8 @@ class UserClient: 'last_name': form.last_name.data, 'phone_number': form.phone_number.data, 'uni_number': form.uni_number.data, - 'user_role': form.user_role.data + 'user_role': form.user_role.data, + 'image_url': form.image_url } url = 'http://' + UserClient.user_service +'/api/user/create' response = requests.request("POST", url=url, data=payload) @@ -44,6 +54,24 @@ class UserClient: user = response.json() return user + @staticmethod + def post_user_update(form): + user = False + payload = { + 'user_id':session['user']['id'], + 'image_url': form.image_url, + 'first_name': form.first_name.data, + 'last_name': form.last_name.data, + 'phone_number': form.phone_number.data, + 'user_role': form.user_role.data, + } + url = 'http://' + UserClient.user_service +'/api/user/update' + response = requests.request("POST", url=url, data=payload) + + if response: + user = response.json() + return user + @staticmethod def post_login(form): user = False diff --git a/frontend/application/frontend/forms.py b/frontend/application/frontend/forms.py index b59b5b7773b7f6b5f545b44f53ec078888917798..83f6f4e3c41d79e333e68724a5384bfdac45447f 100644 --- a/frontend/application/frontend/forms.py +++ b/frontend/application/frontend/forms.py @@ -1,11 +1,12 @@ # application/frontend/forms.py from flask_wtf import FlaskForm -from flask_wtf.file import FileField, FileAllowed +from flask_wtf.file import FileField, FileAllowed, FileRequired from flask_login import current_user from wtforms import (StringField, PasswordField, SubmitField, SelectField) +from flask_wtf.file import FileField, FileAllowed from wtforms.validators import (InputRequired, Email, EqualTo, - ValidationError, Length) + ValidationError, Length, Optional) from flask_ckeditor import CKEditorField class CreatePostForm(FlaskForm): @@ -24,18 +25,24 @@ class CreatePostForm(FlaskForm): ]) content = CKEditorField('Content', validators=[ InputRequired(), Length(min=20)]) - submit = SubmitField('CREATE POST') + image = FileField('Upload an image') + submit = SubmitField('CREATE POST', validators=[ + FileAllowed(['jpg', 'jpeg', 'png'])]) class CommentForm(FlaskForm): '''Comment post form''' content = CKEditorField('Comment', validators=[InputRequired()]) submit = SubmitField('SUBMIT') +class SearchForm(FlaskForm): + keywords = StringField('Type keywords', validators=[InputRequired()]) + submit = SubmitField('Search') + class RegistrationForm(FlaskForm): first_name = StringField('First name', validators=[InputRequired()]) last_name = StringField('Last name', validators=[InputRequired()]) email = StringField('Email address', validators=[InputRequired(), Email()]) - phone_number = StringField('Phone Number', validators=[InputRequired()]) + phone_number = StringField('Phone Number') uni_number = StringField('University Number') user_role = SelectField('Role', choices=[ ('staff', 'Staff'), @@ -52,4 +59,15 @@ class LoginForm(FlaskForm): '''Login form for registered users''' email = StringField('Email', validators=[InputRequired(), Email()]) password = PasswordField('Password', validators=[InputRequired()]) - submit = SubmitField('LOGIN') \ No newline at end of file + submit = SubmitField('LOGIN') + +class AccountUpdateForm(FlaskForm): + image = FileField('Upload an image') + first_name = StringField('First name') + last_name = StringField('Last name') + phone_number = StringField('Phone Number') + user_role = SelectField('Role', choices=[ + ('graduate', 'Graduate') + ],validators = [Optional()]) + submit = SubmitField('Update',validators=[ + FileAllowed(['jpg', 'jpeg', 'png'])]) \ No newline at end of file diff --git a/frontend/application/frontend/views.py b/frontend/application/frontend/views.py index e2da31f150774a16ee4bddae0cb76f1d3cb83c19..b7466bd1977ee2998621652cbb22c1676459f99e 100644 --- a/frontend/application/frontend/views.py +++ b/frontend/application/frontend/views.py @@ -8,19 +8,25 @@ from .api.UserClient import UserClient from .api.PostClient import PostClient from password_strength import PasswordPolicy from password_strength import PasswordStats -from flask import render_template, session, redirect, url_for, flash, request, abort +from flask import render_template, session, redirect, url_for, flash, request, abort, current_app from flask_login import login_user, logout_user, login_required, current_user +import secrets +from PIL import Image +from flask import send_file +from os import environ, path @login_manager.user_loader def load_user(user_id): return None + policy = PasswordPolicy.from_names( length=8, uppercase=1, numbers=1, strength=0.4) + @frontend_blueprint.route('/signup', methods=['GET', 'POST']) def sign_up(): if len(session)>=4: @@ -53,16 +59,20 @@ def sign_up(): # if phone_number, uni_number else: # Attempt to create new user + form.image_url = 'defaultpp.png' user = UserClient.post_user_create(form) if user: flash('Thanks for registering, login to access home page', 'success') return redirect(url_for('frontend.login_route')) - + else: + flash('Error found', 'error') + return render_template('auth/signup.html', form=form) else: flash('Errors found', 'error') return render_template('auth/signup.html', form=form) + @frontend_blueprint.route('/login', methods=['GET', 'POST']) def login_route(): if len(session)>=4: @@ -85,23 +95,28 @@ def login_route(): return render_template('auth/login.html', form=form) + @frontend_blueprint.route('/', methods=['GET']) def get_posts(): if len(session)<4: return redirect(url_for('frontend.login_route')) categories = [] + image_urls = [] posts = PostClient.get_posts() - user = UserClient.get_user() - for post in posts: - categories.append(post['category']) - categories = sorted(set(categories)) if posts == 404: - return render_template('forum/index.html') + return render_template('forum/index.html') + + for post in posts: + categories.append(post['category']) + user = UserClient.get_otheruser(post['user_id']) + image_urls.append(user['image_url']) + categories = sorted(set(categories)) return render_template('forum/index.html', - posts=posts, user=user, categories=categories) + data=zip(posts,image_urls), categories=categories) + @frontend_blueprint.route('/category/<category>', methods=['GET']) def categories(category = None): @@ -109,26 +124,81 @@ def categories(category = None): return redirect(url_for('frontend.login_route')) category_posts = [] - posts = PostClient.get_posts() - user = UserClient.get_user() + image_urls = [] + posts = PostClient.get_posts() + for post in posts: if post['category'] == category: category_posts.append(post) + user = UserClient.get_otheruser(post['user_id']) + image_urls.append(user['image_url']) if category_posts == []: return redirect(url_for('frontend.get_posts')) return render_template('forum/index.html', - posts=category_posts, user=user, category=category) + data=zip(category_posts,image_urls), category=category) + + +def save_post_image(image_file): + random_hex = secrets.token_hex(8) + _, file_ext = path.splitext(image_file.filename) + picture_filename = random_hex + file_ext + picture_path = path.join(current_app.root_path, + 'static/images', picture_filename) + output_size = (200, 200) + pic = Image.open(image_file) + pic.thumbnail(output_size) + pic.save(picture_path) + + return picture_filename + + +def save_user_image(image_file): + random_hex = secrets.token_hex(8) + _, file_ext = path.splitext(image_file.filename) + picture_filename = random_hex + file_ext + picture_path = path.join(current_app.root_path, + 'static/images/users', picture_filename) + output_size = (200, 200) + pic = Image.open(image_file) + pic.thumbnail(output_size) + pic.save(picture_path) + + return picture_filename + +@frontend_blueprint.route('/category/images/users/<image_id>' , methods=['GET']) +@frontend_blueprint.route('/post/images/users/<image_id>' , methods=['GET']) +@frontend_blueprint.route('/user/images/users/<image_id>' , methods=['GET']) +@frontend_blueprint.route('/images/users/<image_id>' , methods=['GET']) +def get_user_image(image_id): + picture_path = path.join(current_app.root_path, + 'static/images/users', image_id) + print(picture_path) + return send_file(picture_path, mimetype='image/gif') + + +@frontend_blueprint.route('/post/images/<image_id>' , methods=['GET']) +def get_post_image(image_id): + picture_path = path.join(current_app.root_path, + 'static/images', image_id) + print(picture_path) + return send_file(picture_path, mimetype='image/gif') + @frontend_blueprint.route('/post/new', methods=['GET','POST']) def create_post(): if len(session)<4: return redirect(url_for('frontend.login_route')) - user = UserClient.get_user() - form = forms.CreatePostForm(request.form) + form = forms.CreatePostForm() if request.method == "POST": if form.validate_on_submit(): + if form.image.data: + post_image = save_post_image(form.image.data) + form.image_url = post_image + else: + form.image_url = '' + post = PostClient.create_post(form) if post: flash('Post created successfully', 'success') @@ -141,21 +211,28 @@ def create_post(): 'form': form, 'post': None } - return render_template('forum/create_post.html',user=user, **content) + return render_template('forum/create_post.html', **content) @frontend_blueprint.route('/post/<int:post_id>', methods=['GET','POST']) def display_post(post_id): if len(session)<4: return redirect(url_for('frontend.login_route')) - + user_id = session['user']['id'] response = PostClient.get_post(post_id) if response == 404: abort(404) post = response[0] + post_owner = UserClient.get_otheruser(post['user_id']) + comments = response[1:] + comment_owners_images = [] + for comment in comments: + comment_owner = UserClient.get_otheruser(comment['user_id']) + comment_owners_images.append(comment_owner['image_url']) + form = forms.CommentForm(request.form) if request.method == "POST": @@ -170,8 +247,10 @@ def display_post(post_id): content = { 'post': post, + 'owner_image': post_owner['image_url'], 'form': form, - 'comments': comments + 'current_user_id':user_id, + 'comments': zip(comments, comment_owners_images) } return render_template('forum/post.html', **content) @@ -188,10 +267,9 @@ def delete_comment(post_id,comment_id): abort(404) comment_user_id = comment[0]['user_id'] - user_response = UserClient.get_user() - user = user_response['result'] + user_id = session['user']['id'] - if comment_user_id != int(user['id']): + if comment_user_id != user_id: abort(403) delete_comment = PostClient.delete_comment(post_id, comment_id) @@ -210,15 +288,14 @@ def delete_post(post_id): return redirect(url_for('frontend.login_route')) post = PostClient.get_post(post_id) - user_response = UserClient.get_user() - user = user_response['result'] + user_id = session['user']['id'] if post == 404: abort(404) post_user_id = post[0]['user_id'] - if post_user_id != int(user['id']): + if post_user_id != user_id: abort(403) delete_post = PostClient.delete_post(post_id) @@ -235,4 +312,67 @@ def logout(): return redirect(url_for('frontend.login_route')) UserClient.post_logout() session.clear() - return redirect(url_for('frontend.get_posts')) \ No newline at end of file + return redirect(url_for('frontend.get_posts')) + + +@frontend_blueprint.route('/user/<int:user_id>', methods=['GET']) +def display_user(user_id): + if len(session)<4: + return redirect(url_for('frontend.login_route')) + + response = UserClient.get_otheruser(user_id) + + if response is None: + flash('Profile is not detected', 'fail') + return redirect(url_for('frontend.get_posts')) + + if user_id == int(session['user']['id']): + return redirect(url_for('frontend.display_currentuser')) + + user_posts = [] + posts = PostClient.get_posts() + for post in posts: + if post['user_id'] == int(response['id']): + user_posts.append(post) + + content = { + 'name': response['full_name'], + 'email': response['email'], + 'role': response['user_role'], + 'image': response['image_url'] + } + + return render_template('forum/user.html', **content, posts=user_posts) + + +@frontend_blueprint.route('/user/profile', methods=['GET','POST']) +def display_currentuser(): + if len(session)<4: + return redirect(url_for('frontend.login_route')) + + user_posts = [] + posts = PostClient.get_posts() + for post in posts: + if post['user_id'] == session['user']['id']: + user_posts.append(post) + + form = forms.AccountUpdateForm() + if request.method == "POST": + if form.validate_on_submit(): + if form.image.data: + post_image = save_user_image(form.image.data) + form.image_url = post_image + else: + form.image_url = 'defaultpp.png' + + user = UserClient.post_user_update(form) + if user: + flash('Account information updated', 'success') + updated_user=UserClient.get_otheruser(session['user']['id']) + session['user'] = updated_user + return redirect(url_for('frontend.display_currentuser')) + else: + flash('Account update failed', 'fail') + return redirect(url_for('frontend.display_currentuser')) + + return render_template('forum/profile.html', user=session['user'], posts=user_posts, form=form) \ No newline at end of file diff --git a/frontend/application/static/images/722d7684936cf20d.jpeg b/frontend/application/static/images/722d7684936cf20d.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..a6289b8b0e59ca11c677765248e1e11ec2096543 Binary files /dev/null and b/frontend/application/static/images/722d7684936cf20d.jpeg differ diff --git a/frontend/application/static/images/users/4a52b61da594027f.jpg b/frontend/application/static/images/users/4a52b61da594027f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d918cbbe80beb5f3509787ff252b91a3e103c1d0 Binary files /dev/null and b/frontend/application/static/images/users/4a52b61da594027f.jpg differ diff --git a/frontend/application/static/images/users/a7674577287ec3b2.jpg b/frontend/application/static/images/users/a7674577287ec3b2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d918cbbe80beb5f3509787ff252b91a3e103c1d0 Binary files /dev/null and b/frontend/application/static/images/users/a7674577287ec3b2.jpg differ diff --git a/frontend/application/static/images/users/defaultpp.png b/frontend/application/static/images/users/defaultpp.png new file mode 100644 index 0000000000000000000000000000000000000000..c4dfadc67da69aa2a3c75c5235142329e18c5ac9 Binary files /dev/null and b/frontend/application/static/images/users/defaultpp.png differ diff --git a/frontend/application/static/images/users/e1f20076328bbebd.jpeg b/frontend/application/static/images/users/e1f20076328bbebd.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..26db3bcd59a30039285eee69ecc46a9225a853a0 Binary files /dev/null and b/frontend/application/static/images/users/e1f20076328bbebd.jpeg differ diff --git a/frontend/application/static/images/users/eda01672470e1477.png b/frontend/application/static/images/users/eda01672470e1477.png new file mode 100644 index 0000000000000000000000000000000000000000..a4b262436cb660d3307b02572bc56682ef663685 Binary files /dev/null and b/frontend/application/static/images/users/eda01672470e1477.png differ diff --git a/frontend/application/templates/auth/signup.html b/frontend/application/templates/auth/signup.html index e6ce7058662683cc97250c08084fe6d88c882184..f1b177e8ded404b6e270184810b351d7e5589de0 100644 --- a/frontend/application/templates/auth/signup.html +++ b/frontend/application/templates/auth/signup.html @@ -81,9 +81,9 @@ <br> <input type="email" id="email" name="email" placeholder="Enter email" required> <br> - <input type="text" id="phone_number" name="phone_number" placeholder="Enter phone number" required> + <input type="text" id="phone_number" name="phone_number" placeholder="Enter phone number (Not required)"> <br> - <input type="text" id="uni_number" name="uni_number" placeholder="Enter university ID number" required> + <input type="text" id="uni_number" name="uni_number" placeholder="Enter university ID number, for students"> <br> <select id="user_role" name="user_role"> <option value="staff">Staff</option> diff --git a/frontend/application/templates/forum/create_post.html b/frontend/application/templates/forum/create_post.html index dfb2dc8e1d6a531c30aeadcaf1b3480aab1865fa..7e7d7c31aa85d98e145ebd967219748e064fde26 100644 --- a/frontend/application/templates/forum/create_post.html +++ b/frontend/application/templates/forum/create_post.html @@ -22,7 +22,9 @@ <div class="col-md-6 gedf-main"> <div class="card gedf-card"> - <form class="post__form" action="" method="POST"> + <form action="" method="POST", enctype="multipart/form-data"> + + {{ form.csrf_token }} {{ displayField(form.title, 'Enter post title') }} <select id="category" name="category"> @@ -37,12 +39,13 @@ <option value="other">Other</option> </select> {{ displayField(form.content, 'Type content here') }} + <p>Upload an image, not required.</p> + {{ form.image }} + </div> <button class="btn btn-primary" aria-label="Create" type="submit">CREATE</button> <a class="btn btn-danger" href="{{ url_for('frontend.get_posts') }}">CANCEL</a> - {{ form.csrf_token }} - </form> </div> </div> diff --git a/frontend/application/templates/forum/index.html b/frontend/application/templates/forum/index.html index 50ee35a99b24fd748151fafde250db3a56bf5efe..3438f947a6f8fa453948e203c91d6cd5b73fe4de 100644 --- a/frontend/application/templates/forum/index.html +++ b/frontend/application/templates/forum/index.html @@ -9,8 +9,6 @@ </div> - - <div class="col-md-6 gedf-main"> <div class="card gedf-card"> <div class="card-header"> @@ -18,9 +16,6 @@ <li class="nav-item"> <a class="nav-link active" id="posts-tab" data-toggle="tab" href="#posts" role="tab" aria-controls="posts" aria-selected="true">Start a discussion</a> </li> - <li class="nav-item"> - <a class="nav-link" id="images-tab" data-toggle="tab" role="tab" aria-controls="images" aria-selected="false" href="#images">Images</a> - </li> </ul> </div> <div class="card-body"> @@ -31,43 +26,24 @@ <a class="btn btn--link" href="{{ url_for('frontend.create_post') }}">New Post</a> </div> </div> - <div class="tab-pane fade" id="images" role="tabpanel" aria-labelledby="images-tab"> - <div class="form-group"> - <div class="custom-file"> - <input type="file" class="custom-file-input" id="customFile"> - <label class="custom-file-label" for="customFile">Upload image</label> - </div> - </div> - <div class="py-4"></div> - </div> </div> </div> </div> - {% for post in posts %} + {% for post,image in data %} <div class="card gedf-card"> <div class="card-header"> <div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center"> <div class="mr-2"> - <img class="rounded-circle" width="45" src="https://picsum.photos/50/50" alt=""> + <img class="rounded-circle" width="45" src="images/users/{{ image }}"> </div> <div class="ml-2"> - <div class="h5 m-0">@{{ post['user_name']}}</div> - <div class="h7 text-muted">{{ post['user_name']}}</div> + <div class="h5 m-0"><a href="{{ url_for('frontend.display_user', user_id=post.user_id) }}">{{ post['user_name'] }}</a></div> </div> </div> <div> <div class="dropdown"> - <button class="btn btn-link dropdown-toggle" type="button" id="gedf-drop1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <i class="fa fa-ellipsis-h"></i> - </button> - <div class="dropdown-menu dropdown-menu-right" aria-labelledby="gedf-drop1"> - <div class="h6 dropdown-header">Configuration</div> - <a class="dropdown-item" href="#">Save</a> - <a class="dropdown-item" href="#">Hide</a> - <a class="dropdown-item" href="#">Report</a> - </div> </div> </div> </div> @@ -80,7 +56,7 @@ <p class="card-text">{{ post.content | safe }}</p> </div> <div class="card-footer"> - <a href="#" class="card-link"><i class="fa fa-comment"></i> Comment</a> + <a href="{{ url_for('frontend.display_post', post_id=post.id) }}" class="card-link"><i class="fa fa-comment"></i> Comment</a> </div> </div> {% endfor %} diff --git a/frontend/application/templates/forum/post.html b/frontend/application/templates/forum/post.html index 0d121960155acf4155333e307fdaafd30ba6c830..d3ffe4a71ada17318bec9726dfcd66d36e343bca 100644 --- a/frontend/application/templates/forum/post.html +++ b/frontend/application/templates/forum/post.html @@ -1,9 +1,11 @@ {# application/templates/forum/post.html #} -{% from 'macros.html' import displayField %} +<a class="btn btn--link" href="{{ url_for('frontend.get_posts') }}">Home</a> -{% block title %}Post{% endblock %} +{% from 'macros.html' import displayField %} +<br> +<br> <div> {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} @@ -23,24 +25,28 @@ <div class="tile tile--wide"> <div class="tile__content"> - - <div class="tile__header"> - <h2 class="tile__title"><a href="{{ url_for('frontend.display_post', post_id=post.id) }}">{{ post['title'] }}</a></h2> - <p class="tile__date">{{ post['date_added'] }}</p> - </div> + <div class="tile__author"> + <img class="rounded-circle" width="45" src="images/users/{{ owner_image }}"> + <h2 class="tile__title"><a href="{{ url_for('frontend.display_user', user_id=post.user_id) }}">{{ post['user_name'] }}</a></h2> + </div> <div class="tile__text"> <p>{{ post['content'] | safe }}</p> </div> - <div class="tile__footer"> + {% if post['image_url'] != '' %} + <img class="post-image" src="images/{{ post['image_url'] }}"> + {% endif %} - <div class="tile__author"> - {{ post['user_name']}} - </div> + <div class="tile__header"> + {{ post['title'] }} + <p class="tile__date">{{ post['date_added'] }}</p> + </div> + {% if current_user_id == post['user_id'] %} <div class="tile__buttons"> <a class="tile__btn" href="{{ url_for('frontend.delete_post', post_id=post.id) }}">Delete Post</a> + {% endif %} </div> </div> @@ -52,33 +58,30 @@ <!-- END posts --> <!-- START comments --> + {% block title %} COMMENTS {% endblock %} {% if comments %} - {% for comment in comments %} - - <div class="comment comment--wide"> - - <div class="comment__content"> + {% for comment,image in comments %} - <div class="comment__header"> - <h2 class="comment__title">{{ comment['title'] }}</h2> - <p class="tile__date">{{ comment['date_added'] }}</p> + <div class="comment__author"> + <img class="rounded-circle" width="45" src="images/users/{{ image }}"> + <p class="comment__title">{{ comment['user_name']}}</p> </div> - + <div class="comment__text"> <p>{{ comment['content'] | safe }}</p> </div> - <div class="comment__footer"> - - <div class="comment__author"> - <p class="comment__title">{{ comment['user_name']}}</p> - </div> + <div class="comment__header"> + <p class="tile__date">{{ comment['date_added'] }}</p> + </div> + {% if current_user_id == comment['user_id'] %} <div class="tile__buttons"> <a class="tile__btn" href="{{ url_for('frontend.delete_comment', post_id=post.id, comment_id=comment.id) }}">Delete Comment</a> </div> + {% endif %} </div> @@ -97,8 +100,6 @@ {{ displayField(form.content, 'Type comment here...') }} <button class="btn btn--solid post-btn" type="submit">SUBMIT</button> - <a class="btn btn--link" href="{{ url_for('frontend.get_posts') }}">HOME</a> - </form> {{ ckeditor.load(pkg_type="basic") }} diff --git a/frontend/application/templates/forum/profile.html b/frontend/application/templates/forum/profile.html new file mode 100644 index 0000000000000000000000000000000000000000..68afac69d84d7dbad9407e0dc4511ca6a9877060 --- /dev/null +++ b/frontend/application/templates/forum/profile.html @@ -0,0 +1,66 @@ +<a class="btn btn--link" href="{{ url_for('frontend.get_posts') }}">Home</a> +<br> +<br> + +<div> + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + <ul> + {% for category, message in messages %} + <li class="{{ category }}">{{ message }}</li> + {% endfor %} + </ul> + {% endif %} + {% endwith %} +</div> + +<form action="{{ url_for('frontend.display_currentuser') }}" method="POST", enctype="multipart/form-data"> + + {{ form.csrf_token }} + + <img class="rounded-circle" width="100" src="images/users/{{session['user'].image_url }}"> + <br> + <input type="file" id="image" name="image" placeholder="Upload new profile picture"> + <br> + <br> + {{user['first_name']}} + <br> + <input type="text" id="first_name" name="first_name" placeholder="Enter modified first name"> + <br> + <br> + {{user['last_name']}} + <br> + <input type="text" id="last_name" name="last_name" placeholder="Enter modified last name"> + <br> + <br> + {{user['email']}} + <br> + <br> + {{user['phone_number']}} + <br> + <input type="text" id="phone_number" name="phone_number" placeholder="Enter modified phone number"> + <br> + <br> + {{user['user_role']}} + {% if user['user_role'] == 'student' %} + <br> + <select id="user_role" name="user_role"> + <option value=""></option> + <option value="graduate">Graduate</option> + </select> + {% endif %} + <br> + <br> + <input type="submit" value="Update"> +</form> + +{% block title %}YOUR POSTS{% endblock %} + +{% for post in posts %} + + <div class="h4 m-0"><a href="{{ url_for('frontend.display_post', post_id=post.id) }}">{{ post['title'] }}</a></div> + <p class="card-text">{{ post.content | safe }}</p> + +{% endfor %} + + diff --git a/frontend/application/templates/forum/user.html b/frontend/application/templates/forum/user.html new file mode 100644 index 0000000000000000000000000000000000000000..9b0de10f4da4380c5a2e39e704c4c43776eaad22 --- /dev/null +++ b/frontend/application/templates/forum/user.html @@ -0,0 +1,28 @@ +<a class="btn btn--link" href="{{ url_for('frontend.get_posts') }}">Home</a> +<br> +<br> +<img class="rounded-circle" width="100" src="images/users/{{ image }}"> +<br> +<br> +{{name}} +<br> +{{email}} +<br> +{{role}} +<br> +<br> + +{% block title %}POSTS OF THE USER{% endblock %} + +{% for post in posts %} + <div class="h4 m-0"><a href="{{ url_for('frontend.display_post', post_id=post.id) }}">{{ post['title'] }}</a></div> + <p class="card-text">{{ post.content | safe }}</p> + <br> + +{% endfor %} + + + + + + diff --git a/frontend/application/templates/sidebar.html b/frontend/application/templates/sidebar.html index 4bf76b655a7043767a5558bf9da180d11384a11f..5a514cceb9d81b487c85618fd95bd67c079fc23a 100644 --- a/frontend/application/templates/sidebar.html +++ b/frontend/application/templates/sidebar.html @@ -1,9 +1,10 @@ <div class="col-md-3"> <div class="card"> <div class="card-body"> - <div class="h5">{{user['result'].full_name}}</div> - <div class="h7 text-muted">{{user['result'].email}}</div> - <div class="h7">{{user['result'].user_role}} at University of Surrey + <img class="rounded-circle" width="45" src="images/users/{{ session['user'].image_url }}"> + <div class="h5"><a href="{{ url_for('frontend.display_currentuser') }}">{{ session['user'].full_name }}</a></div> + <div class="h7 text-muted">{{session['user'].email}}</div> + <div class="h7">{{session['user'].user_role}} at University of Surrey </div> </div> <ul class="list-group list-group-flush"> diff --git a/post-service/application/models.py b/post-service/application/models.py index 5a72d4f5bf17c113a1386b2edaa22afa589668cb..3fecb837f25e3840737e063a9b1ed2b57570800d 100644 --- a/post-service/application/models.py +++ b/post-service/application/models.py @@ -10,6 +10,7 @@ class Post(db.Model): category = db.Column(db.String(20), index=True, nullable=False) date_added = db.Column(db.DateTime, default=datetime.utcnow) content = db.Column(db.Text, nullable=False) + image_url = db.Column(db.Text,nullable=True) comments = db.relationship('Comment', backref='post', lazy='dynamic') def __repr__(self): @@ -23,7 +24,8 @@ class Post(db.Model): 'title': self.title, 'category': self.category, 'date_added': self.date_added, - 'content' : self.content + 'content' : self.content, + 'image_url':self.image_url } diff --git a/post-service/application/post_api/routes.py b/post-service/application/post_api/routes.py index 66f043e4ffad092c146e2b9dafa0d5ec14d8efb6..0bdc961fd2428fe4946849371997a9e0f5677a61 100644 --- a/post-service/application/post_api/routes.py +++ b/post-service/application/post_api/routes.py @@ -75,6 +75,7 @@ def post_newpost(): title = request.form['title'] category = request.form['category'] content = request.form['content'] + image_url = request.form['image'] post = Post() post.user_id = u_id @@ -82,6 +83,7 @@ def post_newpost(): post.title = title post.category = category post.content = content + post.image_url = image_url db.session.add(post) db.session.commit() diff --git a/post-service/instance/posts-service.db b/post-service/instance/posts-service.db index 4aaee25d447ab4685b6f3fb9607a438e58ce2a87..a04cec11ce5c881e4ea16be66fdc7e5fe1705e3e 100644 Binary files a/post-service/instance/posts-service.db and b/post-service/instance/posts-service.db differ diff --git a/user-service/application/models.py b/user-service/application/models.py index 23f6342a9123ed9e74facb1b2b8a985a6eac967f..2372270037d8376d6983283b866c9eff62ee8836 100644 --- a/user-service/application/models.py +++ b/user-service/application/models.py @@ -7,13 +7,14 @@ from passlib.hash import sha256_crypt class User(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(255), unique=True, nullable=False) - phone_number = db.Column(db.String(255), unique=True, nullable=True) - uni_number = db.Column(db.Integer, unique=True, nullable=True) + phone_number = db.Column(db.String(255), unique=False, nullable=True) + uni_number = db.Column(db.Integer, unique=False, nullable=True) user_role = db.Column(db.String(255), unique=False, nullable=False) first_name = db.Column(db.String(255), unique=False, nullable=False) last_name = db.Column(db.String(255), unique=False, nullable=False) authenticated = db.Column(db.Boolean, default=False) password = db.Column(db.String(255), unique=False, nullable=False) + image_url = db.Column(db.String(255), nullable=False) api_key = db.Column(db.String(255), unique=True, nullable=True) date_added = db.Column(db.DateTime, default=datetime.utcnow) # date_updated = db.Column(db.DateTime, onupdate=datetime.utcnow) @@ -37,6 +38,7 @@ class User(db.Model, UserMixin): 'uni_number': self.uni_number, 'user_role': self.user_role, 'id': self.id, + 'image_url':self.image_url, 'api_key': self.api_key, 'is_active': True } diff --git a/user-service/application/user_api/routes.py b/user-service/application/user_api/routes.py index 87391d66aa820bac0818287f47c6af4ea04bad6f..b9229df0a39e54ae5a17fe25e11418bcaa724310 100644 --- a/user-service/application/user_api/routes.py +++ b/user-service/application/user_api/routes.py @@ -62,6 +62,7 @@ def post_register(): phone_number = request.form['phone_number'] uni_number = request.form['uni_number'] user_role = request.form['user_role'] + image_url = request.form['image_url'] password = sha256_crypt.hash((str(request.form['password']))) @@ -73,6 +74,7 @@ def post_register(): user.uni_number = uni_number user.phone_number = phone_number user.user_role = user_role + user.image_url = image_url user.authenticated = True db.session.add(user) @@ -82,6 +84,40 @@ def post_register(): return response +@user_api_blueprint.route('/api/user/update', methods=['POST']) +def post_update(): + + id = request.form['user_id'] + user = User.query.filter_by(id=id).first() + + image_url = request.form['image_url'] + first_name = request.form['first_name'] + last_name = request.form['last_name'] + phone_number = request.form['phone_number'] + + if len(request.form) > 5: + if request.form['user_role'] != '': + user_role = request.form['user_role'] + user.user_role = user_role + + if image_url != 'defaultpp.png': + user.image_url = image_url + + if first_name != '': + user.first_name = first_name + + if last_name != '': + user.last_name = last_name + + if phone_number != '': + user.phone_number = phone_number + + db.session.commit() + + response = jsonify({'message': 'User updated', 'result': user.to_json()}) + + return response + @user_api_blueprint.route('/api/user/login', methods=['POST']) def post_login(): email = request.form['email'] @@ -112,4 +148,17 @@ def get_email(email): response = jsonify({'result': True}) else: response = jsonify({'message': 'Cannot find email'}), 404 + return response + +@user_api_blueprint.route('/api/user/<id>', methods=['GET']) +def get_otheruser(id): + response = [] + + user = User.query.filter_by(id=id).first() + if user is None: + response = jsonify({'message': 'Cannot find user'}), 404 + return response + + response = jsonify(user.to_json()) + return response \ No newline at end of file diff --git a/user-service/instance/users-service.db b/user-service/instance/users-service.db index b8ea0d48ca9994f2a82b9c1c46c802abffb453e2..683a2edb7ee3a1cfcf8ea88e445a618df676d5d5 100644 Binary files a/user-service/instance/users-service.db and b/user-service/instance/users-service.db differ