Moved authentification code into js/auth.js
This commit is contained in:
parent
fec4f513c8
commit
e3724a4842
290
backend/app.py
290
backend/app.py
@ -6,109 +6,120 @@ import os
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = 'your-secret-key-here-change-in-production' # needed for sessions
|
app.secret_key = "your-secret-key-here-change-in-production" # needed for sessions
|
||||||
CORS(app, supports_credentials=True) # allow cookies
|
CORS(app, supports_credentials=True) # allow cookies
|
||||||
|
|
||||||
# Directories
|
# Directories
|
||||||
DATA_DIR = 'data'
|
DATA_DIR = "data"
|
||||||
USERS_FILE = os.path.join(DATA_DIR, 'users.json')
|
USERS_FILE = os.path.join(DATA_DIR, "users.json")
|
||||||
os.makedirs(DATA_DIR, exist_ok=True)
|
os.makedirs(DATA_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
# ==================== User helpers ====================
|
# ==================== User helpers ====================
|
||||||
def load_users():
|
def load_users():
|
||||||
try:
|
try:
|
||||||
if os.path.exists(USERS_FILE):
|
if os.path.exists(USERS_FILE):
|
||||||
with open(USERS_FILE, 'r') as f:
|
with open(USERS_FILE, "r") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading users: {e}")
|
print(f"Error loading users: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def save_users(users):
|
def save_users(users):
|
||||||
try:
|
try:
|
||||||
with open(USERS_FILE, 'w') as f:
|
with open(USERS_FILE, "w") as f:
|
||||||
json.dump(users, f, indent=2)
|
json.dump(users, f, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error saving users: {e}")
|
print(f"Error saving users: {e}")
|
||||||
|
|
||||||
|
|
||||||
def get_next_user_id(users):
|
def get_next_user_id(users):
|
||||||
if not users:
|
if not users:
|
||||||
return 1
|
return 1
|
||||||
return max(u['id'] for u in users) + 1
|
return max(u["id"] for u in users) + 1
|
||||||
|
|
||||||
|
|
||||||
def get_user_by_username(username):
|
def get_user_by_username(username):
|
||||||
users = load_users()
|
users = load_users()
|
||||||
return next((u for u in users if u['username'] == username), None)
|
return next((u for u in users if u["username"] == username), None)
|
||||||
|
|
||||||
|
|
||||||
def get_user_by_id(user_id):
|
def get_user_by_id(user_id):
|
||||||
users = load_users()
|
users = load_users()
|
||||||
return next((u for u in users if u['id'] == user_id), None)
|
return next((u for u in users if u["id"] == user_id), None)
|
||||||
|
|
||||||
|
|
||||||
# ==================== Per‑user data helpers ====================
|
# ==================== Per‑user data helpers ====================
|
||||||
def get_user_data_dir(user_id):
|
def get_user_data_dir(user_id):
|
||||||
user_dir = os.path.join(DATA_DIR, 'users', str(user_id))
|
user_dir = os.path.join(DATA_DIR, "users", str(user_id))
|
||||||
os.makedirs(user_dir, exist_ok=True)
|
os.makedirs(user_dir, exist_ok=True)
|
||||||
return user_dir
|
return user_dir
|
||||||
|
|
||||||
|
|
||||||
def load_user_journeys(user_id):
|
def load_user_journeys(user_id):
|
||||||
file_path = os.path.join(get_user_data_dir(user_id), 'journeys.json')
|
file_path = os.path.join(get_user_data_dir(user_id), "journeys.json")
|
||||||
try:
|
try:
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
with open(file_path, 'r') as f:
|
with open(file_path, "r") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading journeys for user {user_id}: {e}")
|
print(f"Error loading journeys for user {user_id}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def save_user_journeys(user_id, journeys):
|
def save_user_journeys(user_id, journeys):
|
||||||
file_path = os.path.join(get_user_data_dir(user_id), 'journeys.json')
|
file_path = os.path.join(get_user_data_dir(user_id), "journeys.json")
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'w') as f:
|
with open(file_path, "w") as f:
|
||||||
json.dump(journeys, f, indent=2)
|
json.dump(journeys, f, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error saving journeys for user {user_id}: {e}")
|
print(f"Error saving journeys for user {user_id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def load_user_posts(user_id):
|
def load_user_posts(user_id):
|
||||||
file_path = os.path.join(get_user_data_dir(user_id), 'posts.json')
|
file_path = os.path.join(get_user_data_dir(user_id), "posts.json")
|
||||||
try:
|
try:
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
with open(file_path, 'r') as f:
|
with open(file_path, "r") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading posts for user {user_id}: {e}")
|
print(f"Error loading posts for user {user_id}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def save_user_posts(user_id, posts):
|
def save_user_posts(user_id, posts):
|
||||||
file_path = os.path.join(get_user_data_dir(user_id), 'posts.json')
|
file_path = os.path.join(get_user_data_dir(user_id), "posts.json")
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'w') as f:
|
with open(file_path, "w") as f:
|
||||||
json.dump(posts, f, indent=2)
|
json.dump(posts, f, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error saving posts for user {user_id}: {e}")
|
print(f"Error saving posts for user {user_id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
# ==================== Authentication endpoints ====================
|
# ==================== Authentication endpoints ====================
|
||||||
@app.route('/api/register', methods=['POST'])
|
@app.route("/api/register", methods=["POST"])
|
||||||
def register():
|
def register():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
username = data.get('username')
|
username = data.get("username")
|
||||||
password = data.get('password')
|
password = data.get("password")
|
||||||
|
|
||||||
if not username or not password:
|
if not username or not password:
|
||||||
return jsonify({'error': 'Username and password required'}), 400
|
return jsonify({"error": "Username and password required"}), 400
|
||||||
|
|
||||||
# Check if username already exists
|
# Check if username already exists
|
||||||
if get_user_by_username(username):
|
if get_user_by_username(username):
|
||||||
return jsonify({'error': 'Username already taken'}), 409
|
return jsonify({"error": "Username already taken"}), 409
|
||||||
|
|
||||||
users = load_users()
|
users = load_users()
|
||||||
new_id = get_next_user_id(users)
|
new_id = get_next_user_id(users)
|
||||||
|
|
||||||
hashed = generate_password_hash(password)
|
hashed = generate_password_hash(password)
|
||||||
new_user = {
|
new_user = {
|
||||||
'id': new_id,
|
"id": new_id,
|
||||||
'username': username,
|
"username": username,
|
||||||
'password_hash': hashed,
|
"password_hash": hashed,
|
||||||
'created_at': datetime.now().isoformat()
|
"created_at": datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
users.append(new_user)
|
users.append(new_user)
|
||||||
save_users(users)
|
save_users(users)
|
||||||
@ -118,59 +129,58 @@ def register():
|
|||||||
save_user_posts(new_id, [])
|
save_user_posts(new_id, [])
|
||||||
|
|
||||||
# Log the user in automatically
|
# Log the user in automatically
|
||||||
session['user_id'] = new_id
|
session["user_id"] = new_id
|
||||||
|
|
||||||
return jsonify({
|
return jsonify(
|
||||||
'id': new_id,
|
{"id": new_id, "username": username, "message": "Registration successful"}
|
||||||
'username': username,
|
), 201
|
||||||
'message': 'Registration successful'
|
|
||||||
}), 201
|
|
||||||
|
|
||||||
@app.route('/api/login', methods=['POST'])
|
|
||||||
|
@app.route("/api/login", methods=["POST"])
|
||||||
def login():
|
def login():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
username = data.get('username')
|
username = data.get("username")
|
||||||
password = data.get('password')
|
password = data.get("password")
|
||||||
|
|
||||||
user = get_user_by_username(username)
|
user = get_user_by_username(username)
|
||||||
if not user or not check_password_hash(user['password_hash'], password):
|
if not user or not check_password_hash(user["password_hash"], password):
|
||||||
return jsonify({'error': 'Invalid username or password'}), 401
|
return jsonify({"error": "Invalid username or password"}), 401
|
||||||
|
|
||||||
session['user_id'] = user['id']
|
session["user_id"] = user["id"]
|
||||||
return jsonify({
|
return jsonify(
|
||||||
'id': user['id'],
|
{"id": user["id"], "username": user["username"], "message": "Login successful"}
|
||||||
'username': user['username'],
|
)
|
||||||
'message': 'Login successful'
|
|
||||||
})
|
|
||||||
|
|
||||||
@app.route('/api/logout', methods=['POST'])
|
|
||||||
|
@app.route("/api/logout", methods=["POST"])
|
||||||
def logout():
|
def logout():
|
||||||
session.pop('user_id', None)
|
session.pop("user_id", None)
|
||||||
return jsonify({'message': 'Logged out'})
|
return jsonify({"message": "Logged out"})
|
||||||
|
|
||||||
@app.route('/api/me', methods=['GET'])
|
|
||||||
|
@app.route("/api/me", methods=["GET"])
|
||||||
def me():
|
def me():
|
||||||
user_id = session.get('user_id')
|
user_id = session.get("user_id")
|
||||||
if not user_id:
|
if not user_id:
|
||||||
return jsonify({'error': 'Not logged in'}), 401
|
return jsonify({"error": "Not logged in"}), 401
|
||||||
user = get_user_by_id(user_id)
|
user = get_user_by_id(user_id)
|
||||||
if not user:
|
if not user:
|
||||||
# Should not happen, but clean session
|
# Should not happen, but clean session
|
||||||
session.pop('user_id', None)
|
session.pop("user_id", None)
|
||||||
return jsonify({'error': 'User not found'}), 401
|
return jsonify({"error": "User not found"}), 401
|
||||||
return jsonify({
|
return jsonify({"id": user["id"], "username": user["username"]})
|
||||||
'id': user['id'],
|
|
||||||
'username': user['username']
|
|
||||||
})
|
|
||||||
|
|
||||||
# ==================== Journey endpoints (protected, user‑specific) ====================
|
# ==================== Journey endpoints (protected, user‑specific) ====================
|
||||||
def require_login():
|
def require_login():
|
||||||
if 'user_id' not in session:
|
if "user_id" not in session:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_current_user_id():
|
def get_current_user_id():
|
||||||
return session.get('user_id')
|
return session.get("user_id")
|
||||||
|
|
||||||
|
|
||||||
def get_journeys_for_current_user():
|
def get_journeys_for_current_user():
|
||||||
user_id = get_current_user_id()
|
user_id = get_current_user_id()
|
||||||
@ -178,91 +188,98 @@ def get_journeys_for_current_user():
|
|||||||
return None
|
return None
|
||||||
return load_user_journeys(user_id)
|
return load_user_journeys(user_id)
|
||||||
|
|
||||||
@app.route('/api/journeys', methods=['GET'])
|
|
||||||
|
@app.route("/api/journeys", methods=["GET"])
|
||||||
def get_journeys():
|
def get_journeys():
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
journeys = get_journeys_for_current_user()
|
journeys = get_journeys_for_current_user()
|
||||||
return jsonify(journeys)
|
return jsonify(journeys)
|
||||||
|
|
||||||
|
|
||||||
def get_next_journey_id(journeys):
|
def get_next_journey_id(journeys):
|
||||||
if not journeys:
|
if not journeys:
|
||||||
return 1
|
return 1
|
||||||
return max(j['id'] for j in journeys) + 1
|
return max(j["id"] for j in journeys) + 1
|
||||||
|
|
||||||
@app.route('/api/journeys', methods=['POST'])
|
|
||||||
|
@app.route("/api/journeys", methods=["POST"])
|
||||||
def create_journey():
|
def create_journey():
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data:
|
if not data:
|
||||||
return jsonify({'error': 'No data provided'}), 400
|
return jsonify({"error": "No data provided"}), 400
|
||||||
|
|
||||||
title = data.get('title')
|
title = data.get("title")
|
||||||
if not title:
|
if not title:
|
||||||
return jsonify({'error': 'Journey title is required'}), 400
|
return jsonify({"error": "Journey title is required"}), 400
|
||||||
|
|
||||||
user_id = get_current_user_id()
|
user_id = get_current_user_id()
|
||||||
journeys = get_journeys_for_current_user()
|
journeys = get_journeys_for_current_user()
|
||||||
new_id = get_next_journey_id(journeys)
|
new_id = get_next_journey_id(journeys)
|
||||||
|
|
||||||
new_journey = {
|
new_journey = {
|
||||||
'id': new_id,
|
"id": new_id,
|
||||||
'title': title,
|
"title": title,
|
||||||
'description': data.get('description', ''),
|
"description": data.get("description", ""),
|
||||||
'markers': data.get('markers', []),
|
"markers": data.get("markers", []),
|
||||||
'created_at': datetime.now().isoformat()
|
"created_at": datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
journeys.append(new_journey)
|
journeys.append(new_journey)
|
||||||
save_user_journeys(user_id, journeys)
|
save_user_journeys(user_id, journeys)
|
||||||
return jsonify(new_journey), 201
|
return jsonify(new_journey), 201
|
||||||
|
|
||||||
@app.route('/api/journeys/<int:journey_id>', methods=['GET'])
|
|
||||||
|
@app.route("/api/journeys/<int:journey_id>", methods=["GET"])
|
||||||
def get_journey(journey_id):
|
def get_journey(journey_id):
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
journeys = get_journeys_for_current_user()
|
journeys = get_journeys_for_current_user()
|
||||||
journey = next((j for j in journeys if j['id'] == journey_id), None)
|
journey = next((j for j in journeys if j["id"] == journey_id), None)
|
||||||
if journey is None:
|
if journey is None:
|
||||||
return jsonify({'error': 'Journey not found'}), 404
|
return jsonify({"error": "Journey not found"}), 404
|
||||||
return jsonify(journey)
|
return jsonify(journey)
|
||||||
|
|
||||||
@app.route('/api/journeys/<int:journey_id>', methods=['PUT'])
|
|
||||||
|
@app.route("/api/journeys/<int:journey_id>", methods=["PUT"])
|
||||||
def update_journey(journey_id):
|
def update_journey(journey_id):
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
journeys = get_journeys_for_current_user()
|
journeys = get_journeys_for_current_user()
|
||||||
journey = next((j for j in journeys if j['id'] == journey_id), None)
|
journey = next((j for j in journeys if j["id"] == journey_id), None)
|
||||||
if journey is None:
|
if journey is None:
|
||||||
return jsonify({'error': 'Journey not found'}), 404
|
return jsonify({"error": "Journey not found"}), 404
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data:
|
if not data:
|
||||||
return jsonify({'error': 'No data provided'}), 400
|
return jsonify({"error": "No data provided"}), 400
|
||||||
|
|
||||||
if 'title' in data:
|
if "title" in data:
|
||||||
journey['title'] = data['title']
|
journey["title"] = data["title"]
|
||||||
if 'description' in data:
|
if "description" in data:
|
||||||
journey['description'] = data['description']
|
journey["description"] = data["description"]
|
||||||
if 'markers' in data:
|
if "markers" in data:
|
||||||
journey['markers'] = data['markers']
|
journey["markers"] = data["markers"]
|
||||||
|
|
||||||
save_user_journeys(get_current_user_id(), journeys)
|
save_user_journeys(get_current_user_id(), journeys)
|
||||||
return jsonify(journey)
|
return jsonify(journey)
|
||||||
|
|
||||||
@app.route('/api/journeys/<int:journey_id>', methods=['DELETE'])
|
|
||||||
|
@app.route("/api/journeys/<int:journey_id>", methods=["DELETE"])
|
||||||
def delete_journey(journey_id):
|
def delete_journey(journey_id):
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
journeys = get_journeys_for_current_user()
|
journeys = get_journeys_for_current_user()
|
||||||
journey = next((j for j in journeys if j['id'] == journey_id), None)
|
journey = next((j for j in journeys if j["id"] == journey_id), None)
|
||||||
if journey is None:
|
if journey is None:
|
||||||
return jsonify({'error': 'Journey not found'}), 404
|
return jsonify({"error": "Journey not found"}), 404
|
||||||
|
|
||||||
journeys = [j for j in journeys if j['id'] != journey_id]
|
journeys = [j for j in journeys if j["id"] != journey_id]
|
||||||
save_user_journeys(get_current_user_id(), journeys)
|
save_user_journeys(get_current_user_id(), journeys)
|
||||||
return jsonify({'message': 'Journey deleted successfully', 'journey': journey})
|
return jsonify({"message": "Journey deleted successfully", "journey": journey})
|
||||||
|
|
||||||
|
|
||||||
# ==================== Blog Post endpoints (protected, user‑specific) ====================
|
# ==================== Blog Post endpoints (protected, user‑specific) ====================
|
||||||
def get_posts_for_current_user():
|
def get_posts_for_current_user():
|
||||||
@ -271,97 +288,105 @@ def get_posts_for_current_user():
|
|||||||
return None
|
return None
|
||||||
return load_user_posts(user_id)
|
return load_user_posts(user_id)
|
||||||
|
|
||||||
|
|
||||||
def get_next_post_id(posts):
|
def get_next_post_id(posts):
|
||||||
if not posts:
|
if not posts:
|
||||||
return 1
|
return 1
|
||||||
return max(p['id'] for p in posts) + 1
|
return max(p["id"] for p in posts) + 1
|
||||||
|
|
||||||
@app.route('/api/blog-posts', methods=['GET'])
|
|
||||||
|
@app.route("/api/blog-posts", methods=["GET"])
|
||||||
def get_blog_posts():
|
def get_blog_posts():
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
posts = get_posts_for_current_user()
|
posts = get_posts_for_current_user()
|
||||||
return jsonify(posts)
|
return jsonify(posts)
|
||||||
|
|
||||||
@app.route('/api/blog-posts/<int:post_id>', methods=['GET'])
|
|
||||||
|
@app.route("/api/blog-posts/<int:post_id>", methods=["GET"])
|
||||||
def get_blog_post(post_id):
|
def get_blog_post(post_id):
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
posts = get_posts_for_current_user()
|
posts = get_posts_for_current_user()
|
||||||
post = next((p for p in posts if p['id'] == post_id), None)
|
post = next((p for p in posts if p["id"] == post_id), None)
|
||||||
if not post:
|
if not post:
|
||||||
return jsonify({'error': 'Post not found'}), 404
|
return jsonify({"error": "Post not found"}), 404
|
||||||
return jsonify(post)
|
return jsonify(post)
|
||||||
|
|
||||||
@app.route('/api/blog-posts', methods=['POST'])
|
|
||||||
|
@app.route("/api/blog-posts", methods=["POST"])
|
||||||
def create_blog_post():
|
def create_blog_post():
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
title = data.get('title')
|
title = data.get("title")
|
||||||
if not title:
|
if not title:
|
||||||
return jsonify({'error': 'Title required'}), 400
|
return jsonify({"error": "Title required"}), 400
|
||||||
|
|
||||||
user_id = get_current_user_id()
|
user_id = get_current_user_id()
|
||||||
posts = get_posts_for_current_user()
|
posts = get_posts_for_current_user()
|
||||||
new_id = get_next_post_id(posts)
|
new_id = get_next_post_id(posts)
|
||||||
|
|
||||||
new_post = {
|
new_post = {
|
||||||
'id': new_id,
|
"id": new_id,
|
||||||
'title': title,
|
"title": title,
|
||||||
'content': data.get('content', ''),
|
"content": data.get("content", ""),
|
||||||
'journeyId': data.get('journeyId'),
|
"journeyId": data.get("journeyId"),
|
||||||
'image': data.get('image'),
|
"image": data.get("image"),
|
||||||
'created_at': datetime.now().isoformat()
|
"created_at": datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
posts.append(new_post)
|
posts.append(new_post)
|
||||||
save_user_posts(user_id, posts)
|
save_user_posts(user_id, posts)
|
||||||
return jsonify(new_post), 201
|
return jsonify(new_post), 201
|
||||||
|
|
||||||
@app.route('/api/blog-posts/<int:post_id>', methods=['PUT'])
|
|
||||||
|
@app.route("/api/blog-posts/<int:post_id>", methods=["PUT"])
|
||||||
def update_blog_post(post_id):
|
def update_blog_post(post_id):
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
posts = get_posts_for_current_user()
|
posts = get_posts_for_current_user()
|
||||||
post = next((p for p in posts if p['id'] == post_id), None)
|
post = next((p for p in posts if p["id"] == post_id), None)
|
||||||
if not post:
|
if not post:
|
||||||
return jsonify({'error': 'Post not found'}), 404
|
return jsonify({"error": "Post not found"}), 404
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if 'title' in data:
|
if "title" in data:
|
||||||
post['title'] = data['title']
|
post["title"] = data["title"]
|
||||||
if 'content' in data:
|
if "content" in data:
|
||||||
post['content'] = data['content']
|
post["content"] = data["content"]
|
||||||
if 'journeyId' in data:
|
if "journeyId" in data:
|
||||||
post['journeyId'] = data['journeyId']
|
post["journeyId"] = data["journeyId"]
|
||||||
if 'image' in data:
|
if "image" in data:
|
||||||
post['image'] = data['image']
|
post["image"] = data["image"]
|
||||||
|
|
||||||
save_user_posts(get_current_user_id(), posts)
|
save_user_posts(get_current_user_id(), posts)
|
||||||
return jsonify(post)
|
return jsonify(post)
|
||||||
|
|
||||||
@app.route('/api/blog-posts/<int:post_id>', methods=['DELETE'])
|
|
||||||
|
@app.route("/api/blog-posts/<int:post_id>", methods=["DELETE"])
|
||||||
def delete_blog_post(post_id):
|
def delete_blog_post(post_id):
|
||||||
if not require_login():
|
if not require_login():
|
||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({"error": "Authentication required"}), 401
|
||||||
posts = get_posts_for_current_user()
|
posts = get_posts_for_current_user()
|
||||||
post = next((p for p in posts if p['id'] == post_id), None)
|
post = next((p for p in posts if p["id"] == post_id), None)
|
||||||
if not post:
|
if not post:
|
||||||
return jsonify({'error': 'Post not found'}), 404
|
return jsonify({"error": "Post not found"}), 404
|
||||||
|
|
||||||
posts = [p for p in posts if p['id'] != post_id]
|
posts = [p for p in posts if p["id"] != post_id]
|
||||||
save_user_posts(get_current_user_id(), posts)
|
save_user_posts(get_current_user_id(), posts)
|
||||||
return jsonify({'message': 'Post deleted'})
|
return jsonify({"message": "Post deleted"})
|
||||||
|
|
||||||
|
|
||||||
# ==================== Health and root ====================
|
# ==================== Health and root ====================
|
||||||
@app.route('/api/journeys/health', methods=['GET'])
|
@app.route("/api/journeys/health", methods=["GET"])
|
||||||
def health_check():
|
def health_check():
|
||||||
return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()})
|
return jsonify({"status": "healthy", "timestamp": datetime.now().isoformat()})
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return '''
|
return """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head><title>Journey Mapper API</title></head>
|
<head><title>Journey Mapper API</title></head>
|
||||||
@ -371,7 +396,8 @@ def index():
|
|||||||
<p>Authentication required: register, login, then use session cookies.</p>
|
<p>Authentication required: register, login, then use session cookies.</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
app.run(debug=True, port=5000)
|
app.run(debug=True, port=5000)
|
||||||
@ -1 +1,42 @@
|
|||||||
[]
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "test",
|
||||||
|
"description": "11",
|
||||||
|
"markers": [
|
||||||
|
{
|
||||||
|
"lat": 48.356249029540734,
|
||||||
|
"lng": 4.866943359375,
|
||||||
|
"title": "New Marker",
|
||||||
|
"date": "",
|
||||||
|
"description": "",
|
||||||
|
"videoUrl": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lat": 46.961510504873104,
|
||||||
|
"lng": 9.371337890625002,
|
||||||
|
"title": "New Marker",
|
||||||
|
"date": "",
|
||||||
|
"description": "",
|
||||||
|
"videoUrl": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lat": 45.51404592560427,
|
||||||
|
"lng": 11.656494140625002,
|
||||||
|
"title": "New Marker",
|
||||||
|
"date": "",
|
||||||
|
"description": "",
|
||||||
|
"videoUrl": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lat": 43.52465500687188,
|
||||||
|
"lng": 11.162109375,
|
||||||
|
"title": "New Marker",
|
||||||
|
"date": "",
|
||||||
|
"description": "",
|
||||||
|
"videoUrl": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"created_at": "2026-03-27T21:49:26.885353"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -1 +1,10 @@
|
|||||||
[]
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "test",
|
||||||
|
"content": "ksafladjsfk",
|
||||||
|
"journeyId": "1",
|
||||||
|
"image": null,
|
||||||
|
"created_at": "2026-03-27T21:23:39.755057"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -221,61 +221,8 @@
|
|||||||
|
|
||||||
<div id="toast" class="toast"></div>
|
<div id="toast" class="toast"></div>
|
||||||
|
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// ==================== AUTH ====================
|
|
||||||
const API_BASE = 'http://127.0.0.1:5000/api';
|
|
||||||
let currentUser = null;
|
|
||||||
|
|
||||||
async function checkAuthAndRedirect() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_BASE}/me`, { credentials: 'include' });
|
|
||||||
if (res.ok) {
|
|
||||||
currentUser = await res.json();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
window.location.href = 'login.html';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
window.location.href = 'login.html';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUserMenu() {
|
|
||||||
const container = document.getElementById('user-menu');
|
|
||||||
if (currentUser) {
|
|
||||||
container.innerHTML = `
|
|
||||||
<span class="username"><i class="fas fa-user"></i> ${escapeHtml(currentUser.username)}</span>
|
|
||||||
<button id="logout-btn" class="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</button>
|
|
||||||
`;
|
|
||||||
document.getElementById('logout-btn')?.addEventListener('click', logout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
await fetch(`${API_BASE}/logout`, { method: 'POST', credentials: 'include' });
|
|
||||||
window.location.href = 'login.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(str) {
|
|
||||||
if (!str) return '';
|
|
||||||
return str.replace(/[&<>]/g, function(m) {
|
|
||||||
if (m === '&') return '&';
|
|
||||||
if (m === '<') return '<';
|
|
||||||
if (m === '>') return '>';
|
|
||||||
return m;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showToast(msg, isError = false) {
|
|
||||||
const toast = document.getElementById('toast');
|
|
||||||
toast.textContent = msg;
|
|
||||||
toast.style.backgroundColor = isError ? 'var(--red-7)' : 'var(--green-7)';
|
|
||||||
toast.style.display = 'block';
|
|
||||||
setTimeout(() => { toast.style.display = 'none'; }, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== BLOG POSTS ====================
|
// ==================== BLOG POSTS ====================
|
||||||
async function loadPosts() {
|
async function loadPosts() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -239,62 +239,10 @@
|
|||||||
|
|
||||||
<div id="toast" class="toast"></div>
|
<div id="toast" class="toast"></div>
|
||||||
|
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// ==================== AUTH ====================
|
|
||||||
const API_BASE = 'http://127.0.0.1:5000/api';
|
|
||||||
let currentUser = null;
|
|
||||||
let currentPostId = null;
|
let currentPostId = null;
|
||||||
|
|
||||||
async function checkAuthAndRedirect() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_BASE}/me`, { credentials: 'include' });
|
|
||||||
if (res.ok) {
|
|
||||||
currentUser = await res.json();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
window.location.href = 'login.html';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
window.location.href = 'login.html';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUserMenu() {
|
|
||||||
const container = document.getElementById('user-menu');
|
|
||||||
if (currentUser) {
|
|
||||||
container.innerHTML = `
|
|
||||||
<span class="username"><i class="fas fa-user"></i> ${escapeHtml(currentUser.username)}</span>
|
|
||||||
<button id="logout-btn" class="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</button>
|
|
||||||
`;
|
|
||||||
document.getElementById('logout-btn')?.addEventListener('click', logout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
await fetch(`${API_BASE}/logout`, { method: 'POST', credentials: 'include' });
|
|
||||||
window.location.href = 'login.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(str) {
|
|
||||||
if (!str) return '';
|
|
||||||
return str.replace(/[&<>]/g, function(m) {
|
|
||||||
if (m === '&') return '&';
|
|
||||||
if (m === '<') return '<';
|
|
||||||
if (m === '>') return '>';
|
|
||||||
return m;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showToast(message, isError = false) {
|
|
||||||
const toast = document.getElementById('toast');
|
|
||||||
toast.textContent = message;
|
|
||||||
toast.style.backgroundColor = isError ? 'var(--red-7)' : 'var(--green-7)';
|
|
||||||
toast.style.display = 'block';
|
|
||||||
setTimeout(() => { toast.style.display = 'none'; }, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== POST CRUD ====================
|
// ==================== POST CRUD ====================
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const postId = urlParams.get('id');
|
const postId = urlParams.get('id');
|
||||||
|
|||||||
67
js/auth.js
Normal file
67
js/auth.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const API_BASE = "http://127.0.0.1:5000/api";
|
||||||
|
let currentUser = null;
|
||||||
|
|
||||||
|
async function checkAuth() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE}/me`, { credentials: "include" });
|
||||||
|
if (res.ok) {
|
||||||
|
currentUser = await res.json();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkAuthAndRedirect() {
|
||||||
|
const ok = await checkAuth();
|
||||||
|
if (!ok) {
|
||||||
|
window.location.href = "login.html";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUserMenu() {
|
||||||
|
const container = document.getElementById("user-menu");
|
||||||
|
if (!container) return;
|
||||||
|
if (currentUser) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<span class="username"><i class="fas fa-user"></i> ${escapeHtml(currentUser.username)}</span>
|
||||||
|
<button id="logout-btn" class="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</button>
|
||||||
|
`;
|
||||||
|
document.getElementById("logout-btn")?.addEventListener("click", logout);
|
||||||
|
} else {
|
||||||
|
container.innerHTML = `<button id="login-open-btn" class="login-btn"><i class="fas fa-sign-in-alt"></i> Login</button>`;
|
||||||
|
document.getElementById("login-open-btn")?.addEventListener("click", () => {
|
||||||
|
window.location.href = "login.html";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
await fetch(`${API_BASE}/logout`, { method: "POST", credentials: "include" });
|
||||||
|
window.location.href = "login.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
if (!str) return "";
|
||||||
|
return str.replace(/[&<>]/g, function (m) {
|
||||||
|
if (m === "&") return "&";
|
||||||
|
if (m === "<") return "<";
|
||||||
|
if (m === ">") return ">";
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(msg, isError = false) {
|
||||||
|
const toast = document.getElementById("toast");
|
||||||
|
if (!toast) return;
|
||||||
|
toast.textContent = msg;
|
||||||
|
toast.style.backgroundColor = isError ? "var(--red-7)" : "var(--green-7)";
|
||||||
|
toast.style.display = "block";
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.style.display = "none";
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
81
login.html
81
login.html
@ -154,18 +154,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="toast" class="toast"></div>
|
<div id="toast" class="toast"></div>
|
||||||
|
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const API_BASE = 'http://127.0.0.1:5000/api';
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Tab switching
|
||||||
function showToast(msg, isError = false) {
|
document.querySelectorAll('.auth-tab').forEach(tab => {
|
||||||
const toast = document.getElementById('toast');
|
tab.addEventListener('click', () => {
|
||||||
toast.textContent = msg;
|
const target = tab.dataset.tab;
|
||||||
toast.style.backgroundColor = isError ? 'var(--red-7)' : 'var(--green-7)';
|
document.querySelectorAll('.auth-tab').forEach(t => t.classList.remove('active'));
|
||||||
toast.style.display = 'block';
|
tab.classList.add('active');
|
||||||
setTimeout(() => { toast.style.display = 'none'; }, 3000);
|
document.querySelectorAll('.auth-form').forEach(f => f.classList.remove('active'));
|
||||||
|
document.getElementById(`${target}-form`).classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Login button
|
||||||
|
document.getElementById('login-submit').addEventListener('click', async () => {
|
||||||
|
const username = document.getElementById('login-username').value.trim();
|
||||||
|
const password = document.getElementById('login-password').value;
|
||||||
|
if (!username || !password) {
|
||||||
|
showToast('Please enter username and password', true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(username, password) {
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE}/login`, {
|
const res = await fetch(`${API_BASE}/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -175,14 +184,24 @@
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) throw new Error(data.error || 'Login failed');
|
if (!res.ok) throw new Error(data.error || 'Login failed');
|
||||||
showToast(`Welcome, ${data.username}!`);
|
showToast(`Welcome, ${data.username}`);
|
||||||
window.location.href = 'map-page.html';
|
window.location.href = 'map-page.html';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(err.message, true);
|
showToast(err.message, true);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
// Register button
|
||||||
|
document.getElementById('register-submit').addEventListener('click', async () => {
|
||||||
|
const username = document.getElementById('register-username').value.trim();
|
||||||
|
const password = document.getElementById('register-password').value;
|
||||||
|
if (!username || !password) {
|
||||||
|
showToast('Please enter username and password', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password.length < 4) {
|
||||||
|
showToast('Password must be at least 4 characters', true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function register(username, password) {
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE}/register`, {
|
const res = await fetch(`${API_BASE}/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -197,42 +216,6 @@
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(err.message, true);
|
showToast(err.message, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
// Tab switching
|
|
||||||
document.querySelectorAll('.auth-tab').forEach(tab => {
|
|
||||||
tab.addEventListener('click', () => {
|
|
||||||
const target = tab.dataset.tab;
|
|
||||||
document.querySelectorAll('.auth-tab').forEach(t => t.classList.remove('active'));
|
|
||||||
tab.classList.add('active');
|
|
||||||
document.querySelectorAll('.auth-form').forEach(f => f.classList.remove('active'));
|
|
||||||
document.getElementById(`${target}-form`).classList.add('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Login button
|
|
||||||
document.getElementById('login-submit').addEventListener('click', () => {
|
|
||||||
const username = document.getElementById('login-username').value.trim();
|
|
||||||
const password = document.getElementById('login-password').value;
|
|
||||||
if (!username || !password) {
|
|
||||||
showToast('Please enter username and password', true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
login(username, password);
|
|
||||||
});
|
|
||||||
// Register button
|
|
||||||
document.getElementById('register-submit').addEventListener('click', () => {
|
|
||||||
const username = document.getElementById('register-username').value.trim();
|
|
||||||
const password = document.getElementById('register-password').value;
|
|
||||||
if (!username || !password) {
|
|
||||||
showToast('Please enter username and password', true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (password.length < 4) {
|
|
||||||
showToast('Password must be at least 4 characters', true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
register(username, password);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -876,65 +876,10 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<div id="toast" class="toast"><p id="toast-message"></p></div>
|
<div id="toast" class="toast"><p id="toast-message"></p></div>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// ==================== AUTH & REDIRECT ====================
|
// ==================== MAP CODE ====================
|
||||||
const API_BASE = "http://127.0.0.1:5000/api";
|
|
||||||
let currentUser = null;
|
|
||||||
|
|
||||||
async function checkAuth() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_BASE}/me`, {
|
|
||||||
credentials: "include",
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
currentUser = await res.json();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
window.location.href = "login.html";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
window.location.href = "login.html";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUserMenu() {
|
|
||||||
const container = document.getElementById("user-menu");
|
|
||||||
if (currentUser) {
|
|
||||||
container.innerHTML = `
|
|
||||||
<span class="username"><i class="fas fa-user"></i> ${escapeHtml(currentUser.username)}</span>
|
|
||||||
<button id="logout-btn" class="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</button>
|
|
||||||
`;
|
|
||||||
document
|
|
||||||
.getElementById("logout-btn")
|
|
||||||
?.addEventListener("click", logout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
await fetch(`${API_BASE}/logout`, {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
});
|
|
||||||
window.location.href = "login.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(str) {
|
|
||||||
if (!str) return "";
|
|
||||||
return str.replace(/[&<>]/g, function (m) {
|
|
||||||
if (m === "&") return "&";
|
|
||||||
if (m === "<") return "<";
|
|
||||||
if (m === ">") return ">";
|
|
||||||
return m;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== MAP CODE (from your file, but with credentials added) ====================
|
|
||||||
(function () {
|
(function () {
|
||||||
// ==================== CONFIG ====================
|
|
||||||
// API_BASE is already defined outside
|
|
||||||
|
|
||||||
// ==================== STATE =====================
|
// ==================== STATE =====================
|
||||||
let map;
|
let map;
|
||||||
let currentJourney = {
|
let currentJourney = {
|
||||||
@ -1462,7 +1407,7 @@
|
|||||||
|
|
||||||
// ==================== AUTHENTICATION CHECK ====================
|
// ==================== AUTHENTICATION CHECK ====================
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
const authenticated = await checkAuth();
|
const authenticated = await checkAuthAndRedirect();
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
updateUserMenu();
|
updateUserMenu();
|
||||||
window.startMap(); // call the map initializer from the IIFE
|
window.startMap(); // call the map initializer from the IIFE
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user