fix: restore brainstorming feature with missing socket server and real-time events
Some checks failed
CI / Lint, Test & Build (push) Failing after 7m48s
CI / Deploy production (on server) (push) Has been cancelled

This commit is contained in:
Antigravity
2026-05-19 20:07:56 +00:00
parent 66c6f7ee8f
commit fdb148144e
15 changed files with 500 additions and 2 deletions

View File

@@ -0,0 +1 @@
/home/devparsa/.gemini/config/projects/49e72a9e-f54f-4798-ba05-2ff82f1069f0.json

View File

@@ -110,3 +110,12 @@ OLLAMA_BASE_URL="http://ollama:11434"
# RESEND EMAIL (alternative to SMTP, optional) # RESEND EMAIL (alternative to SMTP, optional)
# ============================================================================= # =============================================================================
# RESEND_API_KEY="re_..." # RESEND_API_KEY="re_..."
# ─────────────────────────────────────────────────────────────────────────────
# Brainstorm / Socket.io
# ─────────────────────────────────────────────────────────────────────────────
SOCKET_PORT=3002
SOCKET_HTTP_PORT=3003
SOCKET_INTERNAL_KEY=change-this-to-a-random-secret
SOCKET_INTERNAL_URL=http://memento-socket:3003
NEXT_PUBLIC_SOCKET_URL=https://note.parsanet.org

View File

@@ -76,7 +76,7 @@ jobs:
tar czf ../web-artifact.tgz \ tar czf ../web-artifact.tgz \
.next/standalone .next/static public prisma \ .next/standalone .next/static public prisma \
node_modules/.prisma node_modules/@prisma node_modules/prisma \ node_modules/.prisma node_modules/@prisma node_modules/prisma \
docker-entrypoint.sh docker-entrypoint.sh socket-server.ts tsconfig.json
- name: Upload web artifact - name: Upload web artifact
if: github.ref == 'refs/heads/main' && github.event_name == 'push' if: github.ref == 'refs/heads/main' && github.event_name == 'push'
@@ -141,6 +141,11 @@ jobs:
JINA_API_KEY: ${{ secrets.JINA_API_KEY }} JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
AUTH_GOOGLE_ID: ${{ vars.AUTH_GOOGLE_ID }} AUTH_GOOGLE_ID: ${{ vars.AUTH_GOOGLE_ID }}
AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }} AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }}
SOCKET_INTERNAL_KEY: ${{ secrets.SOCKET_INTERNAL_KEY }}
SOCKET_PORT: ${{ vars.SOCKET_PORT }}
SOCKET_HTTP_PORT: ${{ vars.SOCKET_HTTP_PORT }}
SOCKET_INTERNAL_URL: ${{ vars.SOCKET_INTERNAL_URL }}
NEXT_PUBLIC_SOCKET_URL: ${{ vars.NEXT_PUBLIC_SOCKET_URL }}
run: | run: |
ENV_FILE="/opt/memento/.env.docker" ENV_FILE="/opt/memento/.env.docker"
touch "$ENV_FILE" touch "$ENV_FILE"
@@ -185,6 +190,11 @@ jobs:
upsert JINA_API_KEY "$JINA_API_KEY" upsert JINA_API_KEY "$JINA_API_KEY"
upsert AUTH_GOOGLE_ID "$AUTH_GOOGLE_ID" upsert AUTH_GOOGLE_ID "$AUTH_GOOGLE_ID"
upsert AUTH_GOOGLE_SECRET "$AUTH_GOOGLE_SECRET" upsert AUTH_GOOGLE_SECRET "$AUTH_GOOGLE_SECRET"
upsert SOCKET_INTERNAL_KEY "$SOCKET_INTERNAL_KEY"
upsert SOCKET_PORT "$SOCKET_PORT"
upsert SOCKET_HTTP_PORT "$SOCKET_HTTP_PORT"
upsert SOCKET_INTERNAL_URL "$SOCKET_INTERNAL_URL"
upsert NEXT_PUBLIC_SOCKET_URL "$NEXT_PUBLIC_SOCKET_URL"
- name: Deploy on 192.168.1.190 - name: Deploy on 192.168.1.190
env: env:

