From e35464dcfa4908d89ba1efff3f597b0f5ba2fa2b Mon Sep 17 00:00:00 2001 From: Alex Erdei Date: Fri, 2 May 2025 13:58:30 +0100 Subject: [PATCH] Make posts appear in feed --- src/app/store.js | 16 +++++++- src/backend/backend.js | 63 +++++++++++++++++++++++++------- src/features/posts/postsSlice.js | 58 ++++++++++++++++++++++++++--- src/utils/mapRestPost.js | 39 ++++++++++++++++++++ 4 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 src/utils/mapRestPost.js diff --git a/src/app/store.js b/src/app/store.js index 348bddd..0b41681 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -8,7 +8,7 @@ import outgoingMessagesReducer from "../features/outgoingMessages/outgoingMessag import linkReducer from "../features/link/linkSlice"; import accountPageReducer from "../features/accountPage/accountPageSlice"; -export default configureStore({ +const store = configureStore({ reducer: { user: userReducer, currentUser: currentUserReducer, @@ -20,3 +20,17 @@ export default configureStore({ accountPage: accountPageReducer, }, }); + +if (import.meta.env.DEV) { + store.subscribe(() => { + const s = store.getState(); + + console.log("[DEBUG] users:", s.users); + + console.log("[DEBUG] currentUser:", s.currentUser); + + console.log("[DEBUG] posts:", s.posts); + }); +} + +export default store; diff --git a/src/backend/backend.js b/src/backend/backend.js index 54f0d17..66ffc74 100644 --- a/src/backend/backend.js +++ b/src/backend/backend.js @@ -22,7 +22,7 @@ import { currentUserUpdated } from "../features/currentUser/currentUserSlice"; import { usersUpdated } from "../features/users/usersSlice"; -import { postsUpdated } from "../features/posts/postsSlice"; +import { postsLoaded, postsUpdated } from "../features/posts/postsSlice"; import { incomingMessagesUpdated } from "../features/incomingMessages/incomingMessagesSlice"; @@ -40,6 +40,8 @@ const LOGIN_URL = `${API_BASE}/login`; const USERS_URL = `${API_BASE}/users`; +const POSTS_URL = `${API_BASE}/posts`; + const LS_TOKEN = "fakebook.jwt"; const LS_USER_ID = "fakebook.user_id"; @@ -149,6 +151,16 @@ export function subscribeAuth() { isEmailVerified: !!u.isEmailVerified, }) ); + + // ------------------------------------------------------------------ + + // cold-start: get the full feed once + + // ------------------------------------------------------------------ + + subscribePosts(); // <—— fetches /posts and dispatches postsLoaded + + currentUserOnline(); // mark myself online immediately } catch (err) { console.warn("[Auth] subscribeAuth failed:", err.message); @@ -229,6 +241,8 @@ export async function signInUser(user) { ); store.dispatch(errorOccured("")); + + currentUserOnline(); // mark myself online immediately } catch (err) { store.dispatch(errorOccured(err.message)); @@ -314,6 +328,14 @@ function openSocket() { hub.on("fakebook.users.put", (raw) => { store.dispatch(usersUpdated([parseMsg(raw)])); }); + + hub.on("fakebook.posts.post", (raw) => + store.dispatch(postsUpdated([JSON.parse(raw)])) + ); + + hub.on("fakebook.posts.put", (raw) => + store.dispatch(postsUpdated([JSON.parse(raw)])) + ); }) .catch((err) => { @@ -510,21 +532,36 @@ export function subscribeUsers() { }; } -/* ------------------------- posts ------------------------------------- */ +/* ------------------------------------------------------------------ */ + +/* posts subscription: replaces old in-memory mock version */ + +/* ------------------------------------------------------------------ */ export function subscribePosts() { - setTimeout(() => { - store.dispatch( - postsUpdated( - DB.posts.map((p) => ({ - ...p, - timestamp: new Date(p.timestamp).toLocaleString(), - })) - ) - ); - }, 0); + let cancelled = false; - return () => {}; + (async () => { + try { + const token = localStorage.getItem(LS_TOKEN); + + const arr = await $fetch(`${POSTS_URL}?limit=-1`, { + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }); + + if (!cancelled) { + store.dispatch(postsLoaded(arr)); // full list once + } + } catch (err) { + console.warn("[subscribePosts] failed:", err.message); + } + })(); + + /* return unsubscribe fn (keeps contract identical) */ + + return () => { + cancelled = true; + }; } export async function upload(post) { diff --git a/src/features/posts/postsSlice.js b/src/features/posts/postsSlice.js index 26dd972..c408219 100644 --- a/src/features/posts/postsSlice.js +++ b/src/features/posts/postsSlice.js @@ -1,17 +1,65 @@ import { createSlice } from "@reduxjs/toolkit"; +import mapRestPost from "../../utils/mapRestPost"; + export const postsSlice = createSlice({ name: "posts", - initialState: [], + + initialState: [], // still an array + reducers: { + /* full reload (initial screen) --------------------------------- */ + + postsLoaded: (_, action) => action.payload.map(mapRestPost), + + /* incremental updates: insert new or patch existing ------------ */ + postsUpdated: (state, action) => { - const updatedState = []; - action.payload.forEach((post) => updatedState.push(post)); - return updatedState; + const incoming = action.payload; // array of raw rows + + incoming.forEach((raw) => { + const postID = raw.post_id ?? raw.postID; + + const idx = state.findIndex((p) => p.postID === postID); + + if (idx === -1) { + /* NEW */ + + state.push(mapRestPost(raw)); + + return; + } + + /* MERGE PARTIAL UPDATE */ + + const cur = state[idx]; + + const next = { ...cur }; + + if (raw.text !== undefined) next.text = raw.text; + + if (raw.photoURL !== undefined) next.photoURL = raw.photoURL; + + if (raw.youtubeURL !== undefined) next.youtubeURL = raw.youtubeURL; + + if (raw.isPhoto !== undefined) next.isPhoto = !!raw.isPhoto; + + if (raw.isYoutube !== undefined) next.isYoutube = !!raw.isYoutube; + + if (raw.comments !== undefined) + next.comments = JSON.parse(raw.comments); + + if (raw.likes !== undefined) next.likes = JSON.parse(raw.likes); + + if (raw.timestamp !== undefined) + next.timestamp = new Date(raw.timestamp); + + state[idx] = next; + }); }, }, }); -export const { postsUpdated } = postsSlice.actions; +export const { postsLoaded, postsUpdated } = postsSlice.actions; export default postsSlice.reducer; diff --git a/src/utils/mapRestPost.js b/src/utils/mapRestPost.js new file mode 100644 index 0000000..ee6efb8 --- /dev/null +++ b/src/utils/mapRestPost.js @@ -0,0 +1,39 @@ +/* Converts the “Magic / Firebase” row into the shape the UI expects. + + – comments / likes arrive as JSON-encoded strings + + – timestamp comes as “2025-03-18 14:18:43” (UTC); we store it as Date + + – isPhoto / isYoutube arrive as 0|1 (or false|true) → boolean + +*/ + +export default function mapRestPost(row) { + /* helper – coerce all falsy / 0 / "0" / false values to boolean */ + + const bool = (v) => v === true || v === 1 || v === "1"; + + return { + postID: row.post_id, + + userID: row.user_id, + + text: row.text, + + photoURL: row.photoURL, + + youtubeURL: row.youtubeURL, + + isPhoto: bool(row.isPhoto), + + isYoutube: bool(row.isYoutube), + + comments: JSON.parse(row.comments || "[]"), + + likes: JSON.parse(row.likes || "[]"), + + /* keep the original ISO string too if you need it elsewhere */ + + timestamp: new Date(row.timestamp).toLocaleString(), + }; +}