mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 19:26:33 +00:00
a1f9cd4114
Co-authored-by: Stalin-143 <161853795+Stalin-143@users.noreply.github.com>
717 lines
26 KiB
Python
717 lines
26 KiB
Python
from flask import Blueprint, request, jsonify
|
|
from functools import wraps
|
|
import uuid
|
|
from datetime import datetime
|
|
from pymongo import MongoClient
|
|
import os
|
|
from bson import ObjectId
|
|
|
|
bp = Blueprint('admin', __name__)
|
|
|
|
# MongoDB connection
|
|
mongo_uri = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/')
|
|
client = MongoClient(mongo_uri)
|
|
db = client.openlearnx
|
|
|
|
def admin_required(f):
|
|
@wraps(f)
|
|
def decorated_function(*args, **kwargs):
|
|
try:
|
|
auth_header = request.headers.get('Authorization')
|
|
print(f"Admin auth check - Header: {auth_header}")
|
|
|
|
if not auth_header:
|
|
print("❌ No Authorization header")
|
|
return jsonify({"error": "No authorization header provided"}), 401
|
|
|
|
if not auth_header.startswith('Bearer '):
|
|
print("❌ Invalid authorization format")
|
|
return jsonify({"error": "Invalid authorization format"}), 401
|
|
|
|
token = auth_header.split(' ')[1] if len(auth_header.split(' ')) > 1 else None
|
|
|
|
# Check environment variable - no fallback for security
|
|
expected_token = os.getenv('ADMIN_TOKEN')
|
|
if not expected_token:
|
|
print("❌ ADMIN_TOKEN environment variable not set")
|
|
return jsonify({"error": "Server configuration error: ADMIN_TOKEN not configured"}), 500
|
|
|
|
# Strip any whitespace from both tokens
|
|
if token and expected_token:
|
|
if token.strip() == expected_token.strip():
|
|
print("✅ Admin authentication successful")
|
|
return f(*args, **kwargs)
|
|
|
|
print("❌ Token mismatch")
|
|
return jsonify({"error": "Invalid admin token"}), 401
|
|
|
|
except Exception as e:
|
|
print(f"❌ Admin auth error: {str(e)}")
|
|
return jsonify({"error": "Authentication failed"}), 500
|
|
|
|
return decorated_function
|
|
|
|
def serialize_document(doc):
|
|
"""Convert MongoDB document to JSON-serializable format"""
|
|
if doc:
|
|
if '_id' in doc:
|
|
doc['_id'] = str(doc['_id'])
|
|
return doc
|
|
return None
|
|
|
|
def serialize_course(course):
|
|
"""Convert MongoDB document to JSON-serializable format"""
|
|
if course:
|
|
if '_id' in course:
|
|
del course['_id']
|
|
return course
|
|
return None
|
|
|
|
def convert_to_embed_url(youtube_url):
|
|
"""Convert YouTube watch URL to embed URL - ENHANCED VERSION"""
|
|
if not youtube_url:
|
|
return None
|
|
|
|
try:
|
|
if "youtu.be/" in youtube_url:
|
|
video_id = youtube_url.split("youtu.be/")[1].split("?")[0].split("&")[0]
|
|
elif "youtube.com/watch?v=" in youtube_url:
|
|
video_id = youtube_url.split("v=")[1].split("&")[0]
|
|
elif "youtube.com/embed/" in youtube_url:
|
|
return youtube_url
|
|
else:
|
|
return None
|
|
|
|
video_id = video_id.strip()
|
|
return f"https://www.youtube.com/embed/{video_id}?rel=0&modestbranding=1"
|
|
except Exception as e:
|
|
print(f"Error converting YouTube URL: {e}")
|
|
return None
|
|
|
|
@bp.route("/test", methods=["GET"])
|
|
@admin_required
|
|
def test_admin():
|
|
"""Test admin authentication"""
|
|
return jsonify({
|
|
"success": True,
|
|
"message": "Admin authentication working",
|
|
"timestamp": datetime.now().isoformat()
|
|
})
|
|
|
|
@bp.route("/dashboard", methods=["GET"])
|
|
@admin_required
|
|
def admin_dashboard():
|
|
"""Get admin dashboard statistics"""
|
|
try:
|
|
total_courses = db.courses.count_documents({})
|
|
total_lessons = db.lessons.count_documents({})
|
|
total_modules = db.modules.count_documents({})
|
|
active_students = db.users.count_documents({"status": "active"}) or 2341
|
|
|
|
stats = {
|
|
"total_courses": total_courses,
|
|
"total_lessons": total_lessons,
|
|
"total_modules": total_modules,
|
|
"active_students": active_students,
|
|
"completion_rate": 78
|
|
}
|
|
return jsonify(stats)
|
|
except Exception as e:
|
|
print(f"Dashboard error: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/courses", methods=["GET"])
|
|
@admin_required
|
|
def get_admin_courses():
|
|
"""Get all courses for admin management"""
|
|
try:
|
|
print("Fetching courses from database...")
|
|
courses = list(db.courses.find({}, {"_id": 0}))
|
|
print(f"Found {len(courses)} courses")
|
|
|
|
for course in courses:
|
|
course["students"] = course.get("students", 0)
|
|
course["status"] = "published"
|
|
|
|
return jsonify(courses)
|
|
except Exception as e:
|
|
print(f"Error fetching courses: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/courses", methods=["POST"])
|
|
@admin_required
|
|
def create_course():
|
|
"""Create new course"""
|
|
try:
|
|
data = request.json
|
|
print(f"Creating course with data: {data}")
|
|
|
|
course_id = data.get('id') or f"{data.get('title', '').lower().replace(' ', '-').replace('&', 'and')}-course"
|
|
|
|
existing_course = db.courses.find_one({"id": course_id})
|
|
if existing_course:
|
|
return jsonify({"error": "Course with this ID already exists"}), 400
|
|
|
|
new_course = {
|
|
"id": course_id,
|
|
"title": data.get('title'),
|
|
"subject": data.get('subject'),
|
|
"description": data.get('description'),
|
|
"difficulty": data.get('difficulty'),
|
|
"mentor": data.get('mentor', '5t4l1n'),
|
|
"video_url": data.get('video_url'),
|
|
"embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None,
|
|
"created_at": datetime.now().isoformat(),
|
|
"updated_at": datetime.now().isoformat(),
|
|
"students": 0,
|
|
"progress": 0,
|
|
"modules": []
|
|
}
|
|
|
|
result = db.courses.insert_one(new_course)
|
|
print(f"Course created with ID: {result.inserted_id}")
|
|
|
|
# Remove _id field before returning
|
|
new_course_response = serialize_course(new_course)
|
|
|
|
return jsonify({"success": True, "course": new_course_response}), 201
|
|
|
|
except Exception as e:
|
|
print(f"Error creating course: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/courses/<course_id>", methods=["PUT"])
|
|
@admin_required
|
|
def update_course(course_id):
|
|
"""Update existing course"""
|
|
try:
|
|
data = request.json
|
|
print(f"Updating course {course_id} with data: {data}")
|
|
|
|
update_data = {
|
|
"title": data.get('title'),
|
|
"subject": data.get('subject'),
|
|
"description": data.get('description'),
|
|
"difficulty": data.get('difficulty'),
|
|
"mentor": data.get('mentor'),
|
|
"video_url": data.get('video_url'),
|
|
"embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None,
|
|
"updated_at": datetime.now().isoformat()
|
|
}
|
|
|
|
# Remove None values
|
|
update_data = {k: v for k, v in update_data.items() if v is not None}
|
|
print(f"Filtered update data: {update_data}")
|
|
|
|
result = db.courses.update_one(
|
|
{"id": course_id},
|
|
{"$set": update_data}
|
|
)
|
|
|
|
print(f"Update result: matched={result.matched_count}, modified={result.modified_count}")
|
|
|
|
if result.matched_count == 0:
|
|
return jsonify({"error": "Course not found"}), 404
|
|
|
|
# Get updated course without _id field
|
|
updated_course = db.courses.find_one({"id": course_id}, {"_id": 0})
|
|
return jsonify({"success": True, "course": updated_course})
|
|
|
|
except Exception as e:
|
|
print(f"Error updating course: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/courses/<course_id>", methods=["DELETE"])
|
|
@admin_required
|
|
def delete_course(course_id):
|
|
"""Delete course and all related modules and lessons"""
|
|
try:
|
|
print(f"Deleting course: {course_id}")
|
|
|
|
# Delete related lessons first
|
|
lesson_result = db.lessons.delete_many({"course_id": course_id})
|
|
print(f"Deleted {lesson_result.deleted_count} related lessons")
|
|
|
|
# Delete related modules
|
|
module_result = db.modules.delete_many({"course_id": course_id})
|
|
print(f"Deleted {module_result.deleted_count} related modules")
|
|
|
|
# Delete the course
|
|
result = db.courses.delete_one({"id": course_id})
|
|
|
|
if result.deleted_count == 0:
|
|
return jsonify({"error": "Course not found"}), 404
|
|
|
|
return jsonify({"success": True, "message": "Course deleted successfully"})
|
|
|
|
except Exception as e:
|
|
print(f"Error deleting course: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
# ✅ FIXED: Module Management Endpoints (removed duplicates)
|
|
@bp.route("/courses/<course_id>/modules", methods=["GET"])
|
|
@admin_required
|
|
def get_course_modules(course_id):
|
|
"""Get all modules for a specific course"""
|
|
try:
|
|
print(f"Fetching modules for course: {course_id}")
|
|
|
|
modules = list(db.modules.find({"course_id": course_id}).sort("order", 1))
|
|
|
|
# Convert ObjectId to string
|
|
for module in modules:
|
|
if '_id' in module:
|
|
module['id'] = str(module['_id'])
|
|
del module['_id']
|
|
|
|
print(f"Found {len(modules)} modules for course {course_id}")
|
|
return jsonify({"success": True, "modules": modules})
|
|
|
|
except Exception as e:
|
|
print(f"Error fetching modules: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/courses/<course_id>/modules", methods=["POST"])
|
|
@admin_required
|
|
def create_module(course_id):
|
|
"""Create a new module for a course"""
|
|
try:
|
|
data = request.json
|
|
print(f"Creating module for course {course_id} with data: {data}")
|
|
|
|
# Verify course exists
|
|
course = db.courses.find_one({"id": course_id})
|
|
if not course:
|
|
return jsonify({"error": "Course not found"}), 404
|
|
|
|
module = {
|
|
"course_id": course_id,
|
|
"title": data.get('title'),
|
|
"description": data.get('description', ''),
|
|
"order": data.get('order', 1),
|
|
"created_at": datetime.now().isoformat(),
|
|
"updated_at": datetime.now().isoformat()
|
|
}
|
|
|
|
result = db.modules.insert_one(module)
|
|
module['id'] = str(result.inserted_id)
|
|
if '_id' in module:
|
|
del module['_id']
|
|
|
|
print(f"Module created with ID: {result.inserted_id}")
|
|
return jsonify({"success": True, "module": module}), 201
|
|
|
|
except Exception as e:
|
|
print(f"Error creating module: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/modules/<module_id>", methods=["GET"])
|
|
@admin_required
|
|
def get_module(module_id):
|
|
"""Get a specific module by ID"""
|
|
try:
|
|
print(f"Fetching module: {module_id}")
|
|
|
|
module = db.modules.find_one({"_id": ObjectId(module_id)})
|
|
|
|
if not module:
|
|
return jsonify({"error": "Module not found"}), 404
|
|
|
|
# Convert ObjectId to string
|
|
if '_id' in module:
|
|
module['id'] = str(module['_id'])
|
|
del module['_id']
|
|
|
|
return jsonify({"success": True, "module": module})
|
|
|
|
except Exception as e:
|
|
print(f"Error fetching module: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/modules/<module_id>", methods=["PUT"])
|
|
@admin_required
|
|
def update_module(module_id):
|
|
"""Update an existing module"""
|
|
try:
|
|
data = request.json
|
|
print(f"Updating module {module_id} with data: {data}")
|
|
|
|
update_data = {
|
|
"title": data.get('title'),
|
|
"description": data.get('description'),
|
|
"order": data.get('order'),
|
|
"updated_at": datetime.now().isoformat()
|
|
}
|
|
|
|
# Remove None values
|
|
update_data = {k: v for k, v in update_data.items() if v is not None}
|
|
|
|
result = db.modules.update_one(
|
|
{"_id": ObjectId(module_id)},
|
|
{"$set": update_data}
|
|
)
|
|
|
|
if result.matched_count == 0:
|
|
return jsonify({"error": "Module not found"}), 404
|
|
|
|
# Get updated module
|
|
updated_module = db.modules.find_one({"_id": ObjectId(module_id)})
|
|
if updated_module:
|
|
updated_module['id'] = str(updated_module['_id'])
|
|
del updated_module['_id']
|
|
|
|
return jsonify({"success": True, "module": updated_module})
|
|
|
|
except Exception as e:
|
|
print(f"Error updating module: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/modules/<module_id>", methods=["DELETE"])
|
|
@admin_required
|
|
def delete_module(module_id):
|
|
"""Delete a module and all its lessons"""
|
|
try:
|
|
print(f"Deleting module: {module_id}")
|
|
|
|
# Delete related lessons first
|
|
lesson_result = db.lessons.delete_many({"module_id": module_id})
|
|
print(f"Deleted {lesson_result.deleted_count} related lessons")
|
|
|
|
# Delete the module
|
|
result = db.modules.delete_one({"_id": ObjectId(module_id)})
|
|
|
|
if result.deleted_count == 0:
|
|
return jsonify({"error": "Module not found"}), 404
|
|
|
|
return jsonify({"success": True, "message": "Module deleted successfully"})
|
|
|
|
except Exception as e:
|
|
print(f"Error deleting module: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
# ✅ FIXED: Lesson Management Endpoints
|
|
@bp.route("/modules/<module_id>/lessons", methods=["GET"])
|
|
@admin_required
|
|
def get_module_lessons(module_id):
|
|
"""Get all lessons for a specific module"""
|
|
try:
|
|
print(f"Fetching lessons for module: {module_id}")
|
|
|
|
lessons = list(db.lessons.find({"module_id": module_id}).sort("order", 1))
|
|
|
|
# Convert ObjectId to string
|
|
for lesson in lessons:
|
|
if '_id' in lesson:
|
|
lesson['id'] = str(lesson['_id'])
|
|
del lesson['_id']
|
|
|
|
print(f"Found {len(lessons)} lessons for module {module_id}")
|
|
return jsonify({"success": True, "lessons": lessons})
|
|
|
|
except Exception as e:
|
|
print(f"Error fetching lessons: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/modules/<module_id>/lessons", methods=["POST"])
|
|
@admin_required
|
|
def create_lesson(module_id):
|
|
"""Create a new lesson for a module"""
|
|
try:
|
|
data = request.json
|
|
print(f"Creating lesson for module {module_id} with data: {data}")
|
|
|
|
# Verify module exists
|
|
module = db.modules.find_one({"_id": ObjectId(module_id)})
|
|
if not module:
|
|
return jsonify({"error": "Module not found"}), 404
|
|
|
|
lesson = {
|
|
"module_id": module_id,
|
|
"course_id": module.get('course_id'),
|
|
"title": data.get('title'),
|
|
"description": data.get('description', ''),
|
|
"video_url": data.get('video_url'),
|
|
"embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None,
|
|
"order": data.get('order', 1),
|
|
"duration": data.get('duration'),
|
|
"type": data.get('type', 'video'),
|
|
"content": data.get('content', ''),
|
|
"created_at": datetime.now().isoformat(),
|
|
"updated_at": datetime.now().isoformat()
|
|
}
|
|
|
|
result = db.lessons.insert_one(lesson)
|
|
lesson['id'] = str(result.inserted_id)
|
|
if '_id' in lesson:
|
|
del lesson['_id']
|
|
|
|
print(f"Lesson created with ID: {result.inserted_id}")
|
|
return jsonify({"success": True, "lesson": lesson}), 201
|
|
|
|
except Exception as e:
|
|
print(f"Error creating lesson: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/lessons/<lesson_id>", methods=["GET"])
|
|
@admin_required
|
|
def get_lesson(lesson_id):
|
|
"""Get a specific lesson by ID"""
|
|
try:
|
|
print(f"Fetching lesson: {lesson_id}")
|
|
|
|
lesson = db.lessons.find_one({"_id": ObjectId(lesson_id)})
|
|
|
|
if not lesson:
|
|
return jsonify({"error": "Lesson not found"}), 404
|
|
|
|
# Convert ObjectId to string
|
|
if '_id' in lesson:
|
|
lesson['id'] = str(lesson['_id'])
|
|
del lesson['_id']
|
|
|
|
return jsonify({"success": True, "lesson": lesson})
|
|
|
|
except Exception as e:
|
|
print(f"Error fetching lesson: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/lessons/<lesson_id>", methods=["PUT"])
|
|
@admin_required
|
|
def update_lesson(lesson_id):
|
|
"""Update an existing lesson"""
|
|
try:
|
|
data = request.json
|
|
print(f"Updating lesson {lesson_id} with data: {data}")
|
|
|
|
update_data = {
|
|
"title": data.get('title'),
|
|
"description": data.get('description'),
|
|
"video_url": data.get('video_url'),
|
|
"embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None,
|
|
"order": data.get('order'),
|
|
"duration": data.get('duration'),
|
|
"type": data.get('type'),
|
|
"content": data.get('content'),
|
|
"updated_at": datetime.now().isoformat()
|
|
}
|
|
|
|
# Remove None values
|
|
update_data = {k: v for k, v in update_data.items() if v is not None}
|
|
|
|
result = db.lessons.update_one(
|
|
{"_id": ObjectId(lesson_id)},
|
|
{"$set": update_data}
|
|
)
|
|
|
|
if result.matched_count == 0:
|
|
return jsonify({"error": "Lesson not found"}), 404
|
|
|
|
# Get updated lesson
|
|
updated_lesson = db.lessons.find_one({"_id": ObjectId(lesson_id)})
|
|
if updated_lesson:
|
|
updated_lesson['id'] = str(updated_lesson['_id'])
|
|
del updated_lesson['_id']
|
|
|
|
return jsonify({"success": True, "lesson": updated_lesson})
|
|
|
|
except Exception as e:
|
|
print(f"Error updating lesson: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/lessons/<lesson_id>", methods=["DELETE"])
|
|
@admin_required
|
|
def delete_lesson(lesson_id):
|
|
"""Delete a lesson"""
|
|
try:
|
|
print(f"Deleting lesson: {lesson_id}")
|
|
|
|
result = db.lessons.delete_one({"_id": ObjectId(lesson_id)})
|
|
|
|
if result.deleted_count == 0:
|
|
return jsonify({"error": "Lesson not found"}), 404
|
|
|
|
return jsonify({"success": True, "message": "Lesson deleted successfully"})
|
|
|
|
except Exception as e:
|
|
print(f"Error deleting lesson: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
# ✅ LEGACY: For backward compatibility with old course structure
|
|
@bp.route("/courses/<course_id>/lessons", methods=["POST"])
|
|
@admin_required
|
|
def add_lesson_legacy(course_id):
|
|
"""Add lesson to course (legacy endpoint)"""
|
|
try:
|
|
data = request.json
|
|
|
|
lesson = {
|
|
"id": data.get('id') or str(uuid.uuid4()),
|
|
"course_id": course_id,
|
|
"title": data.get('title'),
|
|
"type": data.get('type', 'video'),
|
|
"duration": data.get('duration'),
|
|
"description": data.get('description'),
|
|
"content": data.get('content'),
|
|
"video_url": data.get('video_url'),
|
|
"embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None,
|
|
"created_at": datetime.now().isoformat()
|
|
}
|
|
|
|
# Insert lesson
|
|
db.lessons.insert_one(lesson)
|
|
|
|
# Remove _id field before returning
|
|
lesson_response = serialize_course(lesson)
|
|
|
|
return jsonify({"success": True, "lesson": lesson_response})
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/initialize", methods=["POST"])
|
|
@admin_required
|
|
def initialize_default_courses():
|
|
"""Initialize database with default courses"""
|
|
try:
|
|
existing_count = db.courses.count_documents({})
|
|
if existing_count > 0:
|
|
return jsonify({"message": f"Courses already initialized ({existing_count} courses found)"}), 200
|
|
|
|
default_courses = [
|
|
{
|
|
"id": "python-course",
|
|
"title": "Python Programming Mastery",
|
|
"subject": "Programming",
|
|
"description": "Learn Python from basics to advanced concepts including turtle graphics",
|
|
"difficulty": "Beginner to Advanced",
|
|
"mentor": "5t4l1n",
|
|
"video_url": "https://youtu.be/SsH8GJlqUIg?si=cK7KW_sM0uf95lEp",
|
|
"embed_url": "https://www.youtube.com/embed/SsH8GJlqUIg?rel=0&modestbranding=1",
|
|
"created_at": datetime.now().isoformat(),
|
|
"updated_at": datetime.now().isoformat(),
|
|
"students": 1250,
|
|
"progress": 0,
|
|
"modules": []
|
|
},
|
|
{
|
|
"id": "java-course",
|
|
"title": "Java Development Bootcamp",
|
|
"subject": "Programming",
|
|
"description": "Master Java programming with object-oriented concepts",
|
|
"difficulty": "Intermediate",
|
|
"mentor": "5t4l1n",
|
|
"video_url": "https://youtu.be/SsH8GJlqUIg?si=cK7KW_sM0uf95lEp",
|
|
"embed_url": "https://www.youtube.com/embed/SsH8GJlqUIg?rel=0&modestbranding=1",
|
|
"created_at": datetime.now().isoformat(),
|
|
"updated_at": datetime.now().isoformat(),
|
|
"students": 890,
|
|
"progress": 0,
|
|
"modules": []
|
|
},
|
|
{
|
|
"id": "ethical-hacking-course",
|
|
"title": "Ethical Hacking & Cybersecurity",
|
|
"subject": "Cybersecurity",
|
|
"description": "Learn ethical hacking techniques and penetration testing",
|
|
"difficulty": "Advanced",
|
|
"mentor": "5t4l1n",
|
|
"video_url": "https://youtu.be/cDnX0vyNTaE?si=ZXNI4hv2HlWN7eCS",
|
|
"embed_url": "https://www.youtube.com/embed/cDnX0vyNTaE?rel=0&modestbranding=1",
|
|
"created_at": datetime.now().isoformat(),
|
|
"updated_at": datetime.now().isoformat(),
|
|
"students": 567,
|
|
"progress": 0,
|
|
"modules": []
|
|
},
|
|
{
|
|
"id": "dark-web-hosting-course",
|
|
"title": "Learn Dark Web Hosting",
|
|
"subject": "Cybersecurity",
|
|
"description": "Understanding dark web infrastructure, Tor networks, and secure hosting practices for cybersecurity professionals",
|
|
"difficulty": "Expert",
|
|
"mentor": "5t4l1n",
|
|
"video_url": "https://youtu.be/Z4_USAMVhYs?si=Y_ThVisph5ekM44U",
|
|
"embed_url": "https://www.youtube.com/embed/Z4_USAMVhYs?rel=0&modestbranding=1",
|
|
"created_at": datetime.now().isoformat(),
|
|
"updated_at": datetime.now().isoformat(),
|
|
"students": 234,
|
|
"progress": 0,
|
|
"modules": []
|
|
}
|
|
]
|
|
|
|
result = db.courses.insert_many(default_courses)
|
|
print(f"Initialized {len(result.inserted_ids)} default courses")
|
|
|
|
return jsonify({
|
|
"success": True,
|
|
"message": f"Default courses initialized successfully",
|
|
"courses_created": len(result.inserted_ids)
|
|
})
|
|
except Exception as e:
|
|
print(f"Error initializing courses: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/stats", methods=["GET"])
|
|
@admin_required
|
|
def get_admin_stats():
|
|
"""Get detailed admin statistics"""
|
|
try:
|
|
total_courses = db.courses.count_documents({})
|
|
total_lessons = db.lessons.count_documents({})
|
|
total_modules = db.modules.count_documents({})
|
|
|
|
# Course statistics by subject
|
|
pipeline = [
|
|
{"$group": {"_id": "$subject", "count": {"$sum": 1}}}
|
|
]
|
|
subjects = list(db.courses.aggregate(pipeline))
|
|
|
|
# Course statistics by difficulty
|
|
pipeline = [
|
|
{"$group": {"_id": "$difficulty", "count": {"$sum": 1}}}
|
|
]
|
|
difficulties = list(db.courses.aggregate(pipeline))
|
|
|
|
stats = {
|
|
"total_courses": total_courses,
|
|
"total_lessons": total_lessons,
|
|
"total_modules": total_modules,
|
|
"subjects": subjects,
|
|
"difficulties": difficulties,
|
|
"last_updated": datetime.now().isoformat()
|
|
}
|
|
|
|
return jsonify(stats)
|
|
except Exception as e:
|
|
print(f"Error getting stats: {str(e)}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route("/health", methods=["GET"])
|
|
def admin_health():
|
|
"""Admin health check endpoint"""
|
|
return jsonify({
|
|
"status": "Admin API is healthy",
|
|
"timestamp": datetime.now().isoformat(),
|
|
"database_connected": True,
|
|
"endpoints": [
|
|
"GET /api/admin/dashboard",
|
|
"GET /api/admin/courses",
|
|
"POST /api/admin/courses",
|
|
"PUT /api/admin/courses/<id>",
|
|
"DELETE /api/admin/courses/<id>",
|
|
"GET /api/admin/courses/<course_id>/modules",
|
|
"POST /api/admin/courses/<course_id>/modules",
|
|
"GET /api/admin/modules/<module_id>",
|
|
"PUT /api/admin/modules/<module_id>",
|
|
"DELETE /api/admin/modules/<module_id>",
|
|
"GET /api/admin/modules/<module_id>/lessons",
|
|
"POST /api/admin/modules/<module_id>/lessons",
|
|
"GET /api/admin/lessons/<lesson_id>",
|
|
"PUT /api/admin/lessons/<lesson_id>",
|
|
"DELETE /api/admin/lessons/<lesson_id>",
|
|
"POST /api/admin/initialize",
|
|
"GET /api/admin/test",
|
|
"GET /api/admin/stats"
|
|
]
|
|
})
|