mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 11:25:49 +00:00
harini
This commit is contained in:
+3
-1
@@ -7,7 +7,7 @@ from mongo_service import MongoService
|
||||
from web3_service import Web3Service
|
||||
|
||||
# Import all route blueprints
|
||||
from routes import auth, test_flow, certificate, dashboard
|
||||
from routes import auth, test_flow, certificate, dashboard , courses, quizzes
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@@ -34,6 +34,8 @@ app.register_blueprint(auth.bp, url_prefix='/api/auth')
|
||||
app.register_blueprint(test_flow.bp, url_prefix='/api/test')
|
||||
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.route('/')
|
||||
def health_check():
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
from bson import ObjectId
|
||||
from datetime import datetime
|
||||
from pymongo.collection import Collection
|
||||
|
||||
class UserModel:
|
||||
def __init__(self, collection: Collection):
|
||||
self.collection = collection
|
||||
|
||||
async def get_by_wallet(self, wallet_address: str):
|
||||
return await self.collection.find_one({"wallet_address": wallet_address.lower()})
|
||||
|
||||
async def create_user(self, wallet_address: str):
|
||||
now = datetime.utcnow()
|
||||
user = {
|
||||
"wallet_address": wallet_address.lower(),
|
||||
"created_at": now,
|
||||
"last_login": now,
|
||||
"total_tests": 0,
|
||||
"certificates": []
|
||||
}
|
||||
result = await self.collection.insert_one(user)
|
||||
user["_id"] = result.inserted_id
|
||||
return user
|
||||
|
||||
async def update_last_login(self, wallet_address: str):
|
||||
now = datetime.utcnow()
|
||||
await self.collection.update_one(
|
||||
{"wallet_address": wallet_address.lower()},
|
||||
{"$set": {"last_login": now}}
|
||||
)
|
||||
@@ -3,8 +3,10 @@ from pymongo.errors import ServerSelectionTimeoutError
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
|
||||
class MongoService:
|
||||
def __init__(self, uri: str):
|
||||
self.uri = uri # Store URI for sync operations
|
||||
try:
|
||||
# Simple connection without custom SSL context
|
||||
self.client = AsyncIOMotorClient(
|
||||
@@ -18,16 +20,15 @@ class MongoService:
|
||||
print(f"MongoDB connection failed: {e}")
|
||||
# Fallback to basic connection
|
||||
self.client = AsyncIOMotorClient(uri)
|
||||
|
||||
self.db = self.client.openlearnx
|
||||
|
||||
self.db = self.client.openlearnx
|
||||
# Collections
|
||||
self.users = self.db.users
|
||||
self.questions = self.db.questions
|
||||
self.test_sessions = self.db.test_sessions
|
||||
self.certificates = self.db.certificates
|
||||
self.peer_reviews = self.db.peer_reviews
|
||||
|
||||
|
||||
async def init_db(self):
|
||||
"""Initialize database with indexes and sample data"""
|
||||
try:
|
||||
@@ -38,13 +39,10 @@ class MongoService:
|
||||
# Create indexes
|
||||
await self.users.create_index("wallet_address", unique=True)
|
||||
await self.users.create_index("email", unique=True, sparse=True)
|
||||
|
||||
await self.questions.create_index("subject")
|
||||
await self.questions.create_index("difficulty")
|
||||
|
||||
await self.test_sessions.create_index("user_id")
|
||||
await self.test_sessions.create_index("created_at")
|
||||
|
||||
await self.certificates.create_index("user_id")
|
||||
await self.certificates.create_index("token_id", unique=True)
|
||||
|
||||
@@ -60,4 +58,85 @@ class MongoService:
|
||||
print(f"Database initialization error: {e}")
|
||||
print("Continuing without database initialization...")
|
||||
|
||||
# ... rest of your existing methods remain the same
|
||||
async def get_user_by_wallet(self, wallet_address: str):
|
||||
"""Get user by wallet address"""
|
||||
return await self.users.find_one({"wallet_address": wallet_address.lower()})
|
||||
|
||||
async def create_user(self, wallet_address: str):
|
||||
"""Create a new user"""
|
||||
now = datetime.utcnow()
|
||||
user = {
|
||||
"wallet_address": wallet_address.lower(),
|
||||
"created_at": now,
|
||||
"last_login": now,
|
||||
"total_tests": 0,
|
||||
"certificates": []
|
||||
}
|
||||
result = await self.users.insert_one(user)
|
||||
user["_id"] = result.inserted_id
|
||||
return user
|
||||
|
||||
async def update_user_login(self, wallet_address: str):
|
||||
"""Update user's last login time"""
|
||||
await self.users.update_one(
|
||||
{"wallet_address": wallet_address.lower()},
|
||||
{"$set": {"last_login": datetime.utcnow()}}
|
||||
)
|
||||
|
||||
async def insert_sample_questions(self):
|
||||
"""Insert sample questions - implement based on your needs"""
|
||||
# You'll need to implement this method based on your question structure
|
||||
sample_questions = [
|
||||
{
|
||||
"subject": "Python",
|
||||
"difficulty": "beginner",
|
||||
"question": "What is a variable in Python?",
|
||||
"options": ["A storage location", "A function", "A loop", "A condition"],
|
||||
"correct_answer": 0,
|
||||
"created_at": datetime.utcnow()
|
||||
},
|
||||
# Add more sample questions as needed
|
||||
]
|
||||
await self.questions.insert_many(sample_questions)
|
||||
|
||||
async def close_connection(self):
|
||||
"""Close the database connection"""
|
||||
if self.client:
|
||||
self.client.close()
|
||||
print("MongoDB connection closed")
|
||||
|
||||
def create_user_sync(self, wallet_address: str):
|
||||
"""Synchronous user creation using pymongo instead of motor"""
|
||||
import pymongo
|
||||
|
||||
# Create a synchronous connection for this operation only
|
||||
client = pymongo.MongoClient(self.uri)
|
||||
db = client.openlearnx
|
||||
users = db.users
|
||||
|
||||
try:
|
||||
# Check if user exists
|
||||
user = users.find_one({"wallet_address": wallet_address.lower()})
|
||||
|
||||
if not user:
|
||||
# Create new user
|
||||
new_user = {
|
||||
"wallet_address": wallet_address.lower(),
|
||||
"created_at": datetime.utcnow(),
|
||||
"last_login": datetime.utcnow(),
|
||||
"total_tests": 0,
|
||||
"certificates": []
|
||||
}
|
||||
result = users.insert_one(new_user)
|
||||
new_user["_id"] = result.inserted_id
|
||||
return new_user
|
||||
else:
|
||||
# Update last login
|
||||
users.update_one(
|
||||
{"wallet_address": wallet_address.lower()},
|
||||
{"$set": {"last_login": datetime.utcnow()}}
|
||||
)
|
||||
return user
|
||||
finally:
|
||||
# Always close the connection
|
||||
client.close()
|
||||
+70
-51
@@ -1,71 +1,90 @@
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
import jwt
|
||||
from datetime import datetime, timedelta
|
||||
import secrets
|
||||
|
||||
bp = Blueprint('auth', __name__)
|
||||
bp = Blueprint("auth", __name__)
|
||||
|
||||
@bp.route('/nonce', methods=['POST'])
|
||||
# Store nonces temporarily (in production, use Redis or database)
|
||||
nonces = {}
|
||||
|
||||
@bp.route("/nonce", methods=["POST"])
|
||||
def get_nonce():
|
||||
"""Generate nonce for wallet signature"""
|
||||
data = request.get_json()
|
||||
wallet_address = data.get('wallet_address')
|
||||
wallet_address = data.get("wallet_address")
|
||||
|
||||
if not wallet_address:
|
||||
return jsonify({"error": "Wallet address required"}), 400
|
||||
|
||||
web3_service = current_app.config['WEB3_SERVICE']
|
||||
nonce = web3_service.generate_nonce()
|
||||
return jsonify({"error": "wallet_address is required"}), 400
|
||||
|
||||
# Generate nonce
|
||||
nonce = secrets.token_hex(16)
|
||||
message = f"Sign this message to authenticate with OpenLearnX: {nonce}"
|
||||
|
||||
return jsonify({
|
||||
"nonce": nonce,
|
||||
"message": message
|
||||
})
|
||||
# Store nonce for this wallet address
|
||||
nonces[wallet_address.lower()] = nonce
|
||||
|
||||
return jsonify({"nonce": nonce, "message": message})
|
||||
|
||||
@bp.route('/verify', methods=['POST'])
|
||||
async def verify_signature():
|
||||
"""Verify MetaMask signature and create session"""
|
||||
@bp.route("/verify", methods=["POST"])
|
||||
def verify_signature():
|
||||
data = request.get_json()
|
||||
wallet_address = data.get('wallet_address')
|
||||
signature = data.get('signature')
|
||||
message = data.get('message')
|
||||
wallet_address = data.get("wallet_address", "").lower()
|
||||
signature = data.get("signature")
|
||||
message = data.get("message")
|
||||
|
||||
if not all([wallet_address, signature, message]):
|
||||
return jsonify({"error": "Missing required fields"}), 400
|
||||
|
||||
web3_service = current_app.config['WEB3_SERVICE']
|
||||
mongo_service = current_app.config['MONGO_SERVICE']
|
||||
# Verify nonce
|
||||
stored_nonce = nonces.get(wallet_address)
|
||||
if not stored_nonce or stored_nonce not in message:
|
||||
return jsonify({"error": "Invalid nonce"}), 400
|
||||
|
||||
# Verify signature
|
||||
if not web3_service.verify_signature(wallet_address, message, signature):
|
||||
return jsonify({"error": "Invalid signature"}), 401
|
||||
|
||||
# Create or get user
|
||||
user = await mongo_service.create_user(wallet_address)
|
||||
await mongo_service.update_user_login(wallet_address)
|
||||
|
||||
# Create JWT token
|
||||
token_payload = {
|
||||
'user_id': str(user['_id']),
|
||||
'wallet_address': wallet_address,
|
||||
'exp': datetime.utcnow() + timedelta(days=7)
|
||||
}
|
||||
|
||||
token = jwt.encode(
|
||||
token_payload,
|
||||
current_app.config['SECRET_KEY'],
|
||||
algorithm='HS256'
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"token": token,
|
||||
"user": {
|
||||
"id": str(user['_id']),
|
||||
"wallet_address": user['wallet_address'],
|
||||
"created_at": user['created_at'].isoformat(),
|
||||
"total_tests": user.get('total_tests', 0),
|
||||
"certificates": len(user.get('certificates', []))
|
||||
try:
|
||||
web3_service = current_app.config["WEB3_SERVICE"]
|
||||
|
||||
# Verify signature
|
||||
if not web3_service.verify_signature(wallet_address, message, signature):
|
||||
return jsonify({"error": "Invalid signature"}), 401
|
||||
|
||||
# For now, create a mock user without database operations
|
||||
# This bypasses the async MongoDB issues entirely
|
||||
user = {
|
||||
"_id": f"user_{wallet_address}",
|
||||
"wallet_address": wallet_address,
|
||||
"created_at": datetime.utcnow(),
|
||||
"total_tests": 0,
|
||||
"certificates": []
|
||||
}
|
||||
})
|
||||
|
||||
# Create JWT token
|
||||
token_payload = {
|
||||
"user_id": str(user["_id"]),
|
||||
"wallet_address": wallet_address,
|
||||
"exp": datetime.utcnow() + timedelta(days=7)
|
||||
}
|
||||
|
||||
token = jwt.encode(
|
||||
token_payload,
|
||||
current_app.config["SECRET_KEY"],
|
||||
algorithm="HS256"
|
||||
)
|
||||
|
||||
# Clean up nonce
|
||||
if wallet_address in nonces:
|
||||
del nonces[wallet_address]
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"token": token,
|
||||
"user": {
|
||||
"id": str(user["_id"]),
|
||||
"wallet_address": user["wallet_address"],
|
||||
"total_tests": user.get("total_tests", 0),
|
||||
"certificates": len(user.get("certificates", []))
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Authentication error: {str(e)}")
|
||||
return jsonify({"error": "Authentication failed"}), 500
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
from flask import Blueprint, jsonify, request, current_app
|
||||
import requests
|
||||
from bson import ObjectId
|
||||
from datetime import datetime
|
||||
|
||||
bp = Blueprint('coding', __name__)
|
||||
PISTON_API_URL = "https://emkc.org/api/v2/piston/execute"
|
||||
|
||||
@bp.route("/problems", methods=["GET"])
|
||||
async def get_problems():
|
||||
mongo = current_app.config['MONGO_SERVICE']
|
||||
problems = await mongo.db.coding_problems.find().to_list(100)
|
||||
for p in problems:
|
||||
p['_id'] = str(p['_id'])
|
||||
return jsonify(problems)
|
||||
|
||||
@bp.route("/problems/<problem_id>", methods=["GET"])
|
||||
async def get_problem(problem_id):
|
||||
mongo = current_app.config['MONGO_SERVICE']
|
||||
prob = await mongo.db.coding_problems.find_one({"_id": ObjectId(problem_id)})
|
||||
if not prob:
|
||||
return jsonify({"error": "Problem not found"}), 404
|
||||
prob['_id'] = str(prob['_id'])
|
||||
return jsonify(prob)
|
||||
|
||||
@bp.route("/run", methods=["POST"])
|
||||
async def run_code():
|
||||
data = request.json
|
||||
problem_id = data.get("problem_id")
|
||||
code = data.get("code")
|
||||
language = data.get("language")
|
||||
|
||||
mongo = current_app.config['MONGO_SERVICE']
|
||||
problem = await mongo.db.coding_problems.find_one({"_id": ObjectId(problem_id)})
|
||||
if not problem:
|
||||
return jsonify({"error": "Problem not found"}), 404
|
||||
|
||||
# Concatenate all test case inputs
|
||||
input_data = '\n'.join([tc['input'] for tc in problem['test_cases']])
|
||||
|
||||
try:
|
||||
resp = requests.post(
|
||||
PISTON_API_URL,
|
||||
json={
|
||||
"language": language,
|
||||
"source": code,
|
||||
"input": input_data
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
result = resp.json()
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
# Compare output against expected (simple line-by-line check)
|
||||
output_lines = result.get("output", "").strip().split('\n')
|
||||
expected_outputs = [tc['expected_output'].strip() for tc in problem['test_cases']]
|
||||
correct = output_lines == expected_outputs
|
||||
|
||||
return jsonify({
|
||||
"output": result.get("output"),
|
||||
"error": result.get("stderr"),
|
||||
"runtime": result.get("stats", {}).get("duration"),
|
||||
"correct": correct,
|
||||
})
|
||||
|
||||
@bp.route("/submit", methods=["POST"])
|
||||
async def submit_solution():
|
||||
# Same as run_code, but can mark problem as solved
|
||||
user = await get_authenticated_user()
|
||||
if not user:
|
||||
return jsonify({"error": "Unauthorized"}), 401
|
||||
|
||||
# Run the code first
|
||||
result = await run_code()
|
||||
jres = result.get_json()
|
||||
|
||||
if jres.get("correct"):
|
||||
mongo = current_app.config['MONGO_SERVICE']
|
||||
# Record that user solved problem
|
||||
await mongo.db.user_solutions.update_one(
|
||||
{"user_id": user['_id'], "problem_id": jres.get('problem_id')},
|
||||
{"$set": {"solved": True, "solved_at": datetime.utcnow()}},
|
||||
upsert=True
|
||||
)
|
||||
return jsonify(jres)
|
||||
``
|
||||
@@ -0,0 +1,43 @@
|
||||
from flask import Blueprint, jsonify, current_app
|
||||
import asyncio
|
||||
from bson import ObjectId
|
||||
|
||||
bp = Blueprint('courses', __name__)
|
||||
|
||||
# Remove trailing slash from route definition
|
||||
@bp.route("/", methods=["GET"])
|
||||
@bp.route("", methods=["GET"]) # Add this line to handle both cases
|
||||
def list_courses():
|
||||
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
|
||||
}
|
||||
]
|
||||
return jsonify(courses)
|
||||
except Exception as e:
|
||||
print(f"Error in list_courses: {e}")
|
||||
return jsonify({"error": "Failed to fetch courses"}), 500
|
||||
@@ -0,0 +1,32 @@
|
||||
from flask import Blueprint, jsonify
|
||||
|
||||
bp = Blueprint('quizzes', __name__)
|
||||
|
||||
# Handle both with and without trailing slash
|
||||
@bp.route("/", methods=["GET"])
|
||||
@bp.route("", methods=["GET"]) # Add this line
|
||||
def list_quizzes():
|
||||
quizzes = [
|
||||
{
|
||||
"id": "python-quiz",
|
||||
"title": "Python Fundamentals Quiz",
|
||||
"topic": "Programming",
|
||||
"difficulty": "Easy",
|
||||
"recent_performance": 85
|
||||
},
|
||||
{
|
||||
"id": "java-quiz",
|
||||
"title": "Java OOP Concepts Quiz",
|
||||
"topic": "Programming",
|
||||
"difficulty": "Medium",
|
||||
"recent_performance": 78
|
||||
},
|
||||
{
|
||||
"id": "security-quiz",
|
||||
"title": "Cybersecurity Basics Quiz",
|
||||
"topic": "Security",
|
||||
"difficulty": "Hard",
|
||||
"recent_performance": 72
|
||||
}
|
||||
]
|
||||
return jsonify(quizzes)
|
||||
@@ -0,0 +1,72 @@
|
||||
import asyncio
|
||||
from mongo_service import MongoService
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
async def seed_courses():
|
||||
mongo_service = MongoService(os.getenv('MONGODB_URI'))
|
||||
|
||||
courses = [
|
||||
{
|
||||
"_id": "python-course",
|
||||
"title": "Python Programming Mastery",
|
||||
"subject": "Programming",
|
||||
"description": "Learn Python from basics to advanced concepts including web development, data science, and automation.",
|
||||
"difficulty": "Beginner to Advanced",
|
||||
"modules": [
|
||||
{
|
||||
"id": "python-basics",
|
||||
"title": "Python Fundamentals",
|
||||
"lessons": [
|
||||
{"id": "variables", "title": "Variables and Data Types", "type": "text"},
|
||||
{"id": "functions", "title": "Functions and Modules", "type": "code"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "java-course",
|
||||
"title": "Java Development Bootcamp",
|
||||
"subject": "Programming",
|
||||
"description": "Master Java programming with object-oriented concepts, Spring framework, and enterprise development.",
|
||||
"difficulty": "Intermediate",
|
||||
"modules": [
|
||||
{
|
||||
"id": "java-oop",
|
||||
"title": "Object-Oriented Programming in Java",
|
||||
"lessons": [
|
||||
{"id": "classes", "title": "Classes and Objects", "type": "code"},
|
||||
{"id": "inheritance", "title": "Inheritance and Polymorphism", "type": "text"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "ethical-hacking-course",
|
||||
"title": "Ethical Hacking & Cybersecurity",
|
||||
"subject": "Cybersecurity",
|
||||
"description": "Learn ethical hacking techniques, penetration testing, and cybersecurity fundamentals to protect systems.",
|
||||
"difficulty": "Advanced",
|
||||
"modules": [
|
||||
{
|
||||
"id": "recon",
|
||||
"title": "Reconnaissance and Information Gathering",
|
||||
"lessons": [
|
||||
{"id": "footprinting", "title": "Footprinting Techniques", "type": "text"},
|
||||
{"id": "scanning", "title": "Network Scanning", "type": "code"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
try:
|
||||
await mongo_service.db.courses.insert_many(courses)
|
||||
print("✅ Courses seeded successfully!")
|
||||
except Exception as e:
|
||||
print(f"❌ Error seeding courses: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed_courses())
|
||||
@@ -0,0 +1,8 @@
|
||||
def choose_next_question(current_difficulty: int, last_answer_correct: bool) -> int:
|
||||
"""
|
||||
Simplified adaptive engine logic adjusting difficulty for next question.
|
||||
"""
|
||||
if last_answer_correct:
|
||||
return min(current_difficulty + 1, 3) # max difficulty = 3
|
||||
else:
|
||||
return max(current_difficulty - 1, 1) # min difficulty = 1
|
||||
Reference in New Issue
Block a user