diff --git a/.gitignore b/.gitignore index 6399970..6b089f9 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ env/ .venv/ __pypackages__/ +**.env # Node.js node_modules/ diff --git a/backend/app.py b/backend/app.py index aa9c5cb..64476de 100644 --- a/backend/app.py +++ b/backend/app.py @@ -15,6 +15,7 @@ from utils.utils import random_string_generator, hash_string from blueprints.profile import profile as profileBlueprint from blueprints.session import session as sessionBlueprint +from blueprints.admin import admin as adminBlueprint app = Flask(__name__) # Set configuration directly on the app instance @@ -26,6 +27,7 @@ db.init_app(app) app.register_blueprint(profileBlueprint, url_prefix='/api/profile') app.register_blueprint(sessionBlueprint,url_prefix='/api/session') +app.register_blueprint(adminBlueprint,url_prefix='/api/admin') @app.route('/media/') def send_file(filename): @@ -53,7 +55,7 @@ def seed_data(): joinedDate=datetime.utcnow(), lastOnline=datetime.utcnow(), bio=f"This is user{i}'s bio.", role=int(UserRole.USER), isActivated=True, sessions=[], user_badges=[], - enrollments=[], quizzes=[], quiz_attempts=[], chats=[], notifications=[]) + enrollments=[], quizzes=[], quiz_attempts=[], chats=[], notifications=[],publications=[]) for i in range(1, 6) ] db.session.add_all(users) db.session.commit() diff --git a/backend/blueprints/admin/__init__.py b/backend/blueprints/admin/__init__.py new file mode 100644 index 0000000..9e2641a --- /dev/null +++ b/backend/blueprints/admin/__init__.py @@ -0,0 +1,140 @@ +from utils .auth import auth_required, requires_role +from flask import Blueprint, jsonify, g +from db.model import User, Course, Enrollment, Chat, db +from sqlalchemy import select, func, desc, and_ +from datetime import datetime, timedelta +from constants import UserRole + +admin = Blueprint('admin', __name__) + +@admin.route('/stats/users', methods=['GET']) +@auth_required() +@requires_role([UserRole.ADMIN]) +def get_user_stats(): + """ + Get total users and authors count. + Only accessible by admin users. + """ + try: + # Get total users + total_users = db.session.execute( + select(func.count()).select_from(User) + ).scalar() + + # Get authors (users who have created courses) + distinct_authors_count = db.session.execute( + select(func.count(func.distinct(Course.authorID))) + ).scalar() + + return jsonify({ + 'stats': { + 'totalUsers': total_users, + 'totalAuthors': distinct_authors_count + } + }), 200 + + except Exception as e: + return jsonify({'message': f'An error occurred: {str(e)}'}), 500 + +@admin.route('/stats/enrollments', methods=['GET']) +@auth_required() +@requires_role([UserRole.ADMIN]) +def get_enrollment_stats(): + """ + Get course enrollment and discussion statistics. + Only accessible by admin users. + """ + try: + # Get enrollment and user counts + enrollment_stats = db.session.execute( + select( + func.count(Enrollment.id).label('total_enrollments'), + func.count(func.distinct(Enrollment.userID)).label('enrolled_users') + ) + .select_from(Enrollment) + ).first() + + # Get course-wise enrollment counts + course_stats = db.session.execute( + select( + Course.name, + func.count(Enrollment.id).label('enrollment_count') + ) + .join(Course, Course.id == Enrollment.courseID) + .group_by(Course.id) + .order_by(desc('enrollment_count')) + ).all() + + return jsonify({ + 'stats': { + 'totalEnrollments': enrollment_stats.total_enrollments, + 'totalEnrolledUsers': enrollment_stats.enrolled_users, + 'courseEnrollments': [{ + 'courseName': stat.name, + 'enrollmentCount': stat.enrollment_count + } for stat in course_stats] + } + }), 200 + + except Exception as e: + return jsonify({'message': f'An error occurred: {str(e)}'}), 500 + +@admin.route('/stats/discussions', methods=['GET']) +@auth_required() +@requires_role([UserRole.ADMIN]) +def get_discussion_stats(): + """ + Get chat room activity statistics. + Only accessible by admin users. + """ + try: + # Get activity for last 24 hours + twenty_four_hours_ago = datetime.now() - timedelta(hours=24) + + # Get active rooms and their stats + active_rooms = db.session.execute( + select( + Course.name, + func.count(Chat.id).label('message_count'), + func.count(func.distinct(Chat.userID)).label('active_users') + ) + .join(Course, Course.id == Chat.courseID) + .where(Chat.chatDate >= twenty_four_hours_ago) + .group_by(Course.id) + .order_by(desc('message_count')) + ).all() + + # Get total active rooms + total_active_rooms = len(active_rooms) + + # Get most active room + most_active_room = None + if active_rooms: + most_active = active_rooms[0] + most_active_room = { + 'name': most_active.name, + 'messageCount': most_active.message_count, + 'activeUsers': most_active.active_users + } + + # Get total active users across all rooms + total_active_users = db.session.execute( + select(func.count(func.distinct(Chat.userID))) + .where(Chat.chatDate >= twenty_four_hours_ago) + ).scalar() + + return jsonify({ + 'stats': { + 'totalActiveRooms': total_active_rooms, + 'totalActiveUsers': total_active_users, + 'mostActiveRoom': most_active_room, + 'activeRooms': [{ + 'roomName': room.name, + 'messageCount': room.message_count, + 'activeUsers': room.active_users + } for room in active_rooms] + } + }), 200 + + except Exception as e: + return jsonify({'message': f'An error occurred: {str(e)}'}), 500 diff --git a/backend/blueprints/profile/__init__.py b/backend/blueprints/profile/__init__.py index 2d2dee0..597aab3 100644 --- a/backend/blueprints/profile/__init__.py +++ b/backend/blueprints/profile/__init__.py @@ -260,4 +260,9 @@ def change_password(): user.hash_password = generate_password_hash(new_password) db.session.commit() - return jsonify({"message": "Password updated successfully"}), 200 \ No newline at end of file + return jsonify({"message": "Password updated successfully"}), 200 + + +# @profile.route('/hello') +# @auth_required() +# @requires_role([UserRole.ADMIN]) \ No newline at end of file