From 4c914bf9ba61fac681089ca81314abf2cab96f52 Mon Sep 17 00:00:00 2001 From: DhafinFK Date: Sun, 4 Aug 2024 06:50:23 +0700 Subject: [PATCH] fix: returned admin feature --- app/__init__.py | 2 + app/decorators.py | 12 ++++ app/utils.py | 9 ++- app/views/admin.py | 145 +++++++++++++++++++++++++++++++++++++++++++++ models/admin.py | 10 ++++ 5 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 app/views/admin.py create mode 100644 models/admin.py diff --git a/app/__init__.py b/app/__init__.py index 1407059..65096a1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -14,6 +14,7 @@ from app.views.auth import router_auth from app.views.main import router_main from app.views.review import router_review +from app.views.admin import router_admin from app.cron import cron from uploader.views import router_uploader @@ -86,6 +87,7 @@ app.register_blueprint(router_main, url_prefix=app.config["BASE_PATH"]) app.register_blueprint(router_uploader, url_prefix=app.config["BASE_PATH"]) app.register_blueprint(router_review, url_prefix=app.config["BASE_PATH"]) +app.register_blueprint(router_admin, url_prefix=app.config["BASE_PATH"]) app.register_blueprint(cron) CORS(app) diff --git a/app/decorators.py b/app/decorators.py index 0ed2a22..cf70b82 100644 --- a/app/decorators.py +++ b/app/decorators.py @@ -26,4 +26,16 @@ def decorated_func(*args, **kwargs): 'message': 'There is no token/token is not valid' }), 401) return func(*args, **kwargs) + return decorated_func + + +def require_admin_jwt(func): + @functools.wraps(func) + def decorated_func(*args, **kwargs): + data = extract_header_data(request.headers) + if(data['credentials'] == os.environ.get("ADMIN_CREDENTIAL_VERIFICATION")): + return func(*args, **kwargs) + return (jsonify({ + 'message': 'Unauthorized access' + }), 403) return decorated_func \ No newline at end of file diff --git a/app/utils.py b/app/utils.py index 0ac79bf..f804b4f 100644 --- a/app/utils.py +++ b/app/utils.py @@ -93,4 +93,11 @@ def process_sso_profile(sso_profile): def get_app_config(varname): - return app.config.get(varname) \ No newline at end of file + return app.config.get(varname) + + +def generate_admin_jwt(): + token = encode_token({ + 'credentials': os.environ.get("ADMIN_CREDENTIAL_VERIFICATION") + }) + return token \ No newline at end of file diff --git a/app/views/admin.py b/app/views/admin.py new file mode 100644 index 0000000..51cc7ce --- /dev/null +++ b/app/views/admin.py @@ -0,0 +1,145 @@ +import html +from flask import ( + Blueprint, + jsonify, + request +) + +from datetime import datetime +from app.decorators import require_jwt_token, require_admin_jwt +from app.jwt_utils import decode_token +from models.admin import Admin +from models.review import Review +from app.utils import generate_admin_jwt + +router_admin = Blueprint('router_admin', __name__) + +@router_admin.route('/admin', methods=['GET']) +@require_jwt_token +@require_admin_jwt +def admin_test(): + return (jsonify({ + "Message": "Admin api accessed." + }), 200) + + +@router_admin.route("/admin/login", methods=['POST']) +def admin_login(): + data = request.json + username = data.get('username') + password = data.get('password') + + admin = Admin.objects(username=username).first() + + if(admin and admin.check_password(password)): + token = generate_admin_jwt() + return (jsonify({ + 'token': token + }), 200) + + return (jsonify({ + 'message': 'Invalid credentials.' + }), 401) + + + +@router_admin.route("admin/reviews-overview", methods=['GET']) +@require_jwt_token +@require_admin_jwt +def admin_review_overview(): + total_reviews = Review.objects.count() + if total_reviews == 0: + return jsonify({ + 'average_rating': 0, + 'rating_counts': {str(i): 0 for i in range(1, 6)} + }), 200 + + total_rating = Review.objects.sum('rating') + average_rating = total_rating / total_reviews + + rating_counts = {str(i): Review.objects(rating=i).count() for i in range(1, 6)} + + return jsonify({ + 'average_rating': average_rating, + 'rating_counts': rating_counts + }), 200 + + +@router_admin.route("/admin/reviews/list", methods=['GET']) +@require_jwt_token +@require_admin_jwt +def admin_review_list(): + try: + page = int(request.args.get('page', 1)) + except ValueError: + return (jsonify({ + 'message': 'Invalid page number.' + }), 400) + + if page <= 0: + return (jsonify({ + 'message': 'Page must be a positive integer.' + }), 400) + + data = request.json if request.json else {} + per_page = data.get('per_page', 10) + + try: + per_page = int(per_page) + if per_page <= 0: + raise ValueError + except ValueError: + return jsonify({'message': 'per_page must be a positive integer.'}), 400 + + skip = (page - 1) * per_page + + reviews = Review.objects.order_by('-created_at').skip(skip).limit(per_page) + total_reviews = Review.objects.count() + total_pages = (total_reviews + per_page - 1) // per_page + + serialized_reviews = [review.serialize() for review in reviews] + + return jsonify({ + 'page': page, + 'total_page': total_pages, + 'per_page': per_page, + 'total_reviews': total_reviews, + 'reviews': serialized_reviews + }), 200 + + +@router_admin.route("admin/review/status/", methods=['PATCH']) +@require_jwt_token +@require_admin_jwt +def admin_edit_review_status(review_id): + data = request.json + new_status = data.get("reviewed") + + if new_status is None: + return jsonify({'message': 'Missing reviewed status.'}), 400 + + try: + review = Review.objects.get(id=review_id) + except Review.DoesNotExist: + return jsonify({'message': 'Review not found.'}), 404 + + review.reviewed = new_status + review.save() + + return jsonify({ + 'id': str(review.id), + 'reviewed': review.reviewed + }), 200 + + +@router_admin.route("/admin/review/delete/", methods=['DELETE']) +@require_jwt_token +@require_admin_jwt +def admin_delete_review(review_id): + try: + review = Review.objects.get(id=review_id) + except Review.DoesNotExist: + return jsonify({'message': 'Review not found.'}), 404 + + review.delete() + return jsonify({'message': 'Review deleted successfully.'}), 200 \ No newline at end of file diff --git a/models/admin.py b/models/admin.py new file mode 100644 index 0000000..4a4cada --- /dev/null +++ b/models/admin.py @@ -0,0 +1,10 @@ +import mongoengine as mongo +from werkzeug.security import check_password_hash + + +class Admin(mongo.Document): + username = mongo.StringField(max_length=256, required=True) + password = mongo.StringField(max_length=256, required=True) + + def check_password(self, password): + return check_password_hash(self.password, password) \ No newline at end of file