View File

@@ -54,6 +54,11 @@ jobs:
JINA_API_KEY: ${{ secrets.JINA_API_KEY }} JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
AUTH_GOOGLE_ID: ${{ vars.AUTH_GOOGLE_ID }} AUTH_GOOGLE_ID: ${{ vars.AUTH_GOOGLE_ID }}
AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }} AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }}
SOCKET_INTERNAL_KEY: ${{ secrets.SOCKET_INTERNAL_KEY }}
SOCKET_PORT: ${{ vars.SOCKET_PORT }}
SOCKET_HTTP_PORT: ${{ vars.SOCKET_HTTP_PORT }}
SOCKET_INTERNAL_URL: ${{ vars.SOCKET_INTERNAL_URL }}
NEXT_PUBLIC_SOCKET_URL: ${{ vars.NEXT_PUBLIC_SOCKET_URL }}
run: | run: |
ENV_FILE="/opt/memento/.env.docker" ENV_FILE="/opt/memento/.env.docker"
touch "$ENV_FILE" touch "$ENV_FILE"
@@ -98,6 +103,11 @@ jobs:
upsert JINA_API_KEY "$JINA_API_KEY" upsert JINA_API_KEY "$JINA_API_KEY"
upsert AUTH_GOOGLE_ID "$AUTH_GOOGLE_ID" upsert AUTH_GOOGLE_ID "$AUTH_GOOGLE_ID"
upsert AUTH_GOOGLE_SECRET "$AUTH_GOOGLE_SECRET" upsert AUTH_GOOGLE_SECRET "$AUTH_GOOGLE_SECRET"
upsert SOCKET_INTERNAL_KEY "$SOCKET_INTERNAL_KEY"
upsert SOCKET_PORT "$SOCKET_PORT"
upsert SOCKET_HTTP_PORT "$SOCKET_HTTP_PORT"
upsert SOCKET_INTERNAL_URL "$SOCKET_INTERNAL_URL"
upsert NEXT_PUBLIC_SOCKET_URL "$NEXT_PUBLIC_SOCKET_URL"
- name: Deploy (full build, no CI artifact) - name: Deploy (full build, no CI artifact)
env: env:

View File

@@ -88,6 +88,32 @@ services:
cpus: '0.5' cpus: '0.5'
memory: 512M memory: 512M
# ============================================
# memento-socket - Socket.io Real-time Server
# ============================================
memento-socket:
build:
context: ./memento-note
dockerfile: ${MEMENTO_SOCKET_DOCKERFILE:-Dockerfile.socket}
container_name: memento-socket
env_file:
- .env.docker
ports:
- "3002:3002"
environment:
- NODE_ENV=production
restart: unless-stopped
networks:
- memento-network
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
# ============================================ # ============================================
# mcp-server - MCP Protocol Server # mcp-server - MCP Protocol Server
# ============================================ # ============================================

View File

