From 808b918894171efe33f25990e051b52877f3d4ba Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev Date: Thu, 19 Feb 2026 14:45:31 +0700 Subject: [PATCH] Improve SSR auth bootstrap and align GraphQL communication fields --- Frontend/app.vue | 56 +++++++++++++++++++++++++------ Frontend/prisma/seed.mjs | 4 +-- Frontend/server/graphql/schema.ts | 3 +- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/Frontend/app.vue b/Frontend/app.vue index 7e1b70c..97171b8 100644 --- a/Frontend/app.vue +++ b/Frontend/app.vue @@ -486,8 +486,10 @@ const renderedPilotMessages = computed(() => { }); async function gqlFetch(query: string, variables?: Record) { + const headers = process.server ? useRequestHeaders(["cookie"]) : undefined; const result = await $fetch<{ data?: TData; errors?: Array<{ message: string }> }>("/api/graphql", { method: "POST", + headers, body: { query, variables }, }); @@ -531,6 +533,26 @@ async function loadMe() { authMe.value = data.me; } +const authResolved = ref(false); + +async function bootstrapSession() { + try { + await loadMe(); + if (!authMe.value) { + pilotMessages.value = []; + chatConversations.value = []; + return; + } + await Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]); + } catch { + authMe.value = null; + pilotMessages.value = []; + chatConversations.value = []; + } finally { + authResolved.value = true; + } +} + async function createNewChatConversation() { if (chatCreating.value) return; chatThreadPickerOpen.value = false; @@ -1118,6 +1140,10 @@ async function rollbackLatestChangeSet() { } } +if (process.server) { + await bootstrapSession(); +} + onMounted(() => { pilotHeaderText.value = pilotHeaderPhrases[Math.floor(Math.random() * pilotHeaderPhrases.length)] ?? "Every step moves you forward"; pilotMicSupported.value = @@ -1125,12 +1151,14 @@ onMounted(() => { typeof MediaRecorder !== "undefined" && Boolean(navigator.mediaDevices?.getUserMedia); - loadMe() - .then(() => { - startPilotBackgroundPolling(); - return Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]); - }) - .catch(() => {}); + if (!authResolved.value) { + void bootstrapSession().finally(() => { + if (authMe.value) startPilotBackgroundPolling(); + }); + return; + } + + if (authMe.value) startPilotBackgroundPolling(); }); onBeforeUnmount(() => { @@ -1764,7 +1792,7 @@ const latestPinnedItem = computed(() => selectedCommPinnedStream.value[0] ?? nul const latestPinnedLabel = computed(() => { if (!latestPinnedItem.value) return "No pinned items yet"; - if (latestPinnedItem.value.kind === "pin") return latestPinnedItem.value.text; + if (latestPinnedItem.value.kind === "pin") return stripPinnedPrefix(latestPinnedItem.value.text); return `${latestPinnedItem.value.event.title} · ${formatDay(latestPinnedItem.value.event.start)}`; }); @@ -1772,6 +1800,10 @@ function normalizePinText(value: string) { return String(value ?? "").replace(/\s+/g, " ").trim(); } +function stripPinnedPrefix(value: string) { + return String(value ?? "").replace(/^\s*(закреплено|pinned)\s*:\s*/i, "").trim(); +} + function isPinnedText(contact: string, value: string) { const contactName = String(contact ?? "").trim(); const text = normalizePinText(value); @@ -1781,7 +1813,7 @@ function isPinnedText(contact: string, value: string) { function entryPinText(entry: any): string { if (!entry) return ""; - if (entry.kind === "pin") return normalizePinText(entry.text ?? ""); + if (entry.kind === "pin") return normalizePinText(stripPinnedPrefix(entry.text ?? "")); if (entry.kind === "recommendation") return normalizePinText(entry.card?.text ?? ""); if (entry.kind === "event" || entry.kind === "eventAlert" || entry.kind === "eventLog") { return normalizePinText(entry.event?.note || entry.event?.title || ""); @@ -2353,7 +2385,11 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")