feat: Add app_id parameter to engagement functions for proper routing

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 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-01-31 04:46:10 +00:00
parent 5d5fae63c5
commit cd9ab30ab7
3 changed files with 27 additions and 21 deletions

View File

@ -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"); if (!postId && !guid) throw new Error("post_id or guid required");
const payload = { post_id: postId, vote }; const payload = { post_id: postId, vote };
if (guid) payload.guid = guid; if (guid) payload.guid = guid;
if (appId) payload.app_id = appId;
const body = JSON.stringify(payload); const body = JSON.stringify(payload);
const res = await fetch(`${apiBase()}/post/truth-vote`, { const res = await fetch(`${apiBase()}/post/truth-vote`, {
method: "POST", 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 }; if (!postId && !guid) return { ok: false };
try { try {
const body = { post_id: postId }; const body = { post_id: postId };
if (guid) body.guid = guid; if (guid) body.guid = guid;
if (appId) body.app_id = appId;
const res = await fetch(`${apiBase()}/post/view`, { const res = await fetch(`${apiBase()}/post/view`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, 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 }; if (!postId && !guid) return { ok: false };
try { try {
const body = { post_id: postId, like }; const body = { post_id: postId, like };
if (guid) body.guid = guid; if (guid) body.guid = guid;
if (appId) body.app_id = appId;
const res = await fetch(`${apiBase()}/post/like`, { const res = await fetch(`${apiBase()}/post/like`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", ...authHeaders() }, 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 }; if (!postId && !guid) return { ok: false };
try { try {
const body = { post_id: postId }; const body = { post_id: postId };
if (guid) body.guid = guid; if (guid) body.guid = guid;
if (appId) body.app_id = appId;
const res = await fetch(`${apiBase()}/post/share`, { const res = await fetch(`${apiBase()}/post/share`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, 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 []; if (!postId && !guid) return [];
const qs = new URLSearchParams(); const qs = new URLSearchParams();
qs.set("post_id", String(postId)); qs.set("post_id", String(postId));
if (guid) qs.set("guid", guid); if (guid) qs.set("guid", guid);
if (appId) qs.set("app_id", appId);
if (limit) qs.set("limit", String(limit)); if (limit) qs.set("limit", String(limit));
try { try {
const res = await fetch(`${apiBase()}/post/comments?${qs.toString()}`, { 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 (!postId && !guid) return { ok: false };
if (!body) return { ok: false }; if (!body) return { ok: false };
try { try {
const reqBody = { post_id: postId, body }; const reqBody = { post_id: postId, body };
if (guid) reqBody.guid = guid; if (guid) reqBody.guid = guid;
if (appId) reqBody.app_id = appId;
const res = await fetch(`${apiBase()}/post/comment`, { const res = await fetch(`${apiBase()}/post/comment`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", ...authHeaders() }, headers: { "Content-Type": "application/json", ...authHeaders() },

View File

@ -1812,7 +1812,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
if ((postID > 0 || postData?.guid) && !modalWrap.__swViewed) { if ((postID > 0 || postData?.guid) && !modalWrap.__swViewed) {
modalWrap.__swViewed = true; modalWrap.__swViewed = true;
recordPostView(postID, postData?.guid).then((res) => { recordPostView(postID, postData?.guid, postData?.app_id).then((res) => {
const counts = res?.counts; const counts = res?.counts;
if (counts) { if (counts) {
updateStat(modalWrap, "views", counts.view_count); 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" }); trackEvent("post_like_click", { post_id: postID, context: "map" });
likeBtn.disabled = true; likeBtn.disabled = true;
const res = await togglePostLike(postID, !liked); const res = await togglePostLike(postID, !liked, postData?.guid, postData?.app_id);
if (res?.ok) { if (res?.ok) {
liked = !!res.liked; liked = !!res.liked;
likeBtn.textContent = liked ? "Liked" : "Like"; likeBtn.textContent = liked ? "Liked" : "Like";
@ -1889,7 +1889,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
await navigator.clipboard.writeText(shareUrl); await navigator.clipboard.writeText(shareUrl);
} }
} catch {} } catch {}
const res = await recordPostShare(postID, postData?.guid); const res = await recordPostShare(postID, postData?.guid, postData?.app_id);
const counts = res?.counts; const counts = res?.counts;
if (counts) { if (counts) {
updateStat(modalWrap, "views", counts.view_count); updateStat(modalWrap, "views", counts.view_count);
@ -1945,7 +1945,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
try { try {
trackEvent("truth_vote_click", { post_id: postID || postData?.guid, vote, context: "map" }); 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) { if (stats) {
currentVote = vote; currentVote = vote;
updateButtonStyles(); updateButtonStyles();
@ -2188,7 +2188,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
const commentsBody = modalWrap.querySelector(".sw-livechat-body"); const commentsBody = modalWrap.querySelector(".sw-livechat-body");
if (commentsBody && postID > 0) { 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) { if (commentBtn && commentInput && postID > 0) {
commentBtn.addEventListener("click", async () => { commentBtn.addEventListener("click", async () => {
@ -2200,10 +2200,10 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
if (!body) return; if (!body) return;
trackEvent("post_comment_submit", { post_id: postID, context: "map" }); trackEvent("post_comment_submit", { post_id: postID, context: "map" });
commentBtn.disabled = true; 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) { if (res?.ok) {
commentInput.value = ""; commentInput.value = "";
const existing = await fetchPostComments(postID, 20, postData?.guid); const existing = await fetchPostComments(postID, 20, postData?.guid, postData?.app_id);
renderComments(commentsBody, existing); renderComments(commentsBody, existing);
const counts = res?.counts; const counts = res?.counts;
if (counts) { if (counts) {

View File

@ -624,7 +624,7 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe
// Initial data fetch // Initial data fetch
useEffect(() => { useEffect(() => {
if (!postId) return; 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 })); 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); }); 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; const newLiked = !liked;
setLiked(newLiked); setLiked(newLiked);
setCounts(c => ({ ...c, likes: c.likes + (newLiked ? 1 : -1) })); 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 })); 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 () => { const handleShare = useCallback(async () => {
if (!postId) return; if (!postId) return;
const shareUrl = `${window.location.origin}/p/${postId}`; const shareUrl = `${window.location.origin}/p/${postId}`;
if (navigator.share) { try { await navigator.share({ title: post?.title, url: shareUrl }); } catch {} } if (navigator.share) { try { await navigator.share({ title: post?.title, url: shareUrl }); } catch {} }
else { navigator.clipboard?.writeText(shareUrl); } 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 })); 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) => { const handleTruthVote = useCallback(async (vote) => {
if (!postId) return; if (!postId) return;
@ -709,9 +709,9 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe
window.dispatchEvent(new CustomEvent("sociowire:auth-required")); window.dispatchEvent(new CustomEvent("sociowire:auth-required"));
return; 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 }); 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 handleComment = useCallback(async () => {
const body = commentInput.trim(); 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 // Retry logic - try up to 3 times with delay
let res = null; let res = null;
for (let attempt = 0; attempt < 3; attempt++) { 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 (res?.ok) break;
if (attempt < 2) await new Promise(r => setTimeout(r, 500)); // wait before retry if (attempt < 2) await new Promise(r => setTimeout(r, 500)); // wait before retry
} }