fix: restore brainstorming feature with missing socket server and real-time events
This commit is contained in:
1
.antigravitycli/49e72a9e-f54f-4798-ba05-2ff82f1069f0.json
Symbolic link
1
.antigravitycli/49e72a9e-f54f-4798-ba05-2ff82f1069f0.json
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/home/devparsa/.gemini/config/projects/49e72a9e-f54f-4798-ba05-2ff82f1069f0.json
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
329
docs/plan-reparation-prod-2026-05-19.md
Normal file
329
docs/plan-reparation-prod-2026-05-19.md
Normal 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.
|
||||||
23
memento-note/Dockerfile.socket
Normal file
23
memento-note/Dockerfile.socket
Normal 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"]
|
||||||
35
memento-note/Dockerfile.socket.prebuilt
Normal file
35
memento-note/Dockerfile.socket.prebuilt
Normal 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"]
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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) ==="
|
||||||
|
|||||||
Reference in New Issue
Block a user