@@ -0,0 +1,329 @@
# Plan de réparation production — 19 mai 2026
## État actuel des problèmes
1. **Tables brainstorm manquantes en prod** — les 7 tables n'existent pas en base
2. **Migration non enregistrée** — le container a été build avec 17 migrations, la 18e (brainstorm) est dans le code mais pas dans l'image
3. **Redis fonctionne** — le container redis tourne, mais il faut vérifier que memento-web a bien `REDIS_URL`
## Étape 1 : Créer les tables brainstorm en prod
Se connecter au serveur en root, puis :
```bash
docker exec -i memento-postgres psql -U memento -d memento <<'EOSQL'
-- ========================================
-- Tables brainstorm
-- ========================================
CREATE TABLE IF NOT EXISTS "BrainstormSession" (
"id" TEXT NOT NULL,
"seedIdea" TEXT NOT NULL,
"sourceNoteId" TEXT,
"contextNoteIds" TEXT,
"exportedNoteId" TEXT,
"userId" TEXT NOT NULL,
"inviteToken" TEXT,
"inviteExpiry" TIMESTAMP(3),
"liveblocksRoomId" TEXT,
"isPublic" BOOLEAN NOT NULL DEFAULT false,
"guestCanEdit" BOOLEAN NOT NULL DEFAULT false,
"status" TEXT NOT NULL DEFAULT 'active',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "BrainstormSession_pkey" PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "BrainstormIdea" (
"id" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"waveNumber" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"connectionToSeed" TEXT,
"noveltyScore" INTEGER,
"parentIdeaId" TEXT,
"convertedToNoteId" TEXT,
"relatedNoteIds" TEXT,
"status" TEXT NOT NULL DEFAULT 'active',
"positionX" DOUBLE PRECISION,
"positionY" DOUBLE PRECISION,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdBy" TEXT,
"createdByType" TEXT DEFAULT 'ai',
CONSTRAINT "BrainstormIdea_pkey" PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "BrainstormNoteRef" (
"id" TEXT NOT NULL,
"ideaId" TEXT NOT NULL,
"noteId" TEXT,
"relation" TEXT NOT NULL,
"explanation" TEXT NOT NULL,
"verdict" TEXT NOT NULL DEFAULT 'unresolved',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"visibility" TEXT NOT NULL DEFAULT 'participants',
CONSTRAINT "BrainstormNoteRef_pkey" PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "BrainstormParticipant" (
"id" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"role" TEXT NOT NULL DEFAULT 'viewer',
"joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "BrainstormParticipant_pkey" PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "BrainstormActivity" (
"id" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"userId" TEXT,
"action" TEXT NOT NULL,
"details" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "BrainstormActivity_pkey" PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "BrainstormShare" (
"id" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"sharedBy" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"permission" TEXT NOT NULL DEFAULT 'editor',
"notifiedAt" TIMESTAMP(3),
"respondedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "BrainstormShare_pkey" PRIMARY KEY ("id")
);
CREATE TABLE IF NOT EXISTS "BrainstormSnapshot" (
"id" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"activityId" TEXT,
"step" INTEGER NOT NULL,
"label" TEXT,
"ideaGraph" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "BrainstormSnapshot_pkey" PRIMARY KEY ("id")
);
-- ========================================
-- Index uniques
-- ========================================
CREATE UNIQUE INDEX IF NOT EXISTS "BrainstormSession_inviteToken_key" ON "BrainstormSession"("inviteToken");
CREATE UNIQUE INDEX IF NOT EXISTS "BrainstormParticipant_sessionId_userId_key" ON "BrainstormParticipant"("sessionId", "userId");
CREATE UNIQUE INDEX IF NOT EXISTS "BrainstormShare_sessionId_userId_key" ON "BrainstormShare"("sessionId", "userId");
-- ========================================
-- Index de performance
-- ========================================
CREATE INDEX IF NOT EXISTS "BrainstormSession_userId_idx" ON "BrainstormSession"("userId");
CREATE INDEX IF NOT EXISTS "BrainstormSession_userId_createdAt_idx" ON "BrainstormSession"("userId", "createdAt");
CREATE INDEX IF NOT EXISTS "BrainstormSession_inviteToken_idx" ON "BrainstormSession"("inviteToken");
CREATE INDEX IF NOT EXISTS "BrainstormSession_isPublic_idx" ON "BrainstormSession"("isPublic");
CREATE INDEX IF NOT EXISTS "BrainstormIdea_sessionId_idx" ON "BrainstormIdea"("sessionId");
CREATE INDEX IF NOT EXISTS "BrainstormIdea_waveNumber_idx" ON "BrainstormIdea"("waveNumber");
CREATE INDEX IF NOT EXISTS "BrainstormIdea_status_idx" ON "BrainstormIdea"("status");
CREATE INDEX IF NOT EXISTS "BrainstormIdea_parentIdeaId_idx" ON "BrainstormIdea"("parentIdeaId");
CREATE INDEX IF NOT EXISTS "BrainstormIdea_sessionId_status_idx" ON "BrainstormIdea"("sessionId", "status");
CREATE INDEX IF NOT EXISTS "BrainstormIdea_sessionId_waveNumber_createdAt_idx" ON "BrainstormIdea"("sessionId", "waveNumber", "createdAt");
CREATE INDEX IF NOT EXISTS "BrainstormNoteRef_ideaId_idx" ON "BrainstormNoteRef"("ideaId");
CREATE INDEX IF NOT EXISTS "BrainstormNoteRef_noteId_idx" ON "BrainstormNoteRef"("noteId");
CREATE INDEX IF NOT EXISTS "BrainstormParticipant_sessionId_idx" ON "BrainstormParticipant"("sessionId");
CREATE INDEX IF NOT EXISTS "BrainstormParticipant_userId_idx" ON "BrainstormParticipant"("userId");
CREATE INDEX IF NOT EXISTS "BrainstormActivity_sessionId_createdAt_idx" ON "BrainstormActivity"("sessionId", "createdAt");
CREATE INDEX IF NOT EXISTS "BrainstormShare_userId_idx" ON "BrainstormShare"("userId");
CREATE INDEX IF NOT EXISTS "BrainstormShare_status_idx" ON "BrainstormShare"("status");
CREATE INDEX IF NOT EXISTS "BrainstormSnapshot_sessionId_step_idx" ON "BrainstormSnapshot"("sessionId", "step");
CREATE INDEX IF NOT EXISTS "BrainstormSnapshot_sessionId_createdAt_idx" ON "BrainstormSnapshot"("sessionId", "createdAt");
-- ========================================
-- Clés étrangères
-- ========================================
DO $$ BEGIN
ALTER TABLE "BrainstormSession" ADD CONSTRAINT "BrainstormSession_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormSession" ADD CONSTRAINT "BrainstormSession_sourceNoteId_fkey" FOREIGN KEY ("sourceNoteId") REFERENCES "Note"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormSession" ADD CONSTRAINT "BrainstormSession_exportedNoteId_fkey" FOREIGN KEY ("exportedNoteId") REFERENCES "Note"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormIdea" ADD CONSTRAINT "BrainstormIdea_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "BrainstormSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormIdea" ADD CONSTRAINT "BrainstormIdea_parentIdeaId_fkey" FOREIGN KEY ("parentIdeaId") REFERENCES "BrainstormIdea"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormIdea" ADD CONSTRAINT "BrainstormIdea_convertedToNoteId_fkey" FOREIGN KEY ("convertedToNoteId") REFERENCES "Note"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormNoteRef" ADD CONSTRAINT "BrainstormNoteRef_ideaId_fkey" FOREIGN KEY ("ideaId") REFERENCES "BrainstormIdea"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormNoteRef" ADD CONSTRAINT "BrainstormNoteRef_noteId_fkey" FOREIGN KEY ("noteId") REFERENCES "Note"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormParticipant" ADD CONSTRAINT "BrainstormParticipant_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "BrainstormSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormParticipant" ADD CONSTRAINT "BrainstormParticipant_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormActivity" ADD CONSTRAINT "BrainstormActivity_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "BrainstormSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormActivity" ADD CONSTRAINT "BrainstormActivity_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormShare" ADD CONSTRAINT "BrainstormShare_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "BrainstormSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormShare" ADD CONSTRAINT "BrainstormShare_sharedBy_fkey" FOREIGN KEY ("sharedBy") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormShare" ADD CONSTRAINT "BrainstormShare_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "BrainstormSnapshot" ADD CONSTRAINT "BrainstormSnapshot_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "BrainstormSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
-- ========================================
-- Enregistrer la migration dans Prisma
-- ========================================
INSERT INTO "_prisma_migrations" (id, checksum, migration_name, logs, started_at, finished_at, applied_steps_count)
VALUES (
gen_random_uuid(),
'manual-creation-20260519',
'20260519120000_add_brainstorm_tables',
NULL,
now(),
now(),
1
) ON CONFLICT DO NOTHING;
EOSQL
```
## Étape 2 : Vérifier que les tables sont créées
```bash
docker exec memento-postgres psql -U memento -d memento -c "SELECT tablename FROM pg_tables WHERE schemaname='public' AND tablename LIKE '%rainstorm%' ORDER BY tablename;"
```
Résultat attendu : 7 lignes (BrainstormActivity, BrainstormIdea, BrainstormNoteRef, BrainstormParticipant, BrainstormSession, BrainstormShare, BrainstormSnapshot)
## Étape 3 : Vérifier les migrations enregistrées
```bash
docker exec memento-postgres psql -U memento -d memento -c "SELECT migration_name FROM _prisma_migrations ORDER BY started_at DESC LIMIT 3;"
```
Résultat attendu : la migration `20260519120000_add_brainstorm_tables` doit apparaître.
## Étape 4 : Vérifier Redis
```bash
docker ps | grep redis
docker exec memento-redis redis-cli ping
```
Résultat attendu : `PONG`
Vérifier que memento-web a bien la variable REDIS_URL :
```bash
docker exec memento-web env | grep REDIS
```
Résultat attendu : `REDIS_URL=redis://redis:6379`
Si vide, il faut recréer le container :
```bash
docker compose up -d --force-recreate memento-note
```
## Étape 5 : Redémarrer l'app
```bash
docker compose restart memento-note
```
## Étape 6 : Vérifier les logs
```bash
sleep 10
docker logs memento-web --tail 30 2>&1 | grep -i -E "error|redis|brainstorm"
```
Résultat attendu :
- Aucune erreur `BrainstormSession does not exist`
- Aucune erreur `redis ECONNREFUSED`
- `[redis] Connected` doit apparaître
## Étape 7 : Tester le brainstorm
1. Ouvrir https://note.parsanet.org/brainstorm
2. Taper une idée et cliquer le bouton +
3. Vérifier que les 9 idées apparaissent sur le canvas
---
## Si l'étape 1 échoue
Copier le message d'erreur exact. Les causes possibles :
- Permission denied → il faut être root (`sudo -i` avant)
- duplicate_object → tables déjà créées partiellement, aller à l'étape 2 pour vérifier
- Column exists → certaines colonnes existent déjà, les `IF NOT EXISTS` gèrent ça
## Si le brainstorm charge indéfiniment
Vérifier les logs en temps réel :
```bash
docker logs memento-web -f --tail 50 2>&1 &
```
Puis lancer un brainstorm et regarder les logs. Copier le message d'erreur exact.

