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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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("""
|
||||
|
||||
Reference in New Issue
Block a user