From cd9ab30ab72e9daf20288287d96a6ba42e18c069 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 31 Jan 2026 04:46:10 +0000 Subject: [PATCH] feat: Add app_id parameter to engagement functions for proper routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All engagement functions (like, truth vote, share, view, comment) now accept app_id in addition to guid. This enables the backend to route requests to the correct system based on app_id. - news-global → routes to news-app (ES-based) - no app_id → routes to native post system (PostgreSQL/YCQL) Future apps can be routed based on their app_id. Co-Authored-By: Claude Opus 4.5 --- src/api/client.js | 18 ++++++++++++------ src/components/Map/markerManager.js | 14 +++++++------- src/components/Posts/PostDetailModal.jsx | 16 ++++++++-------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/api/client.js b/src/api/client.js index bb5ae07..ea8b197 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -415,10 +415,11 @@ export async function fetchPostTruth(postId) { } } -export async function votePostTruth(postId, vote, guid = null) { +export async function votePostTruth(postId, vote, guid = null, appId = null) { if (!postId && !guid) throw new Error("post_id or guid required"); const payload = { post_id: postId, vote }; if (guid) payload.guid = guid; + if (appId) payload.app_id = appId; const body = JSON.stringify(payload); const res = await fetch(`${apiBase()}/post/truth-vote`, { method: "POST", @@ -778,11 +779,12 @@ export async function fetchProfile(username = "") { } } -export async function recordPostView(postId, guid = null) { +export async function recordPostView(postId, guid = null, appId = null) { if (!postId && !guid) return { ok: false }; try { const body = { post_id: postId }; if (guid) body.guid = guid; + if (appId) body.app_id = appId; const res = await fetch(`${apiBase()}/post/view`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -796,11 +798,12 @@ export async function recordPostView(postId, guid = null) { } } -export async function togglePostLike(postId, like = true, guid = null) { +export async function togglePostLike(postId, like = true, guid = null, appId = null) { if (!postId && !guid) return { ok: false }; try { const body = { post_id: postId, like }; if (guid) body.guid = guid; + if (appId) body.app_id = appId; const res = await fetch(`${apiBase()}/post/like`, { method: "POST", headers: { "Content-Type": "application/json", ...authHeaders() }, @@ -814,11 +817,12 @@ export async function togglePostLike(postId, like = true, guid = null) { } } -export async function recordPostShare(postId, guid = null) { +export async function recordPostShare(postId, guid = null, appId = null) { if (!postId && !guid) return { ok: false }; try { const body = { post_id: postId }; if (guid) body.guid = guid; + if (appId) body.app_id = appId; const res = await fetch(`${apiBase()}/post/share`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -832,11 +836,12 @@ export async function recordPostShare(postId, guid = null) { } } -export async function fetchPostComments(postId, limit = 20, guid = null) { +export async function fetchPostComments(postId, limit = 20, guid = null, appId = null) { if (!postId && !guid) return []; const qs = new URLSearchParams(); qs.set("post_id", String(postId)); if (guid) qs.set("guid", guid); + if (appId) qs.set("app_id", appId); if (limit) qs.set("limit", String(limit)); try { const res = await fetch(`${apiBase()}/post/comments?${qs.toString()}`, { @@ -850,12 +855,13 @@ export async function fetchPostComments(postId, limit = 20, guid = null) { } } -export async function createPostComment(postId, body, guid = null) { +export async function createPostComment(postId, body, guid = null, appId = null) { if (!postId && !guid) return { ok: false }; if (!body) return { ok: false }; try { const reqBody = { post_id: postId, body }; if (guid) reqBody.guid = guid; + if (appId) reqBody.app_id = appId; const res = await fetch(`${apiBase()}/post/comment`, { method: "POST", headers: { "Content-Type": "application/json", ...authHeaders() }, diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index 58701e9..636895c 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -1812,7 +1812,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { if ((postID > 0 || postData?.guid) && !modalWrap.__swViewed) { modalWrap.__swViewed = true; - recordPostView(postID, postData?.guid).then((res) => { + recordPostView(postID, postData?.guid, postData?.app_id).then((res) => { const counts = res?.counts; if (counts) { updateStat(modalWrap, "views", counts.view_count); @@ -1858,7 +1858,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { } trackEvent("post_like_click", { post_id: postID, context: "map" }); likeBtn.disabled = true; - const res = await togglePostLike(postID, !liked); + const res = await togglePostLike(postID, !liked, postData?.guid, postData?.app_id); if (res?.ok) { liked = !!res.liked; likeBtn.textContent = liked ? "Liked" : "Like"; @@ -1889,7 +1889,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { await navigator.clipboard.writeText(shareUrl); } } catch {} - const res = await recordPostShare(postID, postData?.guid); + const res = await recordPostShare(postID, postData?.guid, postData?.app_id); const counts = res?.counts; if (counts) { updateStat(modalWrap, "views", counts.view_count); @@ -1945,7 +1945,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { try { trackEvent("truth_vote_click", { post_id: postID || postData?.guid, vote, context: "map" }); - const stats = await votePostTruth(postID, vote, postData?.guid); + const stats = await votePostTruth(postID, vote, postData?.guid, postData?.app_id); if (stats) { currentVote = vote; updateButtonStyles(); @@ -2188,7 +2188,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { const commentsBody = modalWrap.querySelector(".sw-livechat-body"); if (commentsBody && postID > 0) { - fetchPostComments(postID, 20).then((items) => renderComments(commentsBody, items)); + fetchPostComments(postID, 20, postData?.guid, postData?.app_id).then((items) => renderComments(commentsBody, items)); } if (commentBtn && commentInput && postID > 0) { commentBtn.addEventListener("click", async () => { @@ -2200,10 +2200,10 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { if (!body) return; trackEvent("post_comment_submit", { post_id: postID, context: "map" }); commentBtn.disabled = true; - const res = await createPostComment(postID, body, postData?.guid); + const res = await createPostComment(postID, body, postData?.guid, postData?.app_id); if (res?.ok) { commentInput.value = ""; - const existing = await fetchPostComments(postID, 20, postData?.guid); + const existing = await fetchPostComments(postID, 20, postData?.guid, postData?.app_id); renderComments(commentsBody, existing); const counts = res?.counts; if (counts) { diff --git a/src/components/Posts/PostDetailModal.jsx b/src/components/Posts/PostDetailModal.jsx index b93f1c1..eba7fa1 100644 --- a/src/components/Posts/PostDetailModal.jsx +++ b/src/components/Posts/PostDetailModal.jsx @@ -624,7 +624,7 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe // Initial data fetch useEffect(() => { if (!postId) return; - recordPostView(postId, post?.guid).then(res => { + recordPostView(postId, post?.guid, post?.app_id).then(res => { if (res?.counts) setCounts(c => ({ ...c, views: res.counts.view_count || c.views, likes: res.counts.like_count || c.likes })); }); fetchPostLikeState(postId).then(res => { if (res?.liked !== undefined) setLiked(res.liked); }); @@ -690,18 +690,18 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe const newLiked = !liked; setLiked(newLiked); setCounts(c => ({ ...c, likes: c.likes + (newLiked ? 1 : -1) })); - const res = await togglePostLike(postId, newLiked, post?.guid); + const res = await togglePostLike(postId, newLiked, post?.guid, post?.app_id); if (res?.counts) setCounts(c => ({ ...c, likes: res.counts.like_count ?? c.likes })); - }, [postId, liked, post?.guid]); + }, [postId, liked, post?.guid, post?.app_id]); const handleShare = useCallback(async () => { if (!postId) return; const shareUrl = `${window.location.origin}/p/${postId}`; if (navigator.share) { try { await navigator.share({ title: post?.title, url: shareUrl }); } catch {} } else { navigator.clipboard?.writeText(shareUrl); } - const res = await recordPostShare(postId, post?.guid); + const res = await recordPostShare(postId, post?.guid, post?.app_id); if (res?.counts) setCounts(c => ({ ...c, shares: res.counts.share_count ?? c.shares })); - }, [postId, post?.title, post?.guid]); + }, [postId, post?.title, post?.guid, post?.app_id]); const handleTruthVote = useCallback(async (vote) => { if (!postId) return; @@ -709,9 +709,9 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe window.dispatchEvent(new CustomEvent("sociowire:auth-required")); return; } - const res = await votePostTruth(postId, vote, post?.guid); + const res = await votePostTruth(postId, vote, post?.guid, post?.app_id); if (res) setTruth({ score: res.truth_score ?? res.user_score ?? truth.score, count: res.vote_count ?? truth.count }); - }, [postId, truth, token, post?.guid]); + }, [postId, truth, token, post?.guid, post?.app_id]); const handleComment = useCallback(async () => { const body = commentInput.trim(); @@ -727,7 +727,7 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe // Retry logic - try up to 3 times with delay let res = null; for (let attempt = 0; attempt < 3; attempt++) { - res = await createPostComment(postId, body, post?.guid); + res = await createPostComment(postId, body, post?.guid, post?.app_id); if (res?.ok) break; if (attempt < 2) await new Promise(r => setTimeout(r, 500)); // wait before retry }