forked from cleveragents/cleveragents-core
9fe3196827
All sims re-run after pipeline fixes (better prompts, real syntax validation, fenced-block extraction fix). Results: 7/7 PASS, 20 files total, all Python files pass compile() syntax check. Includes run_all_sims.py runner script and rag-basic action config.
361 lines
11 KiB
Python
361 lines
11 KiB
Python
from flask import Flask, request, jsonify
|
|
from config import Config
|
|
from database import init_database
|
|
from models import db, User
|
|
from auth import (
|
|
generate_jwt_token,
|
|
token_required,
|
|
validate_email,
|
|
validate_username,
|
|
validate_password_strength
|
|
)
|
|
import traceback
|
|
|
|
app = Flask(__name__)
|
|
app.config.from_object(Config)
|
|
|
|
# Initialize database
|
|
init_database(app)
|
|
|
|
@app.errorhandler(404)
|
|
def not_found(error):
|
|
return jsonify({
|
|
'error': 'Not found',
|
|
'message': 'The requested resource was not found'
|
|
}), 404
|
|
|
|
@app.errorhandler(500)
|
|
def internal_error(error):
|
|
db.session.rollback()
|
|
return jsonify({
|
|
'error': 'Internal server error',
|
|
'message': 'An unexpected error occurred'
|
|
}), 500
|
|
|
|
@app.route('/health', methods=['GET'])
|
|
def health_check():
|
|
"""Health check endpoint."""
|
|
return jsonify({
|
|
'status': 'healthy',
|
|
'message': 'Application is running'
|
|
}), 200
|
|
|
|
@app.route('/api/register', methods=['POST'])
|
|
def register():
|
|
"""User registration endpoint."""
|
|
try:
|
|
data = request.get_json()
|
|
|
|
if not data:
|
|
return jsonify({
|
|
'error': 'Invalid input',
|
|
'message': 'JSON data is required'
|
|
}), 400
|
|
|
|
username = data.get('username', '').strip()
|
|
email = data.get('email', '').strip().lower()
|
|
password = data.get('password', '')
|
|
|
|
# Validate required fields
|
|
if not username or not email or not password:
|
|
return jsonify({
|
|
'error': 'Missing required fields',
|
|
'message': 'Username, email, and password are required'
|
|
}), 400
|
|
|
|
# Validate username format
|
|
if not validate_username(username):
|
|
return jsonify({
|
|
'error': 'Invalid username',
|
|
'message': 'Username must be 3-80 characters and contain only letters, numbers, and underscores'
|
|
}), 400
|
|
|
|
# Validate email format
|
|
if not validate_email(email):
|
|
return jsonify({
|
|
'error': 'Invalid email',
|
|
'message': 'Please provide a valid email address'
|
|
}), 400
|
|
|
|
# Validate password strength
|
|
is_valid, password_message = validate_password_strength(password)
|
|
if not is_valid:
|
|
return jsonify({
|
|
'error': 'Weak password',
|
|
'message': password_message
|
|
}), 400
|
|
|
|
# Check if user already exists
|
|
existing_user = User.query.filter(
|
|
(User.username == username) | (User.email == email)
|
|
).first()
|
|
|
|
if existing_user:
|
|
if existing_user.username == username:
|
|
return jsonify({
|
|
'error': 'Username taken',
|
|
'message': 'Username already exists'
|
|
}), 409
|
|
else:
|
|
return jsonify({
|
|
'error': 'Email taken',
|
|
'message': 'Email already registered'
|
|
}), 409
|
|
|
|
# Create new user
|
|
new_user = User(
|
|
username=username,
|
|
email=email,
|
|
password=password
|
|
)
|
|
|
|
db.session.add(new_user)
|
|
db.session.commit()
|
|
|
|
# Generate JWT token
|
|
token = generate_jwt_token(new_user.id)
|
|
|
|
return jsonify({
|
|
'message': 'User registered successfully',
|
|
'user': new_user.to_dict(),
|
|
'token': token
|
|
}), 201
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
app.logger.error(f"Registration error: {str(e)}")
|
|
app.logger.error(traceback.format_exc())
|
|
return jsonify({
|
|
'error': 'Registration failed',
|
|
'message': 'An error occurred during registration'
|
|
}), 500
|
|
|
|
@app.route('/api/login', methods=['POST'])
|
|
def login():
|
|
"""User login endpoint."""
|
|
try:
|
|
data = request.get_json()
|
|
|
|
if not data:
|
|
return jsonify({
|
|
'error': 'Invalid input',
|
|
'message': 'JSON data is required'
|
|
}), 400
|
|
|
|
login_identifier = data.get('username') or data.get('email', '')
|
|
password = data.get('password', '')
|
|
|
|
if not login_identifier or not password:
|
|
return jsonify({
|
|
'error': 'Missing credentials',
|
|
'message': 'Username/email and password are required'
|
|
}), 400
|
|
|
|
login_identifier = login_identifier.strip().lower()
|
|
|
|
# Find user by username or email
|
|
user = User.query.filter(
|
|
(User.username == login_identifier) | (User.email == login_identifier)
|
|
).first()
|
|
|
|
if not user or not user.check_password(password):
|
|
return jsonify({
|
|
'error': 'Invalid credentials',
|
|
'message': 'Username/email or password is incorrect'
|
|
}), 401
|
|
|
|
if not user.is_active:
|
|
return jsonify({
|
|
'error': 'Account disabled',
|
|
'message': 'Your account has been disabled'
|
|
}), 401
|
|
|
|
# Generate JWT token
|
|
token = generate_jwt_token(user.id)
|
|
|
|
return jsonify({
|
|
'message': 'Login successful',
|
|
'user': user.to_dict(),
|
|
'token': token
|
|
}), 200
|
|
|
|
except Exception as e:
|
|
app.logger.error(f"Login error: {str(e)}")
|
|
app.logger.error(traceback.format_exc())
|
|
return jsonify({
|
|
'error': 'Login failed',
|
|
'message': 'An error occurred during login'
|
|
}), 500
|
|
|
|
@app.route('/api/profile', methods=['GET'])
|
|
@token_required
|
|
def get_profile(current_user):
|
|
"""Get current user profile (protected endpoint)."""
|
|
return jsonify({
|
|
'message': 'Profile retrieved successfully',
|
|
'user': current_user.to_dict()
|
|
}), 200
|
|
|
|
@app.route('/api/profile', methods=['PUT'])
|
|
@token_required
|
|
def update_profile(current_user):
|
|
"""Update current user profile (protected endpoint)."""
|
|
try:
|
|
data = request.get_json()
|
|
|
|
if not data:
|
|
return jsonify({
|
|
'error': 'Invalid input',
|
|
'message': 'JSON data is required'
|
|
}), 400
|
|
|
|
username = data.get('username')
|
|
email = data.get('email')
|
|
|
|
# Update username if provided
|
|
if username is not None:
|
|
username = username.strip()
|
|
if not validate_username(username):
|
|
return jsonify({
|
|
'error': 'Invalid username',
|
|
'message': 'Username must be 3-80 characters and contain only letters, numbers, and underscores'
|
|
}), 400
|
|
|
|
# Check if username is already taken by another user
|
|
existing_user = User.query.filter(
|
|
User.username == username,
|
|
User.id != current_user.id
|
|
).first()
|
|
|
|
if existing_user:
|
|
return jsonify({
|
|
'error': 'Username taken',
|
|
'message': 'Username already exists'
|
|
}), 409
|
|
|
|
current_user.username = username
|
|
|
|
# Update email if provided
|
|
if email is not None:
|
|
email = email.strip().lower()
|
|
if not validate_email(email):
|
|
return jsonify({
|
|
'error': 'Invalid email',
|
|
'message': 'Please provide a valid email address'
|
|
}), 400
|
|
|
|
# Check if email is already taken by another user
|
|
existing_user = User.query.filter(
|
|
User.email == email,
|
|
User.id != current_user.id
|
|
).first()
|
|
|
|
if existing_user:
|
|
return jsonify({
|
|
'error': 'Email taken',
|
|
'message': 'Email already registered'
|
|
}), 409
|
|
|
|
current_user.email = email
|
|
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'Profile updated successfully',
|
|
'user': current_user.to_dict()
|
|
}), 200
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
app.logger.error(f"Profile update error: {str(e)}")
|
|
app.logger.error(traceback.format_exc())
|
|
return jsonify({
|
|
'error': 'Update failed',
|
|
'message': 'An error occurred while updating profile'
|
|
}), 500
|
|
|
|
@app.route('/api/change-password', methods=['POST'])
|
|
@token_required
|
|
def change_password(current_user):
|
|
"""Change user password (protected endpoint)."""
|
|
try:
|
|
data = request.get_json()
|
|
|
|
if not data:
|
|
return jsonify({
|
|
'error': 'Invalid input',
|
|
'message': 'JSON data is required'
|
|
}), 400
|
|
|
|
current_password = data.get('current_password', '')
|
|
new_password = data.get('new_password', '')
|
|
|
|
if not current_password or not new_password:
|
|
return jsonify({
|
|
'error': 'Missing password fields',
|
|
'message': 'Current password and new password are required'
|
|
}), 400
|
|
|
|
# Verify current password
|
|
if not current_user.check_password(current_password):
|
|
return jsonify({
|
|
'error': 'Invalid current password',
|
|
'message': 'Current password is incorrect'
|
|
}), 401
|
|
|
|
# Validate new password strength
|
|
is_valid, password_message = validate_password_strength(new_password)
|
|
if not is_valid:
|
|
return jsonify({
|
|
'error': 'Weak password',
|
|
'message': password_message
|
|
}), 400
|
|
|
|
# Check if new password is different from current
|
|
if current_user.check_password(new_password):
|
|
return jsonify({
|
|
'error': 'Same password',
|
|
'message': 'New password must be different from current password'
|
|
}), 400
|
|
|
|
# Update password
|
|
current_user.set_password(new_password)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'Password changed successfully'
|
|
}), 200
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
app.logger.error(f"Password change error: {str(e)}")
|
|
app.logger.error(traceback.format_exc())
|
|
return jsonify({
|
|
'error': 'Password change failed',
|
|
'message': 'An error occurred while changing password'
|
|
}), 500
|
|
|
|
@app.route('/api/users', methods=['GET'])
|
|
@token_required
|
|
def get_users(current_user):
|
|
"""Get all users (protected endpoint)."""
|
|
try:
|
|
users = User.query.filter_by(is_active=True).all()
|
|
users_data = [user.to_dict() for user in users]
|
|
|
|
return jsonify({
|
|
'message': 'Users retrieved successfully',
|
|
'users': users_data,
|
|
'count': len(users_data)
|
|
}), 200
|
|
|
|
except Exception as e:
|
|
app.logger.error(f"Get users error: {str(e)}")
|
|
app.logger.error(traceback.format_exc())
|
|
return jsonify({
|
|
'error': 'Failed to retrieve users',
|
|
'message': 'An error occurred while fetching users'
|
|
}), 500
|
|
|
|
if __name__ == '__main__':
|
|
app.run(debug=True, host='0.0.0.0', port=5000) |