Files
platform/services/brain/app/services/storage.py
Yusuf Suleman 8275f3a71b 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>
2026-04-01 11:48:29 -05:00

82 lines
2.1 KiB
Python

"""File storage abstraction — local disk first, S3-ready interface."""
import os
import shutil
from abc import ABC, abstractmethod
from pathlib import Path
from app.config import STORAGE_BACKEND, STORAGE_LOCAL_PATH
class StorageBackend(ABC):
@abstractmethod
def save(self, item_id: str, asset_type: str, filename: str, data: bytes) -> str:
"""Save file, return relative storage path."""
...
@abstractmethod
def read(self, path: str) -> bytes:
...
@abstractmethod
def delete(self, path: str) -> None:
...
@abstractmethod
def exists(self, path: str) -> bool:
...
@abstractmethod
def url(self, path: str) -> str:
"""Return a URL or local path for serving."""
...
class LocalStorage(StorageBackend):
def __init__(self, base_path: str):
self.base = Path(base_path)
self.base.mkdir(parents=True, exist_ok=True)
def _full_path(self, path: str) -> Path:
return self.base / path
def save(self, item_id: str, asset_type: str, filename: str, data: bytes) -> str:
rel = f"{item_id}/{asset_type}/{filename}"
full = self._full_path(rel)
full.parent.mkdir(parents=True, exist_ok=True)
full.write_bytes(data)
return rel
def read(self, path: str) -> bytes:
return self._full_path(path).read_bytes()
def delete(self, path: str) -> None:
full = self._full_path(path)
if full.exists():
full.unlink()
# Clean empty parent dirs
parent = full.parent
while parent != self.base:
try:
parent.rmdir()
parent = parent.parent
except OSError:
break
def exists(self, path: str) -> bool:
return self._full_path(path).exists()
def url(self, path: str) -> str:
return f"/storage/{path}"
# Future: S3Storage class implementing the same interface
def _create_storage() -> StorageBackend:
if STORAGE_BACKEND == "local":
return LocalStorage(STORAGE_LOCAL_PATH)
raise ValueError(f"Unknown storage backend: {STORAGE_BACKEND}")
storage = _create_storage()