From 72a73c859cb5f3bf32bde1422e15ef0e295f65fd Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 15 Mar 2026 16:40:28 +0000 Subject: [PATCH] fix(gateway): CORS, memory userId from session, pgvector auto-init (#110) Co-authored-by: Jason Woltje Co-committed-by: Jason Woltje --- .env.example | 1 + apps/gateway/src/main.ts | 5 +++ apps/gateway/src/memory/memory.controller.ts | 33 ++++++++++---------- docker-compose.yml | 1 + infra/pg-init/01-extensions.sql | 1 + 5 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 infra/pg-init/01-extensions.sql diff --git a/.env.example b/.env.example index f0680bb..54e44c5 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,7 @@ BETTER_AUTH_URL=http://localhost:4000 # Gateway GATEWAY_PORT=4000 +GATEWAY_CORS_ORIGIN=http://localhost:3000 # Discord Plugin (optional — set DISCORD_BOT_TOKEN to enable) # DISCORD_BOT_TOKEN= diff --git a/apps/gateway/src/main.ts b/apps/gateway/src/main.ts index a7df855..8eb3b8d 100644 --- a/apps/gateway/src/main.ts +++ b/apps/gateway/src/main.ts @@ -35,6 +35,11 @@ async function bootstrap(): Promise { new FastifyAdapter({ bodyLimit: 1_048_576 }), ); + app.enableCors({ + origin: process.env['GATEWAY_CORS_ORIGIN'] ?? 'http://localhost:3000', + credentials: true, + }); + await app.register(helmet as never, { contentSecurityPolicy: false }); app.useGlobalPipes( new ValidationPipe({ diff --git a/apps/gateway/src/memory/memory.controller.ts b/apps/gateway/src/memory/memory.controller.ts index ff69238..6aa1324 100644 --- a/apps/gateway/src/memory/memory.controller.ts +++ b/apps/gateway/src/memory/memory.controller.ts @@ -15,6 +15,7 @@ import { import type { Memory } from '@mosaic/memory'; import { MEMORY } from './memory.tokens.js'; import { AuthGuard } from '../auth/auth.guard.js'; +import { CurrentUser } from '../auth/current-user.decorator.js'; import { EmbeddingService } from './embedding.service.js'; import type { UpsertPreferenceDto, CreateInsightDto, SearchMemoryDto } from './memory.dto.js'; @@ -23,33 +24,33 @@ import type { UpsertPreferenceDto, CreateInsightDto, SearchMemoryDto } from './m export class MemoryController { constructor( @Inject(MEMORY) private readonly memory: Memory, - private readonly embeddings: EmbeddingService, + @Inject(EmbeddingService) private readonly embeddings: EmbeddingService, ) {} // ─── Preferences ──────────────────────────────────────────────────── @Get('preferences') - async listPreferences(@Query('userId') userId: string, @Query('category') category?: string) { + async listPreferences(@CurrentUser() user: { id: string }, @Query('category') category?: string) { if (category) { return this.memory.preferences.findByUserAndCategory( - userId, + user.id, category as Parameters[1], ); } - return this.memory.preferences.findByUser(userId); + return this.memory.preferences.findByUser(user.id); } @Get('preferences/:key') - async getPreference(@Query('userId') userId: string, @Param('key') key: string) { - const pref = await this.memory.preferences.findByUserAndKey(userId, key); + async getPreference(@CurrentUser() user: { id: string }, @Param('key') key: string) { + const pref = await this.memory.preferences.findByUserAndKey(user.id, key); if (!pref) throw new NotFoundException('Preference not found'); return pref; } @Post('preferences') - async upsertPreference(@Query('userId') userId: string, @Body() dto: UpsertPreferenceDto) { + async upsertPreference(@CurrentUser() user: { id: string }, @Body() dto: UpsertPreferenceDto) { return this.memory.preferences.upsert({ - userId, + userId: user.id, key: dto.key, value: dto.value, category: dto.category, @@ -59,16 +60,16 @@ export class MemoryController { @Delete('preferences/:key') @HttpCode(HttpStatus.NO_CONTENT) - async removePreference(@Query('userId') userId: string, @Param('key') key: string) { - const deleted = await this.memory.preferences.remove(userId, key); + async removePreference(@CurrentUser() user: { id: string }, @Param('key') key: string) { + const deleted = await this.memory.preferences.remove(user.id, key); if (!deleted) throw new NotFoundException('Preference not found'); } // ─── Insights ─────────────────────────────────────────────────────── @Get('insights') - async listInsights(@Query('userId') userId: string, @Query('limit') limit?: string) { - return this.memory.insights.findByUser(userId, limit ? Number(limit) : undefined); + async listInsights(@CurrentUser() user: { id: string }, @Query('limit') limit?: string) { + return this.memory.insights.findByUser(user.id, limit ? Number(limit) : undefined); } @Get('insights/:id') @@ -79,13 +80,13 @@ export class MemoryController { } @Post('insights') - async createInsight(@Query('userId') userId: string, @Body() dto: CreateInsightDto) { + async createInsight(@CurrentUser() user: { id: string }, @Body() dto: CreateInsightDto) { const embedding = this.embeddings.available ? await this.embeddings.embed(dto.content) : undefined; return this.memory.insights.create({ - userId, + userId: user.id, content: dto.content, source: dto.source, category: dto.category, @@ -104,7 +105,7 @@ export class MemoryController { // ─── Search ───────────────────────────────────────────────────────── @Post('search') - async searchMemory(@Query('userId') userId: string, @Body() dto: SearchMemoryDto) { + async searchMemory(@CurrentUser() user: { id: string }, @Body() dto: SearchMemoryDto) { if (!this.embeddings.available) { return { query: dto.query, @@ -115,7 +116,7 @@ export class MemoryController { const queryEmbedding = await this.embeddings.embed(dto.query); const results = await this.memory.insights.searchByEmbedding( - userId, + user.id, queryEmbedding, dto.limit ?? 10, dto.maxDistance ?? 0.8, diff --git a/docker-compose.yml b/docker-compose.yml index 278d739..d94a7b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: POSTGRES_DB: mosaic volumes: - pg_data:/var/lib/postgresql/data + - ./infra/pg-init:/docker-entrypoint-initdb.d:ro healthcheck: test: ['CMD-SHELL', 'pg_isready -U mosaic'] interval: 5s diff --git a/infra/pg-init/01-extensions.sql b/infra/pg-init/01-extensions.sql new file mode 100644 index 0000000..0aa0fc2 --- /dev/null +++ b/infra/pg-init/01-extensions.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS vector;