mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 11:25:49 +00:00
Course admin panel added
This commit is contained in:
+105
-11
@@ -1,22 +1,32 @@
|
||||
from flask import Flask, jsonify
|
||||
from flask import Flask, jsonify, request
|
||||
from flask_cors import CORS
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import asyncio
|
||||
from mongo_service import MongoService
|
||||
from web3_service import Web3Service
|
||||
import logging
|
||||
|
||||
# Import all route blueprints
|
||||
from routes import auth, test_flow, certificate, dashboard , courses, quizzes
|
||||
from routes import auth, test_flow, certificate, dashboard, courses, quizzes, admin
|
||||
|
||||
load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
# Enhanced CORS configuration for admin panel with credentials support
|
||||
CORS(app, resources={
|
||||
r"/api/*": {
|
||||
"origins": ["http://localhost:3000", "http://127.0.0.1:3000"],
|
||||
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
"allow_headers": ["Content-Type", "Authorization"],
|
||||
"supports_credentials": True # ✅ Added for admin authentication
|
||||
}
|
||||
})
|
||||
|
||||
# Configuration
|
||||
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'your-secret-key')
|
||||
app.config['MONGODB_URI'] = os.getenv('MONGODB_URI')
|
||||
app.config['MONGODB_URI'] = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/')
|
||||
app.config['WEB3_PROVIDER_URL'] = os.getenv('WEB3_PROVIDER_URL', 'http://127.0.0.1:8545')
|
||||
app.config['CONTRACT_ADDRESS'] = os.getenv('CONTRACT_ADDRESS')
|
||||
app.config['MINTER_PRIVATE_KEY'] = os.getenv('MINTER_PRIVATE_KEY')
|
||||
@@ -36,19 +46,103 @@ app.register_blueprint(certificate.bp, url_prefix='/api/certificate')
|
||||
app.register_blueprint(dashboard.bp, url_prefix='/api/dashboard')
|
||||
app.register_blueprint(courses.bp, url_prefix='/api/courses')
|
||||
app.register_blueprint(quizzes.bp, url_prefix='/api/quizzes')
|
||||
app.register_blueprint(admin.bp, url_prefix="/api/admin")
|
||||
|
||||
@app.route('/')
|
||||
def health_check():
|
||||
return jsonify({"status": "OpenLearnX API is running", "version": "1.0.0"})
|
||||
return jsonify({
|
||||
"status": "OpenLearnX API is running",
|
||||
"version": "1.0.0",
|
||||
"endpoints": {
|
||||
"auth": "/api/auth",
|
||||
"courses": "/api/courses",
|
||||
"admin": "/api/admin",
|
||||
"dashboard": "/api/dashboard",
|
||||
"certificates": "/api/certificate",
|
||||
"quizzes": "/api/quizzes"
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/api/admin/health')
|
||||
def admin_health():
|
||||
return jsonify({
|
||||
"status": "Admin API is running",
|
||||
"admin_endpoints": [
|
||||
"/api/admin/dashboard",
|
||||
"/api/admin/courses",
|
||||
"/api/admin/courses/<id>",
|
||||
"/api/admin/test" # ✅ Added test endpoint
|
||||
]
|
||||
})
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
return jsonify({"error": "Endpoint not found"}), 404
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(error):
|
||||
app.logger.error(f"Internal server error: {str(error)}")
|
||||
return jsonify({"error": "Internal server error"}), 500
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_error(error):
|
||||
app.logger.error(f"Error: {str(error)}")
|
||||
return jsonify({"error": "Internal server error"}), 500
|
||||
app.logger.error(f"Unhandled error: {str(error)}")
|
||||
return jsonify({"error": "An unexpected error occurred"}), 500
|
||||
|
||||
# Enable logging for admin operations
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' # ✅ Enhanced logging format
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@app.before_request
|
||||
def log_request_info():
|
||||
if '/api/admin' in request.path:
|
||||
# ✅ Enhanced admin request logging
|
||||
auth_header = request.headers.get('Authorization', 'No auth header')
|
||||
logger.info(f"Admin request: {request.method} {request.path} | Auth: {auth_header}")
|
||||
|
||||
# ✅ Add OPTIONS handler for CORS preflight
|
||||
@app.before_request
|
||||
def handle_preflight():
|
||||
if request.method == "OPTIONS":
|
||||
response = jsonify()
|
||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
response.headers.add('Access-Control-Allow-Headers', "*")
|
||||
response.headers.add('Access-Control-Allow-Methods', "*")
|
||||
return response
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Initialize database
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(mongo_service.init_db())
|
||||
try:
|
||||
# ✅ Enhanced database initialization with better error handling
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(mongo_service.init_db())
|
||||
logger.info("✅ Database initialized successfully")
|
||||
|
||||
# ✅ Test MongoDB connection
|
||||
from pymongo import MongoClient
|
||||
client = MongoClient(app.config['MONGODB_URI'])
|
||||
client.admin.command('ismaster')
|
||||
logger.info("✅ MongoDB connection verified")
|
||||
|
||||
logger.info("✅ OpenLearnX backend starting...")
|
||||
logger.info(f"✅ Admin panel available at: http://localhost:3000/admin/login")
|
||||
logger.info(f"✅ API health check: http://127.0.0.1:5000")
|
||||
logger.info(f"✅ Admin health check: http://127.0.0.1:5000/api/admin/health")
|
||||
|
||||
# ✅ Log admin token for debugging
|
||||
admin_token = os.getenv('ADMIN_TOKEN', 'admin-secret-key')
|
||||
logger.info(f"✅ Admin token configured: {admin_token[:8]}...")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to initialize: {str(e)}")
|
||||
logger.error("Make sure MongoDB is running and accessible")
|
||||
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
# ✅ Enhanced Flask app configuration
|
||||
app.run(
|
||||
debug=True,
|
||||
host='0.0.0.0',
|
||||
port=5000,
|
||||
threaded=True # Better for handling multiple requests
|
||||
)
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
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
|
||||
print(f"Extracted token: '{token}'")
|
||||
|
||||
# Check environment variable first, then fallback to default
|
||||
expected_token = os.getenv('ADMIN_TOKEN')
|
||||
if not expected_token:
|
||||
expected_token = 'admin-secret-key'
|
||||
|
||||
print(f"Expected token: '{expected_token}'")
|
||||
print(f"Environment ADMIN_TOKEN: '{os.getenv('ADMIN_TOKEN')}'")
|
||||
|
||||
# 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_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({})
|
||||
active_students = db.users.count_documents({"status": "active"}) or 2341
|
||||
|
||||
stats = {
|
||||
"total_courses": total_courses,
|
||||
"total_lessons": total_lessons,
|
||||
"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}") # Debug log
|
||||
|
||||
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 - FIXED VERSION"""
|
||||
try:
|
||||
data = request.json
|
||||
print(f"Updating course {course_id} with data: {data}") # Debug log
|
||||
|
||||
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}") # Debug log
|
||||
|
||||
result = db.courses.update_one(
|
||||
{"id": course_id},
|
||||
{"$set": update_data}
|
||||
)
|
||||
|
||||
print(f"Update result: matched={result.matched_count}, modified={result.modified_count}") # Debug log
|
||||
|
||||
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"""
|
||||
try:
|
||||
print(f"Deleting course: {course_id}") # Debug log
|
||||
|
||||
result = db.courses.delete_one({"id": course_id})
|
||||
|
||||
if result.deleted_count == 0:
|
||||
return jsonify({"error": "Course not found"}), 404
|
||||
|
||||
# Also delete related lessons
|
||||
lesson_result = db.lessons.delete_many({"course_id": course_id})
|
||||
print(f"Deleted {lesson_result.deleted_count} related lessons") # Debug log
|
||||
|
||||
return jsonify({"success": True, "message": "Course deleted successfully"})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error deleting course: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@bp.route("/courses/<course_id>/modules", methods=["POST"])
|
||||
@admin_required
|
||||
def add_module(course_id):
|
||||
"""Add module to course"""
|
||||
try:
|
||||
data = request.json
|
||||
|
||||
module = {
|
||||
"id": data.get('id') or str(uuid.uuid4()),
|
||||
"title": data.get('title'),
|
||||
"lessons": []
|
||||
}
|
||||
|
||||
result = db.courses.update_one(
|
||||
{"id": course_id},
|
||||
{"$push": {"modules": module}}
|
||||
)
|
||||
|
||||
if result.matched_count == 0:
|
||||
return jsonify({"error": "Course not found"}), 404
|
||||
|
||||
return jsonify({"success": True, "module": module})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@bp.route("/courses/<course_id>/lessons", methods=["POST"])
|
||||
@admin_required
|
||||
def add_lesson(course_id):
|
||||
"""Add lesson to course"""
|
||||
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({})
|
||||
|
||||
# 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,
|
||||
"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>",
|
||||
"POST /api/admin/initialize",
|
||||
"GET /api/admin/test",
|
||||
"GET /api/admin/stats"
|
||||
]
|
||||
})
|
||||
+82
-32
@@ -1,43 +1,93 @@
|
||||
from flask import Blueprint, jsonify, current_app
|
||||
import asyncio
|
||||
from bson import ObjectId
|
||||
from pymongo import MongoClient
|
||||
import os
|
||||
|
||||
bp = Blueprint('courses', __name__)
|
||||
|
||||
# Remove trailing slash from route definition
|
||||
# MongoDB connection
|
||||
mongo_uri = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/')
|
||||
client = MongoClient(mongo_uri)
|
||||
db = client.openlearnx
|
||||
|
||||
@bp.route("/", methods=["GET"])
|
||||
@bp.route("", methods=["GET"]) # Add this line to handle both cases
|
||||
@bp.route("", methods=["GET"])
|
||||
def list_courses():
|
||||
"""Get all courses - DYNAMIC from database"""
|
||||
try:
|
||||
# Your existing course logic here
|
||||
# Mock data for now since you're having DB async issues
|
||||
courses = [
|
||||
{
|
||||
"id": "python-course",
|
||||
"title": "Python Programming Mastery",
|
||||
"subject": "Programming",
|
||||
"description": "Learn Python from basics to advanced concepts",
|
||||
"difficulty": "Beginner to Advanced",
|
||||
"progress": 0
|
||||
},
|
||||
{
|
||||
"id": "java-course",
|
||||
"title": "Java Development Bootcamp",
|
||||
"subject": "Programming",
|
||||
"description": "Master Java programming with object-oriented concepts",
|
||||
"difficulty": "Intermediate",
|
||||
"progress": 0
|
||||
},
|
||||
{
|
||||
"id": "ethical-hacking-course",
|
||||
"title": "Ethical Hacking & Cybersecurity",
|
||||
"subject": "Cybersecurity",
|
||||
"description": "Learn ethical hacking techniques and penetration testing",
|
||||
"difficulty": "Advanced",
|
||||
"progress": 0
|
||||
courses = list(db.courses.find({}, {"_id": 0}))
|
||||
|
||||
course_list = []
|
||||
for course in courses:
|
||||
course_data = {
|
||||
"id": course.get("id"),
|
||||
"title": course.get("title"),
|
||||
"subject": course.get("subject"),
|
||||
"description": course.get("description"),
|
||||
"difficulty": course.get("difficulty"),
|
||||
"mentor": course.get("mentor"),
|
||||
"video_url": course.get("video_url"),
|
||||
"embed_url": course.get("embed_url"),
|
||||
"progress": course.get("progress", 0)
|
||||
}
|
||||
]
|
||||
return jsonify(courses)
|
||||
course_list.append(course_data)
|
||||
|
||||
return jsonify(course_list)
|
||||
except Exception as e:
|
||||
print(f"Error in list_courses: {e}")
|
||||
return jsonify({"error": "Failed to fetch courses"}), 500
|
||||
|
||||
@bp.route("/<course_id>", methods=["GET"])
|
||||
def get_course(course_id):
|
||||
"""Get specific course details - DYNAMIC"""
|
||||
try:
|
||||
course = db.courses.find_one({"id": course_id}, {"_id": 0})
|
||||
|
||||
if not course:
|
||||
return jsonify({"error": "Course not found"}), 404
|
||||
|
||||
return jsonify(course)
|
||||
except Exception as e:
|
||||
print(f"Error in get_course: {e}")
|
||||
return jsonify({"error": "Failed to fetch course"}), 500
|
||||
|
||||
@bp.route("/<course_id>/lessons/<lesson_id>", methods=["GET"])
|
||||
def get_lesson(course_id, lesson_id):
|
||||
"""Get specific lesson content - DYNAMIC"""
|
||||
try:
|
||||
lesson = db.lessons.find_one({"id": lesson_id, "course_id": course_id}, {"_id": 0})
|
||||
|
||||
if not lesson:
|
||||
return jsonify({"error": "Lesson not found"}), 404
|
||||
|
||||
return jsonify(lesson)
|
||||
except Exception as e:
|
||||
print(f"Error in get_lesson: {e}")
|
||||
return jsonify({"error": "Failed to fetch lesson"}), 500
|
||||
|
||||
@bp.route("/<course_id>/lessons/<lesson_id>/complete", methods=["POST"])
|
||||
def mark_lesson_complete(course_id, lesson_id):
|
||||
"""Mark a lesson as completed for the user"""
|
||||
try:
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"Lesson {lesson_id} marked as complete",
|
||||
"progress_updated": True
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@bp.route("/<course_id>/progress", methods=["GET"])
|
||||
def get_course_progress(course_id):
|
||||
"""Get user's progress in a specific course"""
|
||||
try:
|
||||
progress = {
|
||||
"course_id": course_id,
|
||||
"completion_percentage": 25,
|
||||
"lessons_completed": [],
|
||||
"total_lessons": 4,
|
||||
"last_accessed": "2025-01-26T23:30:00Z",
|
||||
"time_spent": "2 hours 15 minutes"
|
||||
}
|
||||
return jsonify(progress)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
+45
-6
@@ -15,13 +15,16 @@ async def seed_courses():
|
||||
"subject": "Programming",
|
||||
"description": "Learn Python from basics to advanced concepts including web development, data science, and automation.",
|
||||
"difficulty": "Beginner to Advanced",
|
||||
"mentor": "5t4l1n",
|
||||
"video_url": "https://youtu.be/SsH8GJlqUIg?si=cK7KW_sM0uf95lEp",
|
||||
"modules": [
|
||||
{
|
||||
"id": "python-basics",
|
||||
"title": "Python Fundamentals",
|
||||
"title": "Python Fundamentals",
|
||||
"lessons": [
|
||||
{"id": "variables", "title": "Variables and Data Types", "type": "text"},
|
||||
{"id": "functions", "title": "Functions and Modules", "type": "code"}
|
||||
{"id": "variables", "title": "Variables and Data Types", "type": "video", "video_url": "https://youtu.be/SsH8GJlqUIg?si=cK7KW_sM0uf95lEp"},
|
||||
{"id": "functions", "title": "Functions and Modules", "type": "code"},
|
||||
{"id": "turtle-graphics", "title": "Python Turtle Graphics", "type": "video", "video_url": "https://youtu.be/SsH8GJlqUIg?si=cK7KW_sM0uf95lEp"}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -32,6 +35,8 @@ async def seed_courses():
|
||||
"subject": "Programming",
|
||||
"description": "Master Java programming with object-oriented concepts, Spring framework, and enterprise development.",
|
||||
"difficulty": "Intermediate",
|
||||
"mentor": "5t4l1n",
|
||||
"video_url": "https://youtu.be/SsH8GJlqUIg?si=cK7KW_sM0uf95lEp",
|
||||
"modules": [
|
||||
{
|
||||
"id": "java-oop",
|
||||
@@ -49,13 +54,44 @@ async def seed_courses():
|
||||
"subject": "Cybersecurity",
|
||||
"description": "Learn ethical hacking techniques, penetration testing, and cybersecurity fundamentals to protect systems.",
|
||||
"difficulty": "Advanced",
|
||||
"mentor": "5t4l1n",
|
||||
"video_url": "https://youtu.be/cDnX0vyNTaE?si=ZXNI4hv2HlWN7eCS",
|
||||
"modules": [
|
||||
{
|
||||
"id": "recon",
|
||||
"title": "Reconnaissance and Information Gathering",
|
||||
"lessons": [
|
||||
{"id": "footprinting", "title": "Footprinting Techniques", "type": "text"},
|
||||
{"id": "scanning", "title": "Network Scanning", "type": "code"}
|
||||
{"id": "footprinting", "title": "Footprinting Techniques", "type": "video", "video_url": "https://youtu.be/cDnX0vyNTaE?si=ZXNI4hv2HlWN7eCS"},
|
||||
{"id": "scanning", "title": "Network Scanning", "type": "code"},
|
||||
{"id": "enumeration", "title": "Service Enumeration", "type": "text"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_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",
|
||||
"modules": [
|
||||
{
|
||||
"id": "tor-basics",
|
||||
"title": "Tor Network Fundamentals",
|
||||
"lessons": [
|
||||
{"id": "tor-intro", "title": "Introduction to Tor Network", "type": "video", "video_url": "https://youtu.be/Z4_USAMVhYs?si=Y_ThVisph5ekM44U"},
|
||||
{"id": "onion-services", "title": "Setting Up Onion Services", "type": "code"},
|
||||
{"id": "security-practices", "title": "Security Best Practices", "type": "text"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hosting-setup",
|
||||
"title": "Dark Web Hosting Setup",
|
||||
"lessons": [
|
||||
{"id": "server-config", "title": "Server Configuration", "type": "code"},
|
||||
{"id": "anonymity", "title": "Maintaining Anonymity", "type": "text"}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -63,8 +99,11 @@ async def seed_courses():
|
||||
]
|
||||
|
||||
try:
|
||||
# Clear existing courses first
|
||||
await mongo_service.db.courses.delete_many({})
|
||||
# Insert updated courses
|
||||
await mongo_service.db.courses.insert_many(courses)
|
||||
print("✅ Courses seeded successfully!")
|
||||
print("✅ Courses with mentor and video links seeded successfully!")
|
||||
except Exception as e:
|
||||
print(f"❌ Error seeding courses: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user