Madras1 commited on
Commit
43e23ba
·
verified ·
1 Parent(s): 53d0eb2

Upload 61 files

Browse files
app/api/routes/projects.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Projects API Routes - Workspace management
3
+ """
4
+ from fastapi import APIRouter, HTTPException
5
+ from pydantic import BaseModel
6
+ from typing import Optional, List
7
+ from datetime import datetime
8
+
9
+ from app.core.database import get_db
10
+ from app.models import Project, Entity, Relationship
11
+
12
+
13
+ router = APIRouter(prefix="/projects", tags=["Projects"])
14
+
15
+
16
+ class ProjectCreate(BaseModel):
17
+ name: str
18
+ description: Optional[str] = None
19
+ color: str = "#00d4ff"
20
+ icon: str = "folder"
21
+
22
+
23
+ class ProjectResponse(BaseModel):
24
+ id: str
25
+ name: str
26
+ description: Optional[str]
27
+ color: str
28
+ icon: str
29
+ entity_count: int = 0
30
+ created_at: datetime
31
+
32
+ class Config:
33
+ from_attributes = True
34
+
35
+
36
+ @router.get("", response_model=List[ProjectResponse])
37
+ def list_projects():
38
+ """List all projects"""
39
+ db = next(get_db())
40
+ projects = db.query(Project).order_by(Project.created_at.desc()).all()
41
+
42
+ result = []
43
+ for p in projects:
44
+ entity_count = db.query(Entity).filter(Entity.project_id == p.id).count()
45
+ result.append(ProjectResponse(
46
+ id=p.id,
47
+ name=p.name,
48
+ description=p.description,
49
+ color=p.color,
50
+ icon=p.icon,
51
+ entity_count=entity_count,
52
+ created_at=p.created_at
53
+ ))
54
+
55
+ return result
56
+
57
+
58
+ @router.post("", response_model=ProjectResponse)
59
+ def create_project(project: ProjectCreate):
60
+ """Create a new project"""
61
+ db = next(get_db())
62
+
63
+ new_project = Project(
64
+ name=project.name,
65
+ description=project.description,
66
+ color=project.color,
67
+ icon=project.icon
68
+ )
69
+ db.add(new_project)
70
+ db.commit()
71
+ db.refresh(new_project)
72
+
73
+ return ProjectResponse(
74
+ id=new_project.id,
75
+ name=new_project.name,
76
+ description=new_project.description,
77
+ color=new_project.color,
78
+ icon=new_project.icon,
79
+ entity_count=0,
80
+ created_at=new_project.created_at
81
+ )
82
+
83
+
84
+ @router.get("/{project_id}", response_model=ProjectResponse)
85
+ def get_project(project_id: str):
86
+ """Get project by ID"""
87
+ db = next(get_db())
88
+ project = db.query(Project).filter(Project.id == project_id).first()
89
+
90
+ if not project:
91
+ raise HTTPException(status_code=404, detail="Project not found")
92
+
93
+ entity_count = db.query(Entity).filter(Entity.project_id == project_id).count()
94
+
95
+ return ProjectResponse(
96
+ id=project.id,
97
+ name=project.name,
98
+ description=project.description,
99
+ color=project.color,
100
+ icon=project.icon,
101
+ entity_count=entity_count,
102
+ created_at=project.created_at
103
+ )
104
+
105
+
106
+ @router.delete("/{project_id}")
107
+ def delete_project(project_id: str):
108
+ """Delete project and optionally its entities"""
109
+ db = next(get_db())
110
+ project = db.query(Project).filter(Project.id == project_id).first()
111
+
112
+ if not project:
113
+ raise HTTPException(status_code=404, detail="Project not found")
114
+
115
+ # Set entities and relationships to no project (null)
116
+ db.query(Entity).filter(Entity.project_id == project_id).update({"project_id": None})
117
+ db.query(Relationship).filter(Relationship.project_id == project_id).update({"project_id": None})
118
+
119
+ db.delete(project)
120
+ db.commit()
121
+
122
+ return {"message": f"Project '{project.name}' deleted"}
123
+
124
+
125
+ @router.put("/{project_id}")
126
+ def update_project(project_id: str, project: ProjectCreate):
127
+ """Update project"""
128
+ db = next(get_db())
129
+ existing = db.query(Project).filter(Project.id == project_id).first()
130
+
131
+ if not existing:
132
+ raise HTTPException(status_code=404, detail="Project not found")
133
+
134
+ existing.name = project.name
135
+ existing.description = project.description
136
+ existing.color = project.color
137
+ existing.icon = project.icon
138
+ db.commit()
139
+
140
+ return {"message": "Project updated"}
app/core/database.py CHANGED
@@ -54,4 +54,19 @@ def init_db():
54
  print("✅ Added event_date column to relationships table")
55
  except Exception:
56
  pass # Column already exists
57
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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, projects
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(projects.router, prefix="/api/v1")
66
 
67
 
68
  @app.get("/")
app/models/__init__.py CHANGED
@@ -1,2 +1,3 @@
1
  # Models module
2
  from app.models.entity import Entity, Relationship, Event, Document
 
 
1
  # Models module
2
  from app.models.entity import Entity, Relationship, Event, Document
3
+ from app.models.project import Project
app/models/entity.py CHANGED
@@ -21,6 +21,7 @@ class Entity(Base):
21
  __tablename__ = "entities"
22
 
23
  id = Column(String(36), primary_key=True, default=generate_uuid)
 
24
  type = Column(String(50), nullable=False, index=True) # person, organization, location, etc
25
  name = Column(String(255), nullable=False, index=True)
26
  description = Column(Text, nullable=True)
@@ -62,6 +63,7 @@ class Relationship(Base):
62
  __tablename__ = "relationships"
63
 
64
  id = Column(String(36), primary_key=True, default=generate_uuid)
 
65
  source_id = Column(String(36), ForeignKey("entities.id"), nullable=False)
66
  target_id = Column(String(36), ForeignKey("entities.id"), nullable=False)
67
  type = Column(String(50), nullable=False, index=True) # works_for, knows, owns, etc
 
21
  __tablename__ = "entities"
22
 
23
  id = Column(String(36), primary_key=True, default=generate_uuid)
24
+ project_id = Column(String(36), ForeignKey("projects.id"), nullable=True, index=True)
25
  type = Column(String(50), nullable=False, index=True) # person, organization, location, etc
26
  name = Column(String(255), nullable=False, index=True)
27
  description = Column(Text, nullable=True)
 
63
  __tablename__ = "relationships"
64
 
65
  id = Column(String(36), primary_key=True, default=generate_uuid)
66
+ project_id = Column(String(36), ForeignKey("projects.id"), nullable=True, index=True)
67
  source_id = Column(String(36), ForeignKey("entities.id"), nullable=False)
68
  target_id = Column(String(36), ForeignKey("entities.id"), nullable=False)
69
  type = Column(String(50), nullable=False, index=True) # works_for, knows, owns, etc
app/models/project.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Project Model - Workspaces for organizing investigations
3
+ """
4
+ from sqlalchemy import Column, String, Text, DateTime
5
+ from datetime import datetime
6
+ import uuid
7
+
8
+ from app.core.database import Base
9
+
10
+
11
+ def generate_uuid():
12
+ return str(uuid.uuid4())
13
+
14
+
15
+ class Project(Base):
16
+ """
17
+ Projeto/Workspace - agrupa entidades e relacionamentos por investigação
18
+ """
19
+ __tablename__ = "projects"
20
+
21
+ id = Column(String(36), primary_key=True, default=generate_uuid)
22
+ name = Column(String(255), nullable=False)
23
+ description = Column(Text, nullable=True)
24
+ color = Column(String(7), default="#00d4ff") # Hex color for UI
25
+ icon = Column(String(50), default="folder") # Icon name
26
+
27
+ # Timestamps
28
+ created_at = Column(DateTime, default=datetime.utcnow)
29
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)