From 0de91bf814dfddd16a58d31c0f3bc9a0fad23a0a Mon Sep 17 00:00:00 2001 From: Josh-Dev-Quest Date: Sat, 28 Mar 2026 16:40:16 +0100 Subject: [PATCH] Unified the journeys and blogpost datastructure in the backend --- backend/app.py | 350 ++++++++++++++------------------ backend/data/blog_posts.json | 10 - backend/data/journeys.json | 71 ++----- backend/data/users/1/posts.json | 43 ++-- blog-list.html | 95 +++++++-- blog-post-edit.html | 13 +- blog-post.html | 174 ++++++++++------ map-page.html | 29 ++- 8 files changed, 423 insertions(+), 362 deletions(-) delete mode 100644 backend/data/blog_posts.json diff --git a/backend/app.py b/backend/app.py index 3e20377..9aeb9cd 100644 --- a/backend/app.py +++ b/backend/app.py @@ -50,6 +50,50 @@ def get_user_by_id(user_id): users = load_users() return next((u for u in users if u["id"] == user_id), None) +# Central journeys storage +JOURNEYS_FILE = os.path.join(DATA_DIR, 'journeys.json') + +def load_all_journeys(): + try: + if os.path.exists(JOURNEYS_FILE): + with open(JOURNEYS_FILE, 'r') as f: + return json.load(f) + except Exception as e: + print(f"Error loading journeys: {e}") + return [] + +def save_all_journeys(journeys): + try: + with open(JOURNEYS_FILE, 'w') as f: + json.dump(journeys, f, indent=2) + except Exception as e: + print(f"Error saving journeys: {e}") + +def get_next_journey_id(journeys): + if not journeys: + return 1 + return max(j['id'] for j in journeys) + 1 + +def get_journey_by_id(journey_id): + journeys = load_all_journeys() + return next((j for j in journeys if j['id'] == journey_id), None) + +def user_can_view_journey(journey, user_id): + if journey['owner_id'] == user_id: + return True + if journey.get('visibility') == 'public': + return True + if journey.get('visibility') == 'shared' and user_id in journey.get('shared_read', []): + return True + return False + +def user_can_edit_journey(journey, user_id): + if journey['owner_id'] == user_id: + return True + if journey.get('visibility') == 'shared' and user_id in journey.get('shared_edit', []): + return True + return False + # ==================== Per‑user data helpers ==================== def get_user_data_dir(user_id): @@ -172,7 +216,7 @@ def me(): return jsonify({"id": user["id"], "username": user["username"]}) -# ==================== Journey endpoints (protected, user‑specific) ==================== +# ==================== Journey helper functions ==================== def require_login(): if "user_id" not in session: return False @@ -189,224 +233,142 @@ def get_journeys_for_current_user(): return None return load_user_journeys(user_id) - -@app.route("/api/journeys", methods=["GET"]) +# ==================== Journey endpoints ==================== +@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) + return jsonify({'error': 'Authentication required'}), 401 + user_id = get_current_user_id() + all_journeys = load_all_journeys() + result = [] + for j in all_journeys: + if user_can_view_journey(j, user_id): + # Add extra flags for the frontend + j_copy = j.copy() + j_copy['can_edit'] = user_can_edit_journey(j, user_id) + result.append(j_copy) + return jsonify(result) - -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"]) +@app.route('/api/journeys', methods=['POST']) def create_journey(): if not require_login(): - return jsonify({"error": "Authentication required"}), 401 + return jsonify({'error': 'Authentication required'}), 401 data = request.get_json() 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: - return jsonify({"error": "Journey title is required"}), 400 + return jsonify({'error': 'Journey title is required'}), 400 user_id = get_current_user_id() - journeys = get_journeys_for_current_user() + journeys = load_all_journeys() 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(), + 'id': new_id, + 'owner_id': user_id, + 'title': title, + 'description': data.get('description', ''), + 'markers': data.get('markers', []), + 'created_at': datetime.now().isoformat(), + 'visibility': data.get('visibility', 'private'), + 'shared_read': data.get('shared_read', []), + 'shared_edit': data.get('shared_edit', []), + 'comments': data.get('comments', []) } journeys.append(new_journey) - save_user_journeys(user_id, journeys) + save_all_journeys(journeys) return jsonify(new_journey), 201 - -@app.route("/api/journeys/", methods=["GET"]) +@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) + return jsonify({'error': 'Authentication required'}), 401 + user_id = get_current_user_id() + journey = get_journey_by_id(journey_id) if journey is None: - return jsonify({"error": "Journey not found"}), 404 + return jsonify({'error': 'Journey not found'}), 404 + if not user_can_view_journey(journey, user_id): + return jsonify({'error': 'Access denied'}), 403 return jsonify(journey) - -@app.route("/api/journeys/", methods=["PUT"]) +@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) + return jsonify({'error': 'Authentication required'}), 401 + user_id = get_current_user_id() + journeys = load_all_journeys() + 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({'error': 'Journey not found'}), 404 + if not user_can_edit_journey(journey, user_id): + return jsonify({'error': 'Not authorized to edit this journey'}), 403 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'] + if 'visibility' in data: + journey['visibility'] = data['visibility'] + if 'shared_read' in data: + journey['shared_read'] = data['shared_read'] + if 'shared_edit' in data: + journey['shared_edit'] = data['shared_edit'] + if 'comments' in data: + journey['comments'] = data ['comments'] - 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) + save_all_journeys(journeys) return jsonify(journey) - -@app.route("/api/journeys/", methods=["DELETE"]) +@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) + return jsonify({'error': 'Authentication required'}), 401 + user_id = get_current_user_id() + journeys = load_all_journeys() + 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({'error': 'Journey not found'}), 404 + if journey['owner_id'] != user_id: + return jsonify({'error': 'Only the owner can delete this journey'}), 403 - 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}) + journeys = [j for j in journeys if j['id'] != journey_id] + save_all_journeys(journeys) + return jsonify({'message': 'Journey deleted successfully', 'journey': journey}) +# ==================== Journey endpoints (already present) ==================== +# (Keep the existing journey endpoints: GET /api/journeys, POST, etc.) -# ==================== 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"), - "author_id": user_id, - "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 not get_current_user_id() == data["author_id"]: - return jsonify({"error": "Wrong user"}) - 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"}) - - -# ==================== Comments (stored inside posts) ==================== -def get_post_by_id(user_id, post_id): - posts = load_user_posts(user_id) - return next((p for p in posts if p['id'] == post_id), None) - -def save_post(user_id, post): - posts = load_user_posts(user_id) - for i, p in enumerate(posts): - if p['id'] == post['id']: - posts[i] = post +# ==================== Comments (stored inside journeys) ==================== +def save_journey(journey): + journeys = load_all_journeys() + for i, j in enumerate(journeys): + if j['id'] == journey['id']: + journeys[i] = journey break - save_user_posts(user_id, posts) + save_all_journeys(journeys) -@app.route('/api/posts//comments', methods=['GET']) -def get_comments(post_id): +@app.route('/api/journeys//comments', methods=['GET']) +def get_journey_comments(journey_id): user_id = session.get('user_id') if not user_id: return jsonify({'error': 'Authentication required'}), 401 - post = get_post_by_id(user_id, post_id) - if not post: - return jsonify({'error': 'Post not found'}), 404 - return jsonify(post.get('comments', [])) -@app.route('/api/posts//comments', methods=['POST']) -def add_comment(post_id): + journey = get_journey_by_id(journey_id) + if not journey: + return jsonify({'error': 'Journey not found'}), 404 + if not user_can_view_journey(journey, user_id): + return jsonify({'error': 'Access denied'}), 403 + + return jsonify(journey.get('comments', [])) + +@app.route('/api/journeys//comments', methods=['POST']) +def add_journey_comment(journey_id): user_id = session.get('user_id') if not user_id: return jsonify({'error': 'Authentication required'}), 401 @@ -415,21 +377,24 @@ def add_comment(post_id): if not text: return jsonify({'error': 'Comment text required'}), 400 - post = get_post_by_id(user_id, post_id) - if not post: - return jsonify({'error': 'Post not found'}), 404 + journey = get_journey_by_id(journey_id) + if not journey: + return jsonify({'error': 'Journey not found'}), 404 + if not user_can_view_journey(journey, user_id): + return jsonify({'error': 'Access denied'}), 403 comment = { - 'id': int(time.time() * 1000), # simple unique id + 'id': int(time.time() * 1000), 'author_id': user_id, 'author_name': get_user_by_id(user_id)['username'], 'text': text, - 'created_at': datetime.now().isoformat() + 'created_at': datetime.now().isoformat(), } - if 'comments' not in post: - post['comments'] = [] - post['comments'].append(comment) - save_post(user_id, post) + if 'comments' not in journey: + journey['comments'] = [] + journey['comments'].append(comment) + save_journey(journey) + return jsonify(comment), 201 @app.route('/api/comments/', methods=['DELETE']) @@ -438,16 +403,15 @@ def delete_comment(comment_id): if not user_id: return jsonify({'error': 'Authentication required'}), 401 - # Find which post contains this comment - posts = load_user_posts(user_id) - for post in posts: - if 'comments' in post: - for i, c in enumerate(post['comments']): + journeys = load_all_journeys() + for journey in journeys: + if 'comments' in journey: + for i, c in enumerate(journey['comments']): if c['id'] == comment_id: - # Allow deletion if current user is comment author or post author - if c['author_id'] == user_id or post['id'] == post.get('author_id', user_id): - del post['comments'][i] - save_post(user_id, post) + # Check permissions: comment author or journey owner can delete + if c['author_id'] == user_id or journey['owner_id'] == user_id: + del journey['comments'][i] + save_journey(journey) return jsonify({'message': 'Comment deleted'}) else: return jsonify({'error': 'Not authorized'}), 403 diff --git a/backend/data/blog_posts.json b/backend/data/blog_posts.json deleted file mode 100644 index 64c8603..0000000 --- a/backend/data/blog_posts.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "id": 1, - "title": "test", - "content": "sfsfsfsaf", - "journeyId": "1", - "image": null, - "created_at": "2026-03-27T19:49:49.410806" - } -] \ No newline at end of file diff --git a/backend/data/journeys.json b/backend/data/journeys.json index 4a249a3..9d7be41 100644 --- a/backend/data/journeys.json +++ b/backend/data/journeys.json @@ -1,82 +1,39 @@ [ { "id": 1, - "title": "test", - "description": "sdfsf\n", + "owner_id": 1, + "title": "Test journey", + "description": "test", "markers": [ { - "lat": 48.22467264956519, - "lng": 9.536132812500002, + "lat": 46.638122462379656, + "lng": 4.806518554687501, "title": "New Marker", "date": "", "description": "", "videoUrl": "" }, { - "lat": 49.937079756975294, - "lng": 8.789062500000002, + "lat": 47.12621341795227, + "lng": 6.943359375000001, "title": "New Marker", "date": "", "description": "", "videoUrl": "" }, { - "lat": 50.583236614805905, - "lng": 9.689941406250002, + "lat": 46.46813299215556, + "lng": 6.7730712890625, "title": "New Marker", "date": "", "description": "", "videoUrl": "" } ], - "created_at": "2026-03-01T19:02:15.679031" - }, - { - "id": 2, - "title": "test 1", - "description": "sdfdsfafsfsdsf", - "markers": [ - { - "lat": 48.705462895790575, - "lng": 2.4334716796875, - "title": "New Marker", - "date": "", - "description": "", - "videoUrl": "" - }, - { - "lat": 47.37603463349758, - "lng": 2.0654296875000004, - "title": "New Marker", - "date": "", - "description": "", - "videoUrl": "" - }, - { - "lat": 47.25686404408872, - "lng": 5.020751953125, - "title": "New Marker", - "date": "", - "description": "", - "videoUrl": "" - }, - { - "lat": 47.25686404408872, - "lng": 5.954589843750001, - "title": "New Marker", - "date": "", - "description": "", - "videoUrl": "" - }, - { - "lat": 47.357431944587034, - "lng": 7.289428710937501, - "title": "New Marker", - "date": "", - "description": "", - "videoUrl": "" - } - ], - "created_at": "2026-03-05T13:00:48.757539" + "created_at": "2026-03-28T16:34:31.421684", + "visibility": "private", + "shared_read": [], + "shared_edit": [], + "comments": [] } ] \ No newline at end of file diff --git a/backend/data/users/1/posts.json b/backend/data/users/1/posts.json index 35f66cc..98e13bd 100644 --- a/backend/data/users/1/posts.json +++ b/backend/data/users/1/posts.json @@ -2,18 +2,37 @@ { "id": 1, "title": "test", - "content": "ksafladjsfk", - "journeyId": "1", + "content": "qwef", + "journeyId": null, "image": null, - "created_at": "2026-03-27T21:23:39.755057", - "comments": [ - { - "id": 1774703592361, - "author_id": 1, - "author_name": "josh", - "text": "hello", - "created_at": "2026-03-28T14:13:12.362078" - } - ] + "author_id": 1, + "created_at": "2026-03-28T15:47:04.343616", + "visibility": "private", + "shared_read": [], + "shared_edit": [] + }, + { + "id": 2, + "title": "another test post", + "content": "sgadsfg", + "journeyId": null, + "image": null, + "author_id": 1, + "created_at": "2026-03-28T16:08:36.820019", + "visibility": "private", + "shared_read": [], + "shared_edit": [] + }, + { + "id": 3, + "title": "another post", + "content": "sfadfas", + "journeyId": null, + "image": null, + "author_id": 1, + "created_at": "2026-03-28T16:08:52.889611", + "visibility": "private", + "shared_read": [], + "shared_edit": [] } ] \ No newline at end of file diff --git a/blog-list.html b/blog-list.html index eb3790f..4629644 100644 --- a/blog-list.html +++ b/blog-list.html @@ -193,6 +193,28 @@ display: none; z-index: 1100; } + .filter-tabs { + display: flex; + gap: var(--size-2); + } + .filter-btn { + background: var(--surface-3); + color: var(--text-2); + border: none; + border-radius: var(--radius-2); + padding: var(--size-1) var(--size-3); + cursor: pointer; + font-size: var(--font-size-1); + font-weight: var(--font-weight-5); + transition: all 0.2s; + } + .filter-btn.active { + background: var(--indigo-7); + color: white; + } + .filter-btn:hover { + background: var(--surface-4); + } @@ -200,6 +222,11 @@

Journey Mapper

+
+ + + +