diff --git a/src/backend/backend.js b/src/backend/backend.js index 42d6fca..97a6ecf 100644 --- a/src/backend/backend.js +++ b/src/backend/backend.js @@ -552,19 +552,58 @@ export function subscribePosts() { cancelled = true; }; } +/* ------------------------------------------------------------------ */ + +/* CREATE A NEW POST – uses in-memory auth + sends post_id */ + +/* ------------------------------------------------------------------ */ export async function upload(post) { - await delay(); + /* fast path: in-memory → fall back to localStorage once */ - const doc = { ...post, postID: genId(), timestamp: nowISO() }; + const token = authToken || localStorage.getItem(LS_TOKEN); - DB.posts.unshift(doc); + const user_id = authUser || localStorage.getItem(LS_USER_ID); - if (me()) me().posts.unshift(doc.postID); + if (!token || !user_id) throw new Error("Not authenticated"); - persist(); + /* Magic table requires the primary key up front */ - return { id: doc.postID }; + const post_id = genId(); // already in backend.js + + const body = { + post_id, // ← fixes NOT NULL error + + user_id, + + text: post.text ?? "", + + photoURL: post.photoURL ?? "", + + youtubeURL: post.youtubeURL ?? "", + + isPhoto: post.isPhoto ? 1 : 0, // Magic expects 1/0 + + isYoutube: post.isYoutube ? 1 : 0, + + likes: JSON.stringify(post.likes ?? []), + + comments: JSON.stringify(post.comments ?? []), + + timestamp: new Date().toISOString(), // optional but useful + }; + + /* POST /posts – let SignalR broadcast the newly created row */ + + await $fetch(POSTS_URL, { + method: "POST", + + body: JSON.stringify(body), + }); // $fetch adds Authorization + + /* keep old contract: caller expects { id } */ + + return { id: post_id }; } /* -------------------------------------------------------------- diff --git a/src/features/posts/postsSlice.js b/src/features/posts/postsSlice.js index 01306c7..b11b535 100644 --- a/src/features/posts/postsSlice.js +++ b/src/features/posts/postsSlice.js @@ -2,59 +2,65 @@ import { createSlice } from "@reduxjs/toolkit"; import mapRestPost from "../../utils/mapRestPost"; +/* -------------------------------------------------------------- + + helper – fixes the Magic placeholder only once, on INSERT + + -------------------------------------------------------------- */ + +const withValidTimestamp = (post) => + post.timestamp === "Invalid Date" || !post.timestamp + ? { ...post, timestamp: new Date().toISOString() } + : post; + export const postsSlice = createSlice({ - name: "posts", + name: "posts", - initialState: [], + initialState: [], - reducers: { - /* full reload (initial screen) --------------------------------- */ + reducers: { + /* full reload: oldest → newest, so reverse once */ - postsLoaded: (_state, action) => - // API returns oldest → newest, so reverse once + postsLoaded: (_state, action) => action.payload.map(mapRestPost).reverse(), - action.payload.map(mapRestPost).reverse(), + /* incremental updates from Signal R */ - /* incremental updates: insert new or patch existing ------------ */ + postsUpdated: (state, action) => { + action.payload.forEach((raw) => { + const postID = raw.post_id ?? raw.postID; - postsUpdated: (state, action) => { - const incoming = action.payload; // array of raw rows + const idx = state.findIndex((p) => p.postID === postID); - incoming.forEach((raw) => { - const postID = raw.post_id ?? raw.postID; + /* -------- INSERT NEW ------------------------------------------------ */ - const idx = state.findIndex((p) => p.postID === postID); + if (idx === -1) { + state.unshift(withValidTimestamp(mapRestPost(raw))); // newest first - if (idx === -1) { - /* NEW → put at the front so list stays newest-first */ + return; + } - state.unshift(mapRestPost(raw)); + /* -------- MERGE PARTIAL UPDATE ------------------------------------- */ - return; - } + const cur = state[idx]; - /* MERGE PARTIAL UPDATE (order unchanged) ------------------ */ + if (raw.text !== undefined) cur.text = raw.text; - const cur = state[idx]; + if (raw.photoURL !== undefined) cur.photoURL = raw.photoURL; - if (raw.text !== undefined) cur.text = raw.text; + if (raw.youtubeURL !== undefined) cur.youtubeURL = raw.youtubeURL; - if (raw.photoURL !== undefined) cur.photoURL = raw.photoURL; + if (raw.isPhoto !== undefined) cur.isPhoto = !!raw.isPhoto; - if (raw.youtubeURL !== undefined) cur.youtubeURL = raw.youtubeURL; + if (raw.isYoutube !== undefined) cur.isYoutube = !!raw.isYoutube; - if (raw.isPhoto !== undefined) cur.isPhoto = !!raw.isPhoto; + if (raw.comments !== undefined) cur.comments = JSON.parse(raw.comments); - if (raw.isYoutube !== undefined) cur.isYoutube = !!raw.isYoutube; + if (raw.likes !== undefined) cur.likes = JSON.parse(raw.likes); - if (raw.comments !== undefined) cur.comments = JSON.parse(raw.comments); - - if (raw.likes !== undefined) cur.likes = JSON.parse(raw.likes); - - /* raw.timestamp never changes, so we leave it untouched */ - }); - }, - }, + /* raw.timestamp never changes in an update → leave untouched */ + }); + }, + }, }); export const { postsLoaded, postsUpdated } = postsSlice.actions;