Madras1 commited on
Commit
cf48f8c
·
verified ·
1 Parent(s): ef58a7f

Upload 62 files

Browse files
app/api/routes/entities.py CHANGED
@@ -1,18 +1,26 @@
1
  """
2
  Entity CRUD Routes
3
  """
4
- from fastapi import APIRouter, Depends, HTTPException, Query
5
  from sqlalchemy.orm import Session
6
  from sqlalchemy import or_
7
  from typing import List, Optional
8
 
9
- from app.core.database import get_db
10
  from app.models import Entity, Relationship
11
  from app.schemas import EntityCreate, EntityUpdate, EntityResponse, GraphData, GraphNode, GraphEdge
12
 
13
  router = APIRouter(prefix="/entities", tags=["Entities"])
14
 
15
 
 
 
 
 
 
 
 
 
16
  @router.get("", response_model=List[EntityResponse])
17
  def list_entities(
18
  type: Optional[str] = None,
@@ -20,27 +28,39 @@ def list_entities(
20
  project_id: Optional[str] = None,
21
  limit: int = Query(default=50, le=200),
22
  offset: int = 0,
23
- db: Session = Depends(get_db)
 
24
  ):
25
  """Lista todas as entidades com filtros opcionais"""
26
- query = db.query(Entity)
27
 
28
- if project_id:
29
- query = query.filter(Entity.project_id == project_id)
 
 
 
30
 
31
- if type:
32
- query = query.filter(Entity.type == type)
33
-
34
- if search:
35
- query = query.filter(
36
- or_(
37
- Entity.name.ilike(f"%{search}%"),
38
- Entity.description.ilike(f"%{search}%")
 
 
 
 
 
 
 
39
  )
40
- )
41
-
42
- query = query.order_by(Entity.created_at.desc())
43
- return query.offset(offset).limit(limit).all()
 
44
 
45
 
46
  @router.get("/types")
 
1
  """
2
  Entity CRUD Routes
3
  """
4
+ from fastapi import APIRouter, Depends, HTTPException, Query, Header, Cookie
5
  from sqlalchemy.orm import Session
6
  from sqlalchemy import or_
7
  from typing import List, Optional
8
 
9
+ from app.core.database import get_db, get_db_for_session
10
  from app.models import Entity, Relationship
11
  from app.schemas import EntityCreate, EntityUpdate, EntityResponse, GraphData, GraphNode, GraphEdge
12
 
13
  router = APIRouter(prefix="/entities", tags=["Entities"])
14
 
15
 
16
+ def get_session_or_default(
17
+ x_session_id: Optional[str] = Header(None),
18
+ numidium_session: Optional[str] = Cookie(None)
19
+ ) -> Optional[str]:
20
+ """Get session ID from header or cookie"""
21
+ return x_session_id or numidium_session
22
+
23
+
24
  @router.get("", response_model=List[EntityResponse])
25
  def list_entities(
26
  type: Optional[str] = None,
 
28
  project_id: Optional[str] = None,
29
  limit: int = Query(default=50, le=200),
30
  offset: int = 0,
31
+ x_session_id: Optional[str] = Header(None),
32
+ numidium_session: Optional[str] = Cookie(None)
33
  ):
34
  """Lista todas as entidades com filtros opcionais"""
35
+ session_id = x_session_id or numidium_session
36
 
37
+ # Use session-specific database if session exists
38
+ if session_id:
39
+ db = get_db_for_session(session_id)
40
+ else:
41
+ db = next(get_db())
42
 
43
+ try:
44
+ query = db.query(Entity)
45
+
46
+ if project_id:
47
+ query = query.filter(Entity.project_id == project_id)
48
+
49
+ if type:
50
+ query = query.filter(Entity.type == type)
51
+
52
+ if search:
53
+ query = query.filter(
54
+ or_(
55
+ Entity.name.ilike(f"%{search}%"),
56
+ Entity.description.ilike(f"%{search}%")
57
+ )
58
  )
59
+
60
+ query = query.order_by(Entity.created_at.desc())
61
+ return query.offset(offset).limit(limit).all()
62
+ finally:
63
+ db.close()
64
 
65
 
66
  @router.get("/types")
app/api/routes/session.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Session management routes
3
+ """
4
+ from fastapi import APIRouter, Header, Cookie, Response
5
+ from typing import Optional
6
+ import uuid
7
+
8
+ from app.core.database import create_new_session_id
9
+
10
+ router = APIRouter(prefix="/session", tags=["Session"])
11
+
12
+
13
+ @router.post("/create")
14
+ def create_session(response: Response):
15
+ """Create a new session and return session_id"""
16
+ session_id = create_new_session_id()
17
+ response.set_cookie(
18
+ key="numidium_session",
19
+ value=session_id,
20
+ max_age=60*60*24*365, # 1 year
21
+ httponly=True,
22
+ samesite="none",
23
+ secure=True
24
+ )
25
+ return {"session_id": session_id}
26
+
27
+
28
+ @router.get("/current")
29
+ def get_current_session(
30
+ numidium_session: Optional[str] = Cookie(None),
31
+ x_session_id: Optional[str] = Header(None)
32
+ ):
33
+ """Get current session ID"""
34
+ session_id = x_session_id or numidium_session
35
+ if not session_id:
36
+ return {"session_id": None, "message": "No session. Call POST /session/create"}
37
+ return {"session_id": session_id}
app/core/database.py CHANGED
@@ -1,31 +1,72 @@
1
  """
2
  Database configuration and session management
 
3
  """
4
  from sqlalchemy import create_engine, text
5
  from sqlalchemy.ext.declarative import declarative_base
6
- from sqlalchemy.orm import sessionmaker
 
7
  import os
8
-
9
- from app.config import settings
10
 
11
  # Ensure data directory exists
12
- os.makedirs("data", exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- # Create engine
 
 
 
 
 
 
 
 
15
  engine = create_engine(
16
  settings.database_url,
17
- connect_args={"check_same_thread": False} # Needed for SQLite
18
  )
19
-
20
- # Session factory
21
  SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
22
 
23
- # Base class for models
24
- Base = declarative_base()
25
-
26
 
27
  def get_db():
28
- """Dependency to get database session"""
29
  db = SessionLocal()
30
  try:
31
  yield db
@@ -33,40 +74,37 @@ def get_db():
33
  db.close()
34
 
35
 
36
- def init_db():
37
- """Initialize database tables and run migrations"""
38
- Base.metadata.create_all(bind=engine)
39
-
40
- # Run migrations for new columns
41
- with engine.connect() as conn:
42
- # Add event_date to entities if not exists
43
  try:
44
  conn.execute(text("ALTER TABLE entities ADD COLUMN event_date DATETIME"))
45
  conn.commit()
46
- print("✅ Added event_date column to entities table")
47
  except Exception:
48
- pass # Column already exists
49
-
50
- # Add event_date to relationships if not exists
51
  try:
52
  conn.execute(text("ALTER TABLE relationships ADD COLUMN event_date DATETIME"))
53
  conn.commit()
54
- print("✅ Added event_date column to relationships table")
55
  except Exception:
56
- pass # Column already exists
57
-
58
- # Add project_id to entities if not exists
59
  try:
60
  conn.execute(text("ALTER TABLE entities ADD COLUMN project_id VARCHAR(36)"))
61
  conn.commit()
62
- print("✅ Added project_id column to entities table")
63
  except Exception:
64
- pass # Column already exists
65
-
66
- # Add project_id to relationships if not exists
67
  try:
68
  conn.execute(text("ALTER TABLE relationships ADD COLUMN project_id VARCHAR(36)"))
69
  conn.commit()
70
- print("✅ Added project_id column to relationships table")
71
  except Exception:
72
- pass # Column already exists
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
  Database configuration and session management
3
+ Per-session databases - each user session gets its own SQLite file
4
  """
5
  from sqlalchemy import create_engine, text
6
  from sqlalchemy.ext.declarative import declarative_base
7
+ from sqlalchemy.orm import sessionmaker, Session
8
+ from typing import Optional
9
  import os
10
+ import uuid
 
11
 
12
  # Ensure data directory exists
13
+ os.makedirs("data/sessions", exist_ok=True)
14
+
15
+ # Base class for models
16
+ Base = declarative_base()
17
+
18
+ # Cache for session engines
19
+ _session_engines = {}
20
+ _session_makers = {}
21
+
22
+
23
+ def get_session_engine(session_id: str):
24
+ """Get or create engine for a specific session"""
25
+ if session_id not in _session_engines:
26
+ db_path = f"data/sessions/{session_id}.db"
27
+ engine = create_engine(
28
+ f"sqlite:///./{db_path}",
29
+ connect_args={"check_same_thread": False}
30
+ )
31
+ _session_engines[session_id] = engine
32
+ _session_makers[session_id] = sessionmaker(autocommit=False, autoflush=False, bind=engine)
33
+
34
+ # Initialize tables for this session
35
+ Base.metadata.create_all(bind=engine)
36
+ _run_migrations(engine)
37
+
38
+ return _session_engines[session_id]
39
+
40
+
41
+ def get_session_db(session_id: str):
42
+ """Get database session for a specific user session"""
43
+ get_session_engine(session_id) # Ensure engine exists
44
+ SessionLocal = _session_makers[session_id]
45
+ db = SessionLocal()
46
+ try:
47
+ yield db
48
+ finally:
49
+ db.close()
50
+
51
 
52
+ def get_db_for_session(session_id: str) -> Session:
53
+ """Direct session getter (non-generator) for routes"""
54
+ get_session_engine(session_id)
55
+ SessionLocal = _session_makers[session_id]
56
+ return SessionLocal()
57
+
58
+
59
+ # Legacy - default database for backwards compatibility
60
+ from app.config import settings
61
  engine = create_engine(
62
  settings.database_url,
63
+ connect_args={"check_same_thread": False}
64
  )
 
 
65
  SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
66
 
 
 
 
67
 
68
  def get_db():
69
+ """Legacy: Default database session"""
70
  db = SessionLocal()
71
  try:
72
  yield db
 
74
  db.close()
75
 
76
 
77
+ def _run_migrations(eng):
78
+ """Run migrations on an engine"""
79
+ with eng.connect() as conn:
 
 
 
 
80
  try:
81
  conn.execute(text("ALTER TABLE entities ADD COLUMN event_date DATETIME"))
82
  conn.commit()
 
83
  except Exception:
84
+ pass
 
 
85
  try:
86
  conn.execute(text("ALTER TABLE relationships ADD COLUMN event_date DATETIME"))
87
  conn.commit()
 
88
  except Exception:
89
+ pass
 
 
90
  try:
91
  conn.execute(text("ALTER TABLE entities ADD COLUMN project_id VARCHAR(36)"))
92
  conn.commit()
 
93
  except Exception:
94
+ pass
 
 
95
  try:
96
  conn.execute(text("ALTER TABLE relationships ADD COLUMN project_id VARCHAR(36)"))
97
  conn.commit()
 
98
  except Exception:
99
+ pass
100
+
101
+
102
+ def init_db():
103
+ """Initialize default database tables"""
104
+ Base.metadata.create_all(bind=engine)
105
+ _run_migrations(engine)
106
+
107
+
108
+ def create_new_session_id() -> str:
109
+ """Generate a new session ID"""
110
+ return str(uuid.uuid4())
app/main.py CHANGED
@@ -8,7 +8,7 @@ from contextlib import asynccontextmanager
8
 
9
  from app.config import settings
10
  from app.core.database import init_db
11
- from app.api.routes import entities, relationships, events, search, ingest, analyze, graph, research, chat, investigate, dados_publicos, timeline
12
 
13
 
14
  @asynccontextmanager
@@ -62,6 +62,7 @@ app.include_router(chat.router, prefix="/api/v1")
62
  app.include_router(investigate.router, prefix="/api/v1")
63
  app.include_router(dados_publicos.router, prefix="/api/v1")
64
  app.include_router(timeline.router, prefix="/api/v1")
 
65
 
66
 
67
  @app.get("/")
 
8
 
9
  from app.config import settings
10
  from app.core.database import init_db
11
+ from app.api.routes import entities, relationships, events, search, ingest, analyze, graph, research, chat, investigate, dados_publicos, timeline, session
12
 
13
 
14
  @asynccontextmanager
 
62
  app.include_router(investigate.router, prefix="/api/v1")
63
  app.include_router(dados_publicos.router, prefix="/api/v1")
64
  app.include_router(timeline.router, prefix="/api/v1")
65
+ app.include_router(session.router, prefix="/api/v1")
66
 
67
 
68
  @app.get("/")