diff --git a/src/backend/backend.js b/src/backend/backend.js
index 7a5270c..2500ff7 100644
--- a/src/backend/backend.js
+++ b/src/backend/backend.js
@@ -1,10 +1,4 @@
-/* =====================================================================
-
-
- Fakebook — back-end file (AUTH + mock social features)
-
-
- ===================================================================== */
+// backend.js -------------------------------------------------------------
import * as signalR from "@microsoft/signalr";
@@ -30,69 +24,59 @@ import { outgoingMessagesUpdated } from "../features/outgoingMessages/outgoingMe
import { mapRestMessage } from "../utils/mapRestMessage";
-/* --------------------------- CONSTANTS -------------------------------- */
+// urls / constants -------------------------------------------------------
-const API_BASE = "https://alexerdei-team.us.ainiro.io/magic/modules/fakebook";
+const API = "https://alexerdei-team.us.ainiro.io/magic/modules/fakebook";
-const SOCKETS_URL = "wss://alexerdei-team.us.ainiro.io/sockets";
+const SOCKETS = "wss://alexerdei-team.us.ainiro.io/sockets";
-const REGISTER_URL = `${API_BASE}/register`;
+const USERS_URL = `${API}/users`;
-const LOGIN_URL = `${API_BASE}/login`;
+const POSTS_URL = `${API}/posts`;
-const USERS_URL = `${API_BASE}/users`;
+const MSG_URL = `${API}/message`;
-const POSTS_URL = `${API_BASE}/posts`;
+const IMAGE_URL = `${API}/image`;
const LS_TOKEN = "fakebook.jwt";
-const LS_USER_ID = "fakebook.user_id";
+const LS_UID = "fakebook.user_id";
-/* --------------------------- SESSION (NEW) ----------------------------- */
-
-/* In-memory copy – null while logged-out */
+// auth state -------------------------------------------------------------
let authToken = null;
let authUser = null;
-/* Header helper – import this anywhere you need the bearer token */
-
const authHeader = () =>
authToken ? { Authorization: `Bearer ${authToken}` } : {};
-/* Initialise / clear session */
+// localStorage helpers ---------------------------------------------------
-function setAuth(token, user_id) {
- authToken = token;
+const loadAuthFromStorage = () => {
+ authToken = localStorage.getItem(LS_TOKEN);
+ authUser = localStorage.getItem(LS_UID);
+};
- authUser = user_id;
+const saveAuthToStorage = (t, u) => {
+ localStorage.setItem(LS_TOKEN, t);
+ localStorage.setItem(LS_UID, u);
+};
- localStorage.setItem(LS_TOKEN, token);
+const clearAuthStorage = () => {
+ localStorage.removeItem(LS_TOKEN);
+ localStorage.removeItem(LS_UID);
+};
- localStorage.setItem(LS_USER_ID, user_id);
-
- openSocket();
-}
-
-/* --------------------------- Utilities -------------------------------- */
-
-const genId = () => Math.random().toString(36).slice(2, 11);
-
-const delay = (ms = 200) => new Promise((r) => setTimeout(r, ms));
-
-async function $fetch(url, opts = {}) {
- /* inject bearer automatically */
+// fetch helper -----------------------------------------------------------
+const $fetch = async (url, opts = {}) => {
const res = await fetch(url, {
headers: {
"Content-Type": "application/json",
-
...authHeader(),
-
...(opts.headers || {}),
},
-
...opts,
});
@@ -101,206 +85,25 @@ async function $fetch(url, opts = {}) {
if (!res.ok) throw new Error(data.message || res.statusText);
return data;
-}
+};
-/* ===================================================================== *
-
-
- SECTION A — REAL AUTH WORKFLOW
-
-
- ===================================================================== */
+// misc helpers -----------------------------------------------------------
-/* =========================================================================
+const genId = () => Math.random().toString(36).slice(2, 11);
- BOOTSTRAP SESSION – used by subscribeAuth() *and* signInUser()
-
- ====================================================================== */
-
-/**
-
- * Fetches all users, finds the logged-in user, updates Redux,
-
- * cold-starts the post feed, and marks the user online.
-
- *
-
- * Throws if the user row cannot be found.
-
- */
-
-async function bootstrapSession(user_id) {
- // 1. fetch all users (Magic API has no `/users/:id`)
-
- const users = await $fetch(`${USERS_URL}?limit=-1`);
-
- const meRow = users.find((u) => u.user_id === user_id);
-
- if (!meRow) throw new Error("User not found");
-
- // 2. update the auth slice (for navbar, etc.)
-
- store.dispatch(
- signIn({
- id: user_id,
-
- displayName: `${meRow.firstname} ${meRow.lastname}`,
-
- isEmailVerified: !!meRow.isEmailVerified,
- })
- );
-
- // 3. populate currentUser slice (fixes “missing photos / posts”)
-
- store.dispatch(currentUserUpdated(meRow));
-
- // 4. cold-start posts feed and mark myself online
-
- subscribePosts();
-
- currentUserOnline();
-
- /* inside bootstrapSession(), after openSocket() + currentUserOnline() */
-
- subscribeMessages("incoming");
-
- subscribeMessages("outgoing");
-}
-
-export function subscribeAuth() {
- store.dispatch(loadingStarted());
-
- (async () => {
- const token = localStorage.getItem(LS_TOKEN);
-
- const user_id = localStorage.getItem(LS_USER_ID);
-
- if (!token || !user_id) {
- clearAuth();
-
- store.dispatch(signOut());
-
- store.dispatch(loadingFinished());
-
- return;
- }
-
- setAuth(token, user_id);
-
- try {
- await bootstrapSession(user_id); // <── single shared call
- } catch (err) {
- console.warn("[Auth] subscribeAuth failed:", err.message);
-
- clearAuth();
-
- store.dispatch(signOut());
- } finally {
- store.dispatch(loadingFinished());
- }
- })();
-
- return () => {};
-}
-/* ---------------------- createUserAccount ---------------------------- */
-
-export async function createUserAccount(user) {
- store.dispatch(loadingStarted());
-
- try {
- await $fetch(REGISTER_URL, {
- method: "POST",
-
- body: JSON.stringify({
- firstname: user.firstname,
-
- lastname: user.lastname,
-
- email: user.email,
-
- password: user.password,
- }),
- });
-
- store.dispatch(errorOccured(""));
- } catch (err) {
- store.dispatch(errorOccured(err.message));
- } finally {
- store.dispatch(loadingFinished());
- }
-}
-
-export async function signInUser(user) {
- store.dispatch(loadingStarted());
-
- try {
- // 1. login
-
- const url = `${LOGIN_URL}?email=${encodeURIComponent(
- user.email
- )}&password=${encodeURIComponent(user.password)}`;
-
- const { token, user_id } = await $fetch(url);
-
- // 2. persist token + open socket
-
- setAuth(token, user_id);
-
- // 3. reuse the exact same bootstrap logic
-
- await bootstrapSession(user_id);
-
- store.dispatch(errorOccured("")); // clear possible old errors
- } catch (err) {
- store.dispatch(errorOccured(err.message));
-
- clearAuth();
- } finally {
- store.dispatch(loadingFinished());
- }
-}
-
-/* ---------------------- signUserOut ---------------------------------- */
-
-export async function signUserOut() {
- await patchOnline(false); /* mark offline */
-
- clearAuth();
-
- store.dispatch(signOut());
-}
-
-/* -------------------- sendPasswordReminder --------------------------- */
-
-export function sendPasswordReminder(email) {
- console.info("[TODO] Implement password reminder for", email);
-
- return Promise.resolve();
-}
-
-/* ------------------------------------------------------------------ */
-
-/* SignalR hub – one connection shared across the app */
-
-/* ------------------------------------------------------------------ */
+// socket -----------------------------------------------------------------
let hub = null;
-function openSocket() {
- if (hub) return; // already connected / connecting
-
- const token = localStorage.getItem(LS_TOKEN);
-
- if (!token) return; // not logged-in → no live updates
+const openSocket = () => {
+ if (hub || !authToken) return;
hub = new signalR.HubConnectionBuilder()
- .withUrl(SOCKETS_URL, {
- accessTokenFactory: () => token,
-
- skipNegotiation: true, // no CORS pre-flight
-
- transport: signalR.HttpTransportType.WebSockets, // WebSocket only
+ .withUrl(SOCKETS, {
+ accessTokenFactory: () => authToken,
+ skipNegotiation: true,
+ transport: signalR.HttpTransportType.WebSockets,
})
.withAutomaticReconnect()
@@ -309,461 +112,398 @@ function openSocket() {
.build();
- /* ---------------------------------------------------- */
-
- /* helper – SignalR sends a JSON-string → return POJO */
-
- /* ---------------------------------------------------- */
-
- /* helper – does the row belong to me? */
-
- const isMe = (row) => row && row.user_id === localStorage.getItem(LS_USER_ID);
-
- /* 1️⃣ Start first … */
-
hub
.start()
-
.then(() => {
- console.info("[SignalR] connected, id:", hub.connectionId);
-
- /* 2️⃣ … then register listeners ----------------------------- */
-
- /* inside openSocket() – unchanged except the helper */
-
- hub.on("fakebook.users.post", (raw) => {
- store.dispatch(usersUpdated([JSON.parse(raw)]));
- });
+ hub.on("fakebook.users.post", (raw) =>
+ store.dispatch(usersUpdated([JSON.parse(raw)]))
+ );
hub.on("fakebook.users.put", (raw) => {
const row = JSON.parse(raw);
store.dispatch(usersUpdated([row]));
- if (isMe(row)) store.dispatch(currentUserUpdated(row));
+ if (row.user_id === authUser) store.dispatch(currentUserUpdated(row));
});
- hub.on("fakebook.posts.post", (raw) => {
- store.dispatch(postsUpdated([JSON.parse(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)]));
- });
-
- /* helper – my uid so we filter only my messages */
-
- const myUID = () => localStorage.getItem(LS_USER_ID);
-
- /* ---------- new message created -------------------------------- */
+ hub.on("fakebook.posts.put", (raw) =>
+ store.dispatch(postsUpdated([JSON.parse(raw)]))
+ );
hub.on("fakebook.message.post", (raw) => {
- const msg = mapRestMessage(JSON.parse(raw)); // ← reuse the mapper
+ const msg = mapRestMessage(JSON.parse(raw));
- if (msg.recipient === myUID())
+ if (msg.recipient === authUser)
store.dispatch(incomingMessagesUpdated([msg]));
- if (msg.sender === myUID())
+ if (msg.sender === authUser)
store.dispatch(outgoingMessagesUpdated([msg]));
});
})
-
.catch((err) => {
console.warn("[SignalR] start failed:", err);
-
- hub = null; // let auto-reconnect retry
+ hub = null;
});
+};
- /* extra diagnostics ---------------------------------------------- */
+// auth helpers -----------------------------------------------------------
- hub.onreconnecting((err) => console.warn("[SignalR] reconnecting:", err));
-
- hub.onreconnected((id) => console.info("[SignalR] reconnected, id:", id));
-
- hub.onclose((err) => console.warn("[SignalR] closed:", err));
-}
-
-/* ------------------------------------------------------------------ */
-
-/* Close SignalR + forget credentials */
-
-/* ------------------------------------------------------------------ */
-
-function clearAuth() {
- /* forget in-memory copies */
+const setAuth = (t, u) => {
+ authToken = t;
+ authUser = u;
+ saveAuthToStorage(t, u);
+ openSocket();
+};
+const clearAuth = () => {
authToken = null;
-
authUser = null;
-
- /* forget persisted copies */
-
- localStorage.removeItem(LS_TOKEN); // "fakebook.jwt"
-
- localStorage.removeItem(LS_USER_ID); // "fakebook.user_id"
-
- /* close the hub if it exists */
-
+ clearAuthStorage();
if (hub) {
- hub.stop(); // graceful shutdown → returns a promise we don’t await
-
+ hub.stop();
hub = null;
}
-}
+};
-/* ===================================================================== *
-
- SECTION B — IN-MEMORY MOCK (UNCHANGED) *
-
- ===================================================================== */
+// presence ---------------------------------------------------------------
-/* ------------- tiny DB (persists to localStorage for demo) ----------- */
-
-const LS_DB = "__fakebook_mock_db__";
-
-const DB = Object.assign(
- {
- currentUser: null,
-
- users: [],
-
- posts: [],
-
- messages: [],
- },
-
- JSON.parse(localStorage.getItem(LS_DB) || "{}")
-);
-
-const persist = () => localStorage.setItem(LS_DB, JSON.stringify(DB));
-
-const me = () => DB.users.find((u) => u.userID === DB.currentUser?.id);
-
-const nowISO = () => new Date().toISOString();
-
-/* --------------------- generic helpers ------------------------------- */
-
-export async function getImageURL(path) {
- return `/assets/${path}`;
-}
-
-/* ------------------- current user subscriptions ---------------------- */
-
-/* ------------------------------------------------------------------ */
-
-/* Current user document */
-
-/* ------------------------------------------------------------------ */
-
-export function subscribeCurrentUser() {
- let cancelled = false;
-
- (async () => {
- try {
- const token = localStorage.getItem(LS_TOKEN);
-
- const user_id = localStorage.getItem(LS_USER_ID);
-
- if (!token || !user_id) return;
-
- const users = await $fetch(`${USERS_URL}?limit=-1`, {
- headers: { Authorization: `Bearer ${token}` },
- });
-
- const meRow = users.find((u) => u.user_id === user_id);
-
- if (meRow && !cancelled) {
- store.dispatch(currentUserUpdated(meRow));
- }
- } catch (err) {
- console.warn("[subscribeCurrentUser] failed:", err.message);
- }
- })();
-
- return () => {
- cancelled = true;
- };
-}
-/* ------------------------------------------------------------------ */
-
-/* Online / offline flag */
-
-/* ------------------------------------------------------------------ */
-async function patchOnline(isOnline) {
- const token = localStorage.getItem(LS_TOKEN);
-
- const user_id = localStorage.getItem(LS_USER_ID);
-
- if (!token || !user_id) return;
+const patchOnline = async (on) => {
+ if (!authUser) return;
try {
await $fetch(USERS_URL, {
method: "PUT",
-
- body: JSON.stringify({
- user_id,
-
- isOnline: isOnline ? 1 : 0, // DB needs 1/0
- }),
+ body: JSON.stringify({ user_id: authUser, isOnline: on ? 1 : 0 }),
});
- } catch (err) {
- console.warn("[online/offline] PUT failed:", err.message);
+ } catch (e) {
+ console.warn("[online] PUT failed:", e.message);
}
-}
+};
export const currentUserOnline = () => patchOnline(true);
export const currentUserOffline = () => patchOnline(false);
-/* ------------------------- users list -------------------------------- */
+// bootstrap after login/restore -----------------------------------------
-export function subscribeUsers() {
+const bootstrapSession = async (uid) => {
+ const users = await $fetch(`${USERS_URL}?limit=-1`);
+
+ const me = users.find((u) => u.user_id === uid);
+
+ if (!me) throw new Error("User not found");
+
+ store.dispatch(
+ signIn({
+ id: uid,
+ displayName: `${me.firstname} ${me.lastname}`,
+ isEmailVerified: !!me.isEmailVerified,
+ })
+ );
+
+ store.dispatch(currentUserUpdated(me));
+
+ subscribePosts();
+
+ currentUserOnline();
+
+ subscribeMessages("incoming");
+
+ subscribeMessages("outgoing");
+};
+
+// public auth api --------------------------------------------------------
+
+export const subscribeAuth = () => {
+ store.dispatch(loadingStarted());
+
+ loadAuthFromStorage();
+
+ if (!authToken || !authUser) {
+ clearAuth();
+ store.dispatch(signOut());
+ store.dispatch(loadingFinished());
+ return () => {};
+ }
+
+ setAuth(authToken, authUser);
+
+ bootstrapSession(authUser)
+ .catch((err) => {
+ console.warn("[Auth] bootstrap failed:", err.message);
+ clearAuth();
+ store.dispatch(signOut());
+ })
+
+ .finally(() => store.dispatch(loadingFinished()));
+
+ return () => {};
+};
+
+export const createUserAccount = async (u) => {
+ store.dispatch(loadingStarted());
+
+ try {
+ await $fetch(`${API}/register`, {
+ method: "POST",
+ body: JSON.stringify(u),
+ });
+ store.dispatch(errorOccured(""));
+ } catch (e) {
+ store.dispatch(errorOccured(e.message));
+ }
+
+ store.dispatch(loadingFinished());
+};
+
+export const signInUser = async (u) => {
+ store.dispatch(loadingStarted());
+
+ try {
+ const { token, user_id } = await $fetch(
+ `${API}/login?email=${encodeURIComponent(
+ u.email
+ )}&password=${encodeURIComponent(u.password)}`
+ );
+
+ setAuth(token, user_id);
+ await bootstrapSession(user_id);
+ store.dispatch(errorOccured(""));
+ } catch (e) {
+ store.dispatch(errorOccured(e.message));
+ clearAuth();
+ }
+
+ store.dispatch(loadingFinished());
+};
+
+export const signUserOut = async () => {
+ await patchOnline(false);
+ clearAuth();
+ store.dispatch(signOut());
+};
+
+// password reminder ------------------------------------------------------
+
+export const sendPasswordReminder = async (email) => {
+ // no real endpoint yet – stub keeps call-sites happy
+
+ console.info("[TODO] send password reminder for", email);
+
+ return Promise.resolve();
+};
+
+// users subscription -----------------------------------------------------
+
+export const subscribeUsers = () => {
let cancelled = false;
(async () => {
try {
- const token = localStorage.getItem(LS_TOKEN);
-
- const users = await $fetch(`${USERS_URL}?limit=-1`, {
- headers: token ? { Authorization: `Bearer ${token}` } : {},
- });
-
- if (!cancelled) {
- store.dispatch(usersUpdated(users));
- }
- } catch (err) {
- console.warn("[subscribeUsers] failed:", err.message);
+ const u = await $fetch(`${USERS_URL}?limit=-1`);
+ if (!cancelled) store.dispatch(usersUpdated(u));
+ } catch (e) {
+ console.warn("[subscribeUsers] failed:", e.message);
}
})();
- /* return unsubscribe fn to keep same contract */
-
return () => {
cancelled = true;
};
-}
+};
-/* ------------------------------------------------------------------ */
+// posts ------------------------------------------------------------------
-/* posts subscription: replaces old in-memory mock version */
-
-/* ------------------------------------------------------------------ */
-
-export function subscribePosts() {
+export const subscribePosts = () => {
let cancelled = false;
(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);
+ const p = await $fetch(`${POSTS_URL}?limit=-1`);
+ if (!cancelled) store.dispatch(postsLoaded(p));
+ } catch (e) {
+ console.warn("[subscribePosts] failed:", e.message);
}
})();
- /* return unsubscribe fn (keeps contract identical) */
-
return () => {
cancelled = true;
};
-}
-/* ------------------------------------------------------------------ */
+};
-/* CREATE A NEW POST – uses in-memory auth + sends post_id */
+export const upload = async (p) => {
+ if (!authUser) throw new Error("Not authenticated");
-/* ------------------------------------------------------------------ */
-
-export async function upload(post) {
- /* fast path: in-memory → fall back to localStorage once */
-
- const token = authToken || localStorage.getItem(LS_TOKEN);
-
- const user_id = authUser || localStorage.getItem(LS_USER_ID);
-
- if (!token || !user_id) throw new Error("Not authenticated");
-
- /* Magic table requires the primary key up front */
-
- 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 */
+ const post_id = genId();
await $fetch(POSTS_URL, {
method: "POST",
-
- body: JSON.stringify(body),
- }); // $fetch adds Authorization
-
- /* --------------------------------------------------------------
-
- 2️⃣ Update my users.posts array (DB + Redux)
-
- -------------------------------------------------------------- */
+ body: JSON.stringify({
+ post_id,
+ user_id: authUser,
+ text: p.text ?? "",
+ photoURL: p.photoURL ?? "",
+ youtubeURL: p.youtubeURL ?? "",
+ isPhoto: p.isPhoto ? 1 : 0,
+ isYoutube: p.isYoutube ? 1 : 0,
+ likes: JSON.stringify(p.likes ?? []),
+ comments: JSON.stringify(p.comments ?? []),
+ timestamp: new Date().toISOString(),
+ }),
+ });
try {
- const state = store.getState();
-
- const me = state.currentUser; // already normalised
-
- const currentPosts = me?.posts ?? [];
-
- const updatedPosts = [...currentPosts, post_id];
-
- /* 2a. Persist to the server ---------------------------------- */
+ const curPosts = JSON.parse(store.getState().currentUser?.posts ?? "[]");
await $fetch(USERS_URL, {
method: "PUT",
-
body: JSON.stringify({
- user_id,
-
- posts: JSON.stringify(updatedPosts),
+ user_id: authUser,
+ posts: JSON.stringify([...curPosts, post_id]),
}),
});
- } catch (err) {
- console.warn("[upload] failed to patch users.posts:", err.message);
+ } catch (e) {
+ console.warn("[uploadPost] users.posts PUT failed:", e.message);
}
- /* keep old contract: caller expects { id } */
return { id: post_id };
-}
+};
-/* --------------------------------------------------------------
+export const updatePost = async (patch, id) => {
+ if (!authToken) throw new Error("Not authenticated");
- Update a post (likes, comments, text, etc.)
+ const body = { post_id: id };
- post → partial object in Redux/UI format
+ if (patch.comments !== undefined)
+ body.comments = JSON.stringify(patch.comments);
- postID → numeric/string id in the REST DB
+ if (patch.likes !== undefined) body.likes = JSON.stringify(patch.likes);
- -------------------------------------------------------------- */
+ await $fetch(POSTS_URL, { method: "PUT", body: JSON.stringify(body) });
+};
-export async function updatePost(post, postID) {
- const token = localStorage.getItem(LS_TOKEN);
+// messages ---------------------------------------------------------------
- if (!token) throw new Error("Not authenticated");
+export const subscribeMessages = (kind) => {
+ let cancelled = false;
- /* 1. Map Redux-shape → Magic API shape ----------------------- */
+ (async () => {
+ try {
+ if (!authUser) return;
- const body = { post_id: postID }; // mandatory key
+ const filter =
+ kind === "incoming"
+ ? `message.recipient.eq=${authUser}`
+ : `message.sender.eq=${authUser}`;
- if (post.comments !== undefined)
- body.comments = JSON.stringify(post.comments);
+ const rows = await $fetch(`${MSG_URL}?limit=-1&${filter}`);
- if (post.likes !== undefined) body.likes = JSON.stringify(post.likes);
+ const mapped = rows.map(mapRestMessage);
- /* 2. Fire PUT /posts ---------------------------------------- */
+ if (!cancelled)
+ kind === "incoming"
+ ? store.dispatch(incomingMessagesUpdated(mapped))
+ : store.dispatch(outgoingMessagesUpdated(mapped));
+ } catch (e) {
+ console.warn("[subscribeMessages] failed:", e.message);
+ }
+ })();
- await $fetch(POSTS_URL, {
- method: "PUT",
+ return () => {
+ cancelled = true;
+ };
+};
- body: JSON.stringify(body),
+export const uploadMessage = async (m) => {
+ if (!authUser) throw new Error("Not authenticated");
+
+ const message_id = genId();
+
+ await $fetch(MSG_URL, {
+ method: "POST",
+ body: JSON.stringify({
+ message_id,
+ sender: authUser,
+ recipient: m.recipient,
+ text: m.text ?? "",
+ photoURL: m.photoURL ?? "",
+ isPhoto: m.photoURL ? 1 : 0,
+ isRead: 0,
+ }),
});
-}
-/* =====================================================================
+ return { id: message_id };
+};
- PHOTO / PROFILE HELPERS
+export const updateToBeRead = async (id) => {
+ const patch = [{ message_id: id, isRead: 1 }];
- ===================================================================== */
+ store.dispatch(incomingMessagesUpdated(patch));
-const IMAGE_UPLOAD_URL = `${API_BASE}/image`; // POST image
+ store.dispatch(outgoingMessagesUpdated(patch));
-/* --------------------------------------------------------------
-
- addFileToStorage
-
- -------------------------------------------------------------- */
+ try {
+ await $fetch(MSG_URL, {
+ method: "PUT",
+ body: JSON.stringify({ message_id: id, isRead: 1 }),
+ });
+ } catch (e) {
+ console.warn("[updateToBeRead] PUT failed:", e.message);
+ }
+};
-/* file → native File object coming from */
+// currentUser refresh ----------------------------------------------------
-/* RETURNS: { url, path, ... } exactly what the Magic endpoint sends */
+export const subscribeCurrentUser = () => {
+ let cancelled = false;
-export async function addFileToStorage(file) {
- if (!file) throw new Error("No file given");
+ (async () => {
+ try {
+ if (!authUser) return;
+
+ const users = await $fetch(`${USERS_URL}?limit=-1`);
+
+ const me = users.find((u) => u.user_id === authUser);
+
+ if (me && !cancelled) store.dispatch(currentUserUpdated(me));
+ } catch (e) {
+ console.warn("[subscribeCurrentUser] failed:", e.message);
+ }
+ })();
+
+ return () => {
+ cancelled = true;
+ };
+};
+
+// files & profile --------------------------------------------------------
+
+export const addFileToStorage = async (file) => {
+ if (!file) throw new Error("No file");
const fd = new FormData();
-
fd.append("file", file, file.name);
- const res = await fetch(IMAGE_UPLOAD_URL, {
+ const res = await fetch(IMAGE_URL, {
method: "POST",
-
- headers: {
- ...authHeader(), // adds Authorization if we’re logged in
-
- // DO NOT set Content-Type – the browser will add multipart boundary
- },
-
+ headers: { ...authHeader() },
body: fd,
});
- if (!res.ok) {
- const msg = await res.text();
+ if (!res.ok) throw new Error(await res.text());
- throw new Error(`Image upload failed: ${msg || res.statusText}`);
- }
+ return res.json();
+};
- /* Magic returns JSON with at least { url } (and sometimes path, size…) */
+export const updateProfile = async (patch) => {
+ if (!authUser) throw new Error("Not authenticated");
- const data = await res.json();
-
- return data; // UploadPhoto.jsx ignores, updateDatabase uses
-}
-
-/* --------------------------------------------------------------
-
- updateProfile
-
- -------------------------------------------------------------- */
-
-/* patch → only the fields that changed, e.g. */
-
-/* { profilePictureURL: "...jpg" } */
-
-/* { photos: ["p1.jpg", "p2.jpg"] } */
-
-/* Updates DB and refreshes Redux */
-
-export async function updateProfile(patch) {
- const token = localStorage.getItem(LS_TOKEN);
-
- const user_id = localStorage.getItem(LS_USER_ID);
-
- if (!token || !user_id) throw new Error("Not authenticated");
-
- /* Shape request body exactly like the Magic API expects -------- */
-
- const body = { user_id };
+ const body = { user_id: authUser };
if (patch.profilePictureURL !== undefined)
body.profilePictureURL = patch.profilePictureURL;
@@ -771,168 +511,18 @@ export async function updateProfile(patch) {
if (patch.backgroundPictureURL !== undefined)
body.backgroundPictureURL = patch.backgroundPictureURL;
- if (patch.photos !== undefined)
- // array → JSON string
+ if (patch.photos !== undefined) body.photos = JSON.stringify(patch.photos);
- body.photos = JSON.stringify(patch.photos);
-
- /* Fire PUT /users --------------------------------------------- */
-
- const updatedRow = await $fetch(USERS_URL, {
+ const updated = await $fetch(USERS_URL, {
method: "PUT",
-
- body: JSON.stringify(body),
- }); // $fetch auto-adds headers
-
- /* Update Redux immediately for smooth UX ---------------------- */
-
- store.dispatch(currentUserUpdated(updatedRow));
-
- store.dispatch(usersUpdated([updatedRow]));
-
- /* When the hub later sends fakebook.users.put the same row will
-
- merge in again; that’s harmless. */
-}
-/* =====================================================================
-
-
- MESSAGES – read-only bootstrap (Magic back-end)
-
-
- ===================================================================== */
-
-const MESSAGE_URL = `${API_BASE}/message`; // ← singular table name
-
-/* --------------------------------------------------------------
-
- subscribeMessages(kind)
-
-
- kind = "incoming" | "outgoing"
-
- → issues ONE network call with server-side filter
-
- -------------------------------------------------------------- */
-
-export function subscribeMessages(kind = "incoming") {
- let cancelled = false;
-
- (async () => {
- try {
- const uid = localStorage.getItem(LS_USER_ID);
-
- if (!uid) return;
-
- /* Magic filter param: message.sender.eq= etc. */
-
- const filter =
- kind === "incoming"
- ? `message.recipient.eq=${uid}`
- : `message.sender.eq=${uid}`;
-
- const url = `${MESSAGE_URL}?limit=-1&${filter}`;
-
- const rows = await $fetch(url); // $fetch auto adds auth
-
- if (!cancelled) {
- const mapped = rows.map(mapRestMessage);
-
- store.dispatch(
- (kind === "incoming"
- ? incomingMessagesUpdated
- : outgoingMessagesUpdated)(mapped)
- );
- }
- } catch (err) {
- console.warn("[subscribeMessages] failed:", err.message);
- }
- })();
-
- return () => {
- cancelled = true;
- };
-}
-
-/* --------------------------------------------------------------
-
- uploadMessage(msg)
-
-
- msg = { recipient, text, photoURL? }
-
- RETURNS { id } // new message_id
-
- -------------------------------------------------------------- */
-
-export async function uploadMessage(msg) {
- const sender = localStorage.getItem(LS_USER_ID);
-
- if (!sender) throw new Error("Not authenticated");
-
- const message_id = genId(); // Magic table PK
-
- const body = {
- message_id,
-
- sender, // who sends
-
- recipient: msg.recipient, // who receives
-
- text: msg.text ?? "",
-
- photoURL: msg.photoURL ?? "",
-
- isPhoto: msg.photoURL ? 1 : 0,
-
- isRead: 0, // unread on insert
-
- /* timestamp is generated by the server */
- };
-
- await $fetch(`${API_BASE}/message`, {
- method: "POST",
-
body: JSON.stringify(body),
});
- /* No local dispatch necessary – the hub’s `message.post` event
-
- will arrive in a few ms and update Redux for both parties. */
+ store.dispatch(currentUserUpdated(updated));
- return { id: message_id };
-}
+ store.dispatch(usersUpdated([updated]));
+};
-/* ------------------------------------------------------------------
+// misc helper ------------------------------------------------------------
- updateToBeRead(message_id)
-
- – flips the flag in Redux immediately (optimistic)
-
- – then notifies the server; no need to wait for a hub echo
-
------------------------------------------------------------------- */
-
-export async function updateToBeRead(message_id) {
- /* 1️⃣ optimistic local patch (affects whichever slice holds the row) */
-
- const patch = [{ message_id, isRead: 1 }];
-
- store.dispatch(incomingMessagesUpdated(patch));
-
- store.dispatch(outgoingMessagesUpdated(patch));
-
- /* 2️⃣ tell the server – ignore the hub echo, we no longer listen */
-
- try {
- await $fetch(`${API_BASE}/message`, {
- method: "PUT",
-
- body: JSON.stringify({ message_id, isRead: 1 }),
- });
- } catch (err) {
- console.warn("[updateToBeRead] PUT failed:", err.message);
-
- // Optional: revert optimistic change on error
- }
-}
+export const getImageURL = async (path) => `/assets/${path}`;