feat: activity log model + API (audit trail)
This commit is contained in:
44
app/main.py
44
app/main.py
@@ -253,6 +253,7 @@ def startup():
|
|||||||
from app.core.config import Base, engine
|
from app.core.config import Base, engine
|
||||||
from app.models import webhook
|
from app.models import webhook
|
||||||
from app.models import apikey
|
from app.models import apikey
|
||||||
|
from app.models import activity
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
@@ -649,3 +650,46 @@ def batch_assign(data: BatchAssign, db: Session = Depends(get_db)):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return {"updated": len(updated), "issue_ids": updated, "assignee_id": data.assignee_id}
|
return {"updated": len(updated), "issue_ids": updated, "assignee_id": data.assignee_id}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ============ Activity Log ============
|
||||||
|
|
||||||
|
from app.models.activity import ActivityLog
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityLogResponse(PydanticBaseModel):
|
||||||
|
id: int
|
||||||
|
action: str
|
||||||
|
entity_type: str
|
||||||
|
entity_id: int
|
||||||
|
user_id: int | None
|
||||||
|
details: str | None
|
||||||
|
created_at: datetime
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
def log_activity(db: Session, action: str, entity_type: str, entity_id: int, user_id: int = None, details: str = None):
|
||||||
|
"""Helper to record an activity log entry."""
|
||||||
|
entry = ActivityLog(action=action, entity_type=entity_type, entity_id=entity_id, user_id=user_id, details=details)
|
||||||
|
db.add(entry)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/activity", response_model=List[ActivityLogResponse])
|
||||||
|
def list_activity(
|
||||||
|
entity_type: str = None,
|
||||||
|
entity_id: int = None,
|
||||||
|
user_id: int = None,
|
||||||
|
limit: int = 50,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
query = db.query(ActivityLog)
|
||||||
|
if entity_type:
|
||||||
|
query = query.filter(ActivityLog.entity_type == entity_type)
|
||||||
|
if entity_id:
|
||||||
|
query = query.filter(ActivityLog.entity_id == entity_id)
|
||||||
|
if user_id:
|
||||||
|
query = query.filter(ActivityLog.user_id == user_id)
|
||||||
|
return query.order_by(ActivityLog.created_at.desc()).limit(limit).all()
|
||||||
|
|||||||
15
app/models/activity.py
Normal file
15
app/models/activity.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from app.core.config import Base
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityLog(Base):
|
||||||
|
__tablename__ = "activity_logs"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
action = Column(String(50), nullable=False) # e.g. "issue.created", "comment.added"
|
||||||
|
entity_type = Column(String(50), nullable=False) # "issue", "project", "comment"
|
||||||
|
entity_id = Column(Integer, nullable=False)
|
||||||
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
||||||
|
details = Column(Text, nullable=True) # JSON string
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
Reference in New Issue
Block a user