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");
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() },

View File

@ -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) {

View File

@ -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
}