from flask import Flask, request, jsonify, session from flask_cors import CORS from werkzeug.security import generate_password_hash, check_password_hash import json import os from datetime import datetime app = Flask(__name__) app.secret_key = 'your-secret-key-here-change-in-production' # needed for sessions CORS(app, supports_credentials=True) # allow cookies # Directories DATA_DIR = 'data' USERS_FILE = os.path.join(DATA_DIR, 'users.json') os.makedirs(DATA_DIR, exist_ok=True) # ==================== User helpers ==================== def load_users(): try: if os.path.exists(USERS_FILE): with open(USERS_FILE, 'r') as f: return json.load(f) except Exception as e: print(f"Error loading users: {e}") return [] def save_users(users): try: with open(USERS_FILE, 'w') as f: json.dump(users, f, indent=2) except Exception as e: print(f"Error saving users: {e}") def get_next_user_id(users): if not users: return 1 return max(u['id'] for u in users) + 1 def get_user_by_username(username): users = load_users() return next((u for u in users if u['username'] == username), None) def get_user_by_id(user_id): users = load_users() return next((u for u in users if u['id'] == user_id), None) # ==================== Per‑user data helpers ==================== def get_user_data_dir(user_id): user_dir = os.path.join(DATA_DIR, 'users', str(user_id)) os.makedirs(user_dir, exist_ok=True) return user_dir def load_user_journeys(user_id): file_path = os.path.join(get_user_data_dir(user_id), 'journeys.json') try: if os.path.exists(file_path): with open(file_path, 'r') as f: return json.load(f) except Exception as e: print(f"Error loading journeys for user {user_id}: {e}") return [] def save_user_journeys(user_id, journeys): file_path = os.path.join(get_user_data_dir(user_id), 'journeys.json') try: with open(file_path, 'w') as f: json.dump(journeys, f, indent=2) except Exception as e: print(f"Error saving journeys for user {user_id}: {e}") def load_user_posts(user_id): file_path = os.path.join(get_user_data_dir(user_id), 'posts.json') try: if os.path.exists(file_path): with open(file_path, 'r') as f: return json.load(f) except Exception as e: print(f"Error loading posts for user {user_id}: {e}") return [] def save_user_posts(user_id, posts): file_path = os.path.join(get_user_data_dir(user_id), 'posts.json') try: with open(file_path, 'w') as f: json.dump(posts, f, indent=2) except Exception as e: print(f"Error saving posts for user {user_id}: {e}") # ==================== Authentication endpoints ==================== @app.route('/api/register', methods=['POST']) def register(): data = request.get_json() username = data.get('username') password = data.get('password') if not username or not password: return jsonify({'error': 'Username and password required'}), 400 # Check if username already exists if get_user_by_username(username): return jsonify({'error': 'Username already taken'}), 409 users = load_users() new_id = get_next_user_id(users) hashed = generate_password_hash(password) new_user = { 'id': new_id, 'username': username, 'password_hash': hashed, 'created_at': datetime.now().isoformat() } users.append(new_user) save_users(users) # Create empty data files for the new user save_user_journeys(new_id, []) save_user_posts(new_id, []) # Log the user in automatically session['user_id'] = new_id return jsonify({ 'id': new_id, 'username': username, 'message': 'Registration successful' }), 201 @app.route('/api/login', methods=['POST']) def login(): data = request.get_json() username = data.get('username') password = data.get('password') user = get_user_by_username(username) if not user or not check_password_hash(user['password_hash'], password): return jsonify({'error': 'Invalid username or password'}), 401 session['user_id'] = user['id'] return jsonify({ 'id': user['id'], 'username': user['username'], 'message': 'Login successful' }) @app.route('/api/logout', methods=['POST']) def logout(): session.pop('user_id', None) return jsonify({'message': 'Logged out'}) @app.route('/api/me', methods=['GET']) def me(): user_id = session.get('user_id') if not user_id: return jsonify({'error': 'Not logged in'}), 401 user = get_user_by_id(user_id) if not user: # Should not happen, but clean session session.pop('user_id', None) return jsonify({'error': 'User not found'}), 401 return jsonify({ 'id': user['id'], 'username': user['username'] }) # ==================== Journey endpoints (protected, user‑specific) ==================== def require_login(): if 'user_id' not in session: return False return True def get_current_user_id(): return session.get('user_id') def get_journeys_for_current_user(): user_id = get_current_user_id() if user_id is None: return None return load_user_journeys(user_id) @app.route('/api/journeys', methods=['GET']) def get_journeys(): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 journeys = get_journeys_for_current_user() return jsonify(journeys) def get_next_journey_id(journeys): if not journeys: return 1 return max(j['id'] for j in journeys) + 1 @app.route('/api/journeys', methods=['POST']) def create_journey(): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 data = request.get_json() if not data: return jsonify({'error': 'No data provided'}), 400 title = data.get('title') if not title: return jsonify({'error': 'Journey title is required'}), 400 user_id = get_current_user_id() journeys = get_journeys_for_current_user() new_id = get_next_journey_id(journeys) new_journey = { 'id': new_id, 'title': title, 'description': data.get('description', ''), 'markers': data.get('markers', []), 'created_at': datetime.now().isoformat() } journeys.append(new_journey) save_user_journeys(user_id, journeys) return jsonify(new_journey), 201 @app.route('/api/journeys/', methods=['GET']) def get_journey(journey_id): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 journeys = get_journeys_for_current_user() journey = next((j for j in journeys if j['id'] == journey_id), None) if journey is None: return jsonify({'error': 'Journey not found'}), 404 return jsonify(journey) @app.route('/api/journeys/', methods=['PUT']) def update_journey(journey_id): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 journeys = get_journeys_for_current_user() journey = next((j for j in journeys if j['id'] == journey_id), None) if journey is None: return jsonify({'error': 'Journey not found'}), 404 data = request.get_json() if not data: return jsonify({'error': 'No data provided'}), 400 if 'title' in data: journey['title'] = data['title'] if 'description' in data: journey['description'] = data['description'] if 'markers' in data: journey['markers'] = data['markers'] save_user_journeys(get_current_user_id(), journeys) return jsonify(journey) @app.route('/api/journeys/', methods=['DELETE']) def delete_journey(journey_id): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 journeys = get_journeys_for_current_user() journey = next((j for j in journeys if j['id'] == journey_id), None) if journey is None: return jsonify({'error': 'Journey not found'}), 404 journeys = [j for j in journeys if j['id'] != journey_id] save_user_journeys(get_current_user_id(), journeys) return jsonify({'message': 'Journey deleted successfully', 'journey': journey}) # ==================== Blog Post endpoints (protected, user‑specific) ==================== def get_posts_for_current_user(): user_id = get_current_user_id() if user_id is None: return None return load_user_posts(user_id) def get_next_post_id(posts): if not posts: return 1 return max(p['id'] for p in posts) + 1 @app.route('/api/blog-posts', methods=['GET']) def get_blog_posts(): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 posts = get_posts_for_current_user() return jsonify(posts) @app.route('/api/blog-posts/', methods=['GET']) def get_blog_post(post_id): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 posts = get_posts_for_current_user() post = next((p for p in posts if p['id'] == post_id), None) if not post: return jsonify({'error': 'Post not found'}), 404 return jsonify(post) @app.route('/api/blog-posts', methods=['POST']) def create_blog_post(): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 data = request.get_json() title = data.get('title') if not title: return jsonify({'error': 'Title required'}), 400 user_id = get_current_user_id() posts = get_posts_for_current_user() new_id = get_next_post_id(posts) new_post = { 'id': new_id, 'title': title, 'content': data.get('content', ''), 'journeyId': data.get('journeyId'), 'image': data.get('image'), 'created_at': datetime.now().isoformat() } posts.append(new_post) save_user_posts(user_id, posts) return jsonify(new_post), 201 @app.route('/api/blog-posts/', methods=['PUT']) def update_blog_post(post_id): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 posts = get_posts_for_current_user() post = next((p for p in posts if p['id'] == post_id), None) if not post: return jsonify({'error': 'Post not found'}), 404 data = request.get_json() if 'title' in data: post['title'] = data['title'] if 'content' in data: post['content'] = data['content'] if 'journeyId' in data: post['journeyId'] = data['journeyId'] if 'image' in data: post['image'] = data['image'] save_user_posts(get_current_user_id(), posts) return jsonify(post) @app.route('/api/blog-posts/', methods=['DELETE']) def delete_blog_post(post_id): if not require_login(): return jsonify({'error': 'Authentication required'}), 401 posts = get_posts_for_current_user() post = next((p for p in posts if p['id'] == post_id), None) if not post: return jsonify({'error': 'Post not found'}), 404 posts = [p for p in posts if p['id'] != post_id] save_user_posts(get_current_user_id(), posts) return jsonify({'message': 'Post deleted'}) # ==================== Health and root ==================== @app.route('/api/journeys/health', methods=['GET']) def health_check(): return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()}) @app.route('/') def index(): return ''' Journey Mapper API

Journey Mapper API

Backend is running. Use /api/journeys endpoints.

Authentication required: register, login, then use session cookies.

''' if __name__ == '__main__': app.run(debug=True, port=5000)