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

@@ -59,6 +59,9 @@ OPENAI_MODEL = os.environ.get("OPENAI_MODEL", "gpt-5.2")
# ── Session config ──
SESSION_MAX_AGE = int(os.environ.get("SESSION_MAX_AGE", 30 * 86400)) # 30 days
DEV_AUTO_LOGIN = os.environ.get("DEV_AUTO_LOGIN", "").lower() in {"1", "true", "yes", "on"}
DEV_AUTO_LOGIN_USERNAME = os.environ.get("DEV_AUTO_LOGIN_USERNAME", "dev")
DEV_AUTO_LOGIN_DISPLAY_NAME = os.environ.get("DEV_AUTO_LOGIN_DISPLAY_NAME", "Dev User")
# ── Ensure data dir exists ──
DATA_DIR.mkdir(parents=True, exist_ok=True)
@@ -66,4 +69,3 @@ DATA_DIR.mkdir(parents=True, exist_ok=True)
# Note: All internal services use plain HTTP (Docker network).
# No custom SSL context needed. External calls (OpenAI, SMTP2GO, Open Library)
# use default TLS verification.

View File

@@ -5,8 +5,8 @@ Platform Gateway — Response helpers mixed into GatewayHandler.
import json
from http.cookies import SimpleCookie
from config import SESSION_MAX_AGE
from sessions import get_session_user
from config import SESSION_MAX_AGE, DEV_AUTO_LOGIN
from sessions import get_session_user, get_or_create_dev_user
class ResponseMixin:
@@ -30,6 +30,8 @@ class ResponseMixin:
return None
def _get_user(self):
if DEV_AUTO_LOGIN and self.headers.get("X-Dev-Auto-Login") == "1":
return get_or_create_dev_user()
token = self._get_session_token()
return get_session_user(token)

View File

@@ -4,8 +4,9 @@ Platform Gateway — Session and service-connection helpers.
import secrets
from datetime import datetime, timedelta
import bcrypt
from config import SESSION_MAX_AGE
from config import SESSION_MAX_AGE, DEV_AUTO_LOGIN_USERNAME, DEV_AUTO_LOGIN_DISPLAY_NAME
from database import get_db
@@ -52,6 +53,24 @@ def get_service_token(user_id, service):
return dict(row) if row else None
def get_or_create_dev_user():
conn = get_db()
row = conn.execute("SELECT * FROM users WHERE username = ?", (DEV_AUTO_LOGIN_USERNAME,)).fetchone()
if row:
conn.close()
return dict(row)
pw_hash = bcrypt.hashpw(secrets.token_hex(16).encode(), bcrypt.gensalt()).decode()
conn.execute(
"INSERT INTO users (username, password_hash, display_name) VALUES (?, ?, ?)",
(DEV_AUTO_LOGIN_USERNAME, pw_hash, DEV_AUTO_LOGIN_DISPLAY_NAME)
)
conn.commit()
row = conn.execute("SELECT * FROM users WHERE username = ?", (DEV_AUTO_LOGIN_USERNAME,)).fetchone()
conn.close()
return dict(row) if row else None
def set_service_token(user_id, service, auth_token, auth_type="bearer"):
conn = get_db()
conn.execute("""