View File

@@ -0,0 +1,23 @@
FROM node:22-bookworm-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
openssl \
&& rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json* ./
RUN npm install
# We only need the socket server and dependencies
COPY socket-server.ts ./
COPY tsconfig.json ./
# Environment defaults
ENV NODE_ENV=production
ENV SOCKET_PORT=3002
ENV SOCKET_HTTP_PORT=3003
# Run with tsx (included in devDependencies but we need it at runtime here)
# Alternatively, we could build it to JS, but tsx is easier for this standalone file.
CMD ["npx", "tsx", "socket-server.ts"]

View File

@@ -0,0 +1,35 @@
FROM node:22-bookworm-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
openssl \
&& rm -rf /var/lib/apt/lists/*
# In prebuilt mode, we expect node_modules to be partially there or we use the standalone's ones
# Actually, the artifact has node_modules/.prisma etc. but not the full node_modules.
# However, the standalone folder HAS node_modules.
# But we need tsx and socket.io.
# So we still need a minimal npm install or just use the pre-installed ones if we can.
# Let's keep it simple: if it's prebuilt, we just copy what we need.
# But tsx is a devDependency.
# Better to just use the full Dockerfile.socket for now as it's more reliable.
# "tsx" is not in the standalone output.
# Wait, I can just use the same strategy as memento-note:
# if ARTIFACT_TGZ is present, we already tar'ed the files.
# But we still need to run them.
COPY socket-server.ts ./
COPY tsconfig.json ./
COPY package.json package-lock.json* ./
# We still need to install dependencies because standalone doesn't have all of them (like tsx)
RUN npm install --only=production && npm install tsx
ENV NODE_ENV=production
ENV SOCKET_PORT=3002
ENV SOCKET_HTTP_PORT=3003
CMD ["npx", "tsx", "socket-server.ts"]

View File

@@ -2,6 +2,8 @@ import { NextRequest, NextResponse } from 'next/server'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { auth } from '@/auth' import { auth } from '@/auth'
import { z } from 'zod' import { z } from 'zod'
import { logActivity } from '@/lib/brainstorm-collab'
import { emitToSession } from '@/lib/socket-emit'
const convertSchema = z.object({ const convertSchema = z.object({
ideaId: z.string().min(1), ideaId: z.string().min(1),
@@ -153,6 +155,15 @@ export async function POST(
await prisma.$transaction(tagPromises) await prisma.$transaction(tagPromises)
await logActivity(sessionId, 'idea_converted', session.user.id, { ideaTitle: idea.title, ideaId: idea.id, noteId: note.id })
await emitToSession(sessionId, 'activity:new', {
action: 'idea_converted',
userId: session.user.id,
userName: session.user.name || 'Guest',
details: { ideaTitle: idea.title, ideaId: idea.id, noteId: note.id }
})
return NextResponse.json({ success: true, data: note }, { status: 201 }) return NextResponse.json({ success: true, data: note }, { status: 201 })
} catch (error: any) { } catch (error: any) {
if (error instanceof z.ZodError) { if (error instanceof z.ZodError) {

View File

@@ -3,6 +3,7 @@ import prisma from '@/lib/prisma'
import { auth } from '@/auth' import { auth } from '@/auth'
import { z } from 'zod' import { z } from 'zod'
import { verifyParticipant, logActivity, captureSnapshot } from '@/lib/brainstorm-collab' import { verifyParticipant, logActivity, captureSnapshot } from '@/lib/brainstorm-collab'
import { emitToSession } from '@/lib/socket-emit'
const dismissSchema = z.object({ const dismissSchema = z.object({
ideaId: z.string().min(1), ideaId: z.string().min(1),
@@ -56,6 +57,14 @@ export async function POST(
await logActivity(sessionId, 'idea_dismissed', session.user.id, { ideaTitle: idea.title }) await logActivity(sessionId, 'idea_dismissed', session.user.id, { ideaTitle: idea.title })
await emitToSession(sessionId, 'idea:dismissed', { ideaId, userId: session.user.id })
await emitToSession(sessionId, 'activity:new', {
action: 'idea_dismissed',
userId: session.user.id,
userName: session.user.name || 'Guest',
details: { ideaTitle: idea.title, ideaId }
})
await captureSnapshot(sessionId, `Dismissed: ${idea.title}`).catch(() => {}) await captureSnapshot(sessionId, `Dismissed: ${idea.title}`).catch(() => {})
return NextResponse.json({ success: true }) return NextResponse.json({ success: true })

View File

@@ -15,7 +15,9 @@ import {
resolveAiContextUserId, resolveAiContextUserId,
sanitizeNotesForGuest, sanitizeNotesForGuest,
captureSnapshot, captureSnapshot,
logActivity,
} from '@/lib/brainstorm-collab' } from '@/lib/brainstorm-collab'
import { emitToSession } from '@/lib/socket-emit'
const expandSchema = z.object({ const expandSchema = z.object({
ideaId: z.string().min(1), ideaId: z.string().min(1),
@@ -332,6 +334,17 @@ export async function POST(
for (const idea of updatedSession?.ideas || []) { (idea as any).creator = (idea as any).createdBy ? cm.get((idea as any).createdBy) || null : null } for (const idea of updatedSession?.ideas || []) { (idea as any).creator = (idea as any).createdBy ? cm.get((idea as any).createdBy) || null : null }
} }
await logActivity(sessionId, 'wave_generated', session.user.id, { count: newIdeas.length, parentIdeaId: ideaId })
await emitToSession(sessionId, 'activity:new', {
action: 'wave_generated',
userId: session.user.id,
userName: session.user.name || 'Guest',
details: { count: newIdeas.length, parentIdeaId: ideaId }
})
// Trigger refetch for all participants
await emitToSession(sessionId, 'idea:added', {})
await captureSnapshot(sessionId, `Wave expanded: ${parentIdea.title}`).catch(() => {}) await captureSnapshot(sessionId, `Wave expanded: ${parentIdea.title}`).catch(() => {})
return NextResponse.json({ success: true, data: updatedSession }) return NextResponse.json({ success: true, data: updatedSession })

View File

@@ -1,6 +1,8 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { auth } from '@/auth' import { auth } from '@/auth'
import { logActivity } from '@/lib/brainstorm-collab'
import { emitToSession } from '@/lib/socket-emit'
export async function POST( export async function POST(
request: NextRequest, request: NextRequest,
@@ -80,6 +82,15 @@ export async function POST(
const dry = Array.from(noteImpactMap.values()).filter(n => n.acceptedCount === 0 && n.dismissedCount > 0).length const dry = Array.from(noteImpactMap.values()).filter(n => n.acceptedCount === 0 && n.dismissedCount > 0).length
const totalRefs = Array.from(noteImpactMap.values()).length const totalRefs = Array.from(noteImpactMap.values()).length
await logActivity(sessionId, 'session_finalized', session.user.id, { notesEnriched: fruitful, notesMarkedDry: dry })
await emitToSession(sessionId, 'activity:new', {
action: 'session_finalized',
userId: session.user.id,
userName: session.user.name || 'Guest',
details: { notesEnriched: fruitful, notesMarkedDry: dry }
})
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
impact: { impact: {

View File

@@ -177,6 +177,13 @@ export async function POST(
await logActivity(sessionId, 'manual_idea', session.user.id, { ideaTitle: title, ideaId: idea.id }) await logActivity(sessionId, 'manual_idea', session.user.id, { ideaTitle: title, ideaId: idea.id })
await emitToSession(sessionId, 'activity:new', {
action: 'manual_idea',
userId: session.user.id,
userName: session.user.name || 'Guest',
details: { ideaTitle: title, ideaId: idea.id }
})
await captureSnapshot(sessionId, `Manual idea: ${title}`).catch(() => {}) await captureSnapshot(sessionId, `Manual idea: ${title}`).catch(() => {})
// [UPDATE - TEMPS RÉEL] Retourner immédiatement, enrichissement IA en arrière-plan // [UPDATE - TEMPS RÉEL] Retourner immédiatement, enrichissement IA en arrière-plan

View File

@@ -17,7 +17,7 @@ export interface ActivityEvent {
details: any details: any
} }
const SOCKET_URL = process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3001' const SOCKET_URL = process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'
export function useBrainstormSocket( export function useBrainstormSocket(
sessionId: string | null, sessionId: string | null,

View File

@@ -48,18 +48,22 @@ if [ -n "$ARTIFACT_TGZ" ] && [ -f "$ARTIFACT_TGZ" ]; then
echo "=== Fast image (CI artifact) ===" echo "=== Fast image (CI artifact) ==="
tar xzf "$ARTIFACT_TGZ" -C memento-note/ tar xzf "$ARTIFACT_TGZ" -C memento-note/
export MEMENTO_DOCKERFILE=Dockerfile.prebuilt export MEMENTO_DOCKERFILE=Dockerfile.prebuilt
export MEMENTO_SOCKET_DOCKERFILE=Dockerfile.socket.prebuilt
else else
echo "=== Full docker build (no artifact) ===" echo "=== Full docker build (no artifact) ==="
export MEMENTO_DOCKERFILE=Dockerfile export MEMENTO_DOCKERFILE=Dockerfile
export MEMENTO_SOCKET_DOCKERFILE=Dockerfile.socket
fi fi
docker compose build memento-note docker compose build memento-note
docker compose build memento-socket
if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^mcp-server/'; then if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^mcp-server/'; then
docker compose build mcp-server docker compose build mcp-server
fi fi
docker compose up -d --remove-orphans --force-recreate memento-note docker compose up -d --remove-orphans --force-recreate memento-note
docker compose up -d memento-socket
docker compose up -d mcp-server 2>/dev/null || true docker compose up -d mcp-server 2>/dev/null || true
echo "=== Migrations (Prisma CLI via node, pas npx) ===" echo "=== Migrations (Prisma CLI via node, pas npx) ==="