feat: brain service — self-contained second brain knowledge manager

Full backend service with:
- FastAPI REST API with CRUD, search, reprocess endpoints
- PostgreSQL + pgvector for items and semantic search
- Redis + RQ for background job processing
- Meilisearch for fast keyword/filter search
- Browserless/Chrome for JS rendering and screenshots
- OpenAI structured output for AI classification
- Local file storage with S3-ready abstraction
- Gateway auth via X-Gateway-User-Id header
- Own docker-compose stack (6 containers)

Classification: fixed folders (Home/Family/Work/Travel/Knowledge/Faith/Projects)
and fixed tags (28 predefined). AI assigns exactly 1 folder, 2-3 tags, title,
summary, and confidence score per item.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-01 11:48:29 -05:00
parent 51a8157fd4
commit 8275f3a71b
73 changed files with 24081 additions and 4209 deletions

View File

@@ -2,17 +2,35 @@ import type { Handle } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
const gatewayUrl = env.GATEWAY_URL || 'http://localhost:8100';
const devAutoLogin = ['1', 'true', 'yes', 'on'].includes((env.DEV_AUTO_LOGIN || '').toLowerCase());
const immichUrl = env.IMMICH_URL || '';
const immichApiKey = env.IMMICH_API_KEY || '';
const karakeepUrl = env.KARAKEEP_URL || '';
const karakeepApiKey = env.KARAKEEP_API_KEY || '';
export const handle: Handle = async ({ event, resolve }) => {
function shouldUseDevAutoLogin(): boolean {
if (!devAutoLogin) return false;
const host = event.url.host.toLowerCase();
return host.includes(':4174') || host.startsWith('test.');
}
function normalizeSetCookieForHttp(value: string): string {
// The gateway issues Secure cookies by default. On local HTTP dev hosts
// like 192.168.x.x or localhost, browsers drop those cookies entirely.
// Relax only at the frontend proxy boundary when the current app URL is HTTP.
if (event.url.protocol !== 'http:') return value;
return value.replace(/;\s*Secure/gi, '');
}
async function isAuthenticated(request: Request): Promise<boolean> {
const cookie = request.headers.get('cookie') || '';
if (!cookie.includes('platform_session=')) return false;
if (!cookie.includes('platform_session=') && !shouldUseDevAutoLogin()) return false;
try {
const res = await fetch(`${gatewayUrl}/api/auth/me`, { headers: { cookie } });
const headers: Record<string, string> = {};
if (cookie) headers.cookie = cookie;
if (shouldUseDevAutoLogin()) headers['X-Dev-Auto-Login'] = '1';
const res = await fetch(`${gatewayUrl}/api/auth/me`, { headers });
if (!res.ok) return false;
const data = await res.json();
return data.authenticated === true;
@@ -187,6 +205,9 @@ export const handle: Handle = async ({ event, resolve }) => {
headers.set(key, value);
}
}
if (shouldUseDevAutoLogin()) {
headers.set('X-Dev-Auto-Login', '1');
}
try {
const response = await fetch(targetUrl, {
@@ -200,7 +221,11 @@ export const handle: Handle = async ({ event, resolve }) => {
// Forward set-cookie headers from gateway
const responseHeaders = new Headers();
for (const [key, value] of response.headers.entries()) {
responseHeaders.append(key, value);
if (key.toLowerCase() === 'set-cookie') {
responseHeaders.append(key, normalizeSetCookieForHttp(value));
} else {
responseHeaders.append(key, value);
}
}
return new Response(response.body, {