Journaling improvements

This commit is contained in:
2026-04-03 03:57:20 -04:00
parent 2dce14507b
commit dee58e0aae
18 changed files with 1061 additions and 44 deletions
+25 -2
View File
@@ -58,7 +58,7 @@ class CRUDArrival:
)
).order_by(Arrival.created_dt).all()
def create(self, db: Session, obj_in: ArrivalCreate, created_by: str, submitted_via: str = "ADMIN") -> Arrival:
def create(self, db: Session, obj_in: ArrivalCreate, created_by: str, submitted_via: str = "ADMIN", user_ip: Optional[str] = None) -> Arrival:
from app.models.arrival import SubmissionSource
# Set initial status based on submission source
@@ -76,6 +76,17 @@ class CRUDArrival:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log creation in journal
journal.log_change(
db,
EntityType.ARRIVAL,
db_obj.id,
f"Arrival created: {db_obj.registration}",
created_by,
user_ip
)
return db_obj
def update(self, db: Session, db_obj: Arrival, obj_in: ArrivalUpdate, user: str = "system", user_ip: Optional[str] = None) -> Arrival:
@@ -156,15 +167,27 @@ class CRUDArrival:
return db_obj
def cancel(self, db: Session, arrival_id: int) -> Optional[Arrival]:
def cancel(self, db: Session, arrival_id: int, user: str = "system", user_ip: Optional[str] = None) -> Optional[Arrival]:
db_obj = self.get(db, arrival_id)
if not db_obj:
return None
old_status = db_obj.status
db_obj.status = ArrivalStatus.CANCELLED
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log cancellation in journal
journal.log_change(
db,
EntityType.ARRIVAL,
arrival_id,
f"Status changed from {old_status.value} to CANCELLED",
user,
user_ip
)
return db_obj
+58 -4
View File
@@ -4,6 +4,8 @@ from sqlalchemy import desc
from datetime import datetime
from app.models.circuit import Circuit
from app.schemas.circuit import CircuitCreate, CircuitUpdate
from app.models.journal import EntityType
from app.crud.crud_journal import journal
class CRUDCircuit:
@@ -30,7 +32,7 @@ class CRUDCircuit:
) -> List[Circuit]:
return db.query(Circuit).order_by(desc(Circuit.created_at)).offset(skip).limit(limit).all()
def create(self, db: Session, obj_in: CircuitCreate) -> Circuit:
def create(self, db: Session, obj_in: CircuitCreate, user: str = "system", user_ip: Optional[str] = None) -> Circuit:
db_obj = Circuit(
local_flight_id=obj_in.local_flight_id,
arrival_id=obj_in.arrival_id,
@@ -39,22 +41,74 @@ class CRUDCircuit:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log circuit creation in journal
# Use LOCAL_FLIGHT entity type if local_flight_id exists, otherwise ARRIVAL
entity_type = EntityType.LOCAL_FLIGHT if obj_in.local_flight_id else EntityType.ARRIVAL
entity_id = obj_in.local_flight_id if obj_in.local_flight_id else obj_in.arrival_id
journal.log_change(
db,
entity_type,
entity_id,
f"Circuit recorded at {obj_in.circuit_timestamp.isoformat()}",
user,
user_ip
)
return db_obj
def update(self, db: Session, db_obj: Circuit, obj_in: CircuitUpdate) -> Circuit:
def update(self, db: Session, db_obj: Circuit, obj_in: CircuitUpdate, user: str = "system", user_ip: Optional[str] = None) -> Circuit:
obj_data = obj_in.dict(exclude_unset=True)
changes = []
for field, value in obj_data.items():
setattr(db_obj, field, value)
old_value = getattr(db_obj, field)
if old_value != value:
changes.append(f"{field} changed from '{old_value}' to '{value}'")
setattr(db_obj, field, value)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log changes in journal if any were made
if changes:
entity_type = EntityType.LOCAL_FLIGHT if db_obj.local_flight_id else EntityType.ARRIVAL
entity_id = db_obj.local_flight_id if db_obj.local_flight_id else db_obj.arrival_id
for change in changes:
journal.log_change(
db,
entity_type,
entity_id,
f"Circuit: {change}",
user,
user_ip
)
return db_obj
def delete(self, db: Session, circuit_id: int) -> bool:
def delete(self, db: Session, circuit_id: int, user: str = "system", user_ip: Optional[str] = None) -> bool:
circuit = self.get(db, circuit_id)
if circuit:
# Determine which entity this circuit belongs to
entity_type = EntityType.LOCAL_FLIGHT if circuit.local_flight_id else EntityType.ARRIVAL
entity_id = circuit.local_flight_id if circuit.local_flight_id else circuit.arrival_id
db.delete(circuit)
db.commit()
# Log deletion in journal
journal.log_change(
db,
entity_type,
entity_id,
f"Circuit deleted (recorded at {circuit.circuit_timestamp.isoformat()})",
user,
user_ip
)
return True
return False
+25 -2
View File
@@ -47,7 +47,7 @@ class CRUDDeparture:
)
).order_by(Departure.created_dt).all()
def create(self, db: Session, obj_in: DepartureCreate, created_by: str, submitted_via: str = "ADMIN") -> Departure:
def create(self, db: Session, obj_in: DepartureCreate, created_by: str, submitted_via: str = "ADMIN", user_ip: Optional[str] = None) -> Departure:
from app.models.departure import SubmissionSource
# Set initial status based on submission source
@@ -68,6 +68,17 @@ class CRUDDeparture:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log creation in journal
journal.log_change(
db,
EntityType.DEPARTURE,
db_obj.id,
f"Departure created: {db_obj.registration}",
created_by,
user_ip
)
return db_obj
def update(self, db: Session, db_obj: Departure, obj_in: DepartureUpdate, user: str = "system", user_ip: Optional[str] = None) -> Departure:
@@ -150,15 +161,27 @@ class CRUDDeparture:
return db_obj
def cancel(self, db: Session, departure_id: int) -> Optional[Departure]:
def cancel(self, db: Session, departure_id: int, user: str = "system", user_ip: Optional[str] = None) -> Optional[Departure]:
db_obj = self.get(db, departure_id)
if not db_obj:
return None
old_status = db_obj.status
db_obj.status = DepartureStatus.CANCELLED
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log cancellation in journal
journal.log_change(
db,
EntityType.DEPARTURE,
departure_id,
f"Status changed from {old_status.value} to CANCELLED",
user,
user_ip
)
return db_obj
+37 -1
View File
@@ -1,7 +1,8 @@
from typing import List, Optional
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, func, desc
from app.models.journal import JournalEntry, EntityType
from datetime import datetime
from datetime import datetime, date
class CRUDJournal:
@@ -58,6 +59,41 @@ class CRUDJournal:
JournalEntry.user == user
).order_by(JournalEntry.entry_dt.desc()).limit(limit).all()
def search_entries(
self,
db: Session,
date_from: Optional[date] = None,
date_to: Optional[date] = None,
entity_type: Optional[str] = None,
entity_id: Optional[int] = None,
user: Optional[str] = None,
limit: int = 500
) -> List[JournalEntry]:
"""Search journal entries with optional filters."""
query = db.query(JournalEntry)
# Apply date filters
if date_from:
query = query.filter(func.date(JournalEntry.entry_dt) >= date_from)
if date_to:
query = query.filter(func.date(JournalEntry.entry_dt) <= date_to)
# Apply entity type filter
if entity_type:
query = query.filter(JournalEntry.entity_type == entity_type.upper())
# Apply entity ID filter
if entity_id:
query = query.filter(JournalEntry.entity_id == entity_id)
# Apply user filter
if user:
query = query.filter(JournalEntry.user == user)
# Order by date descending (newest first) and apply limit
return query.order_by(JournalEntry.entry_dt.desc()).limit(limit).all()
# Convenience methods for backward compatibility with PPR journal
def log_ppr_change(
self,
+24 -2
View File
@@ -84,7 +84,7 @@ class CRUDLocalFlight:
)
).order_by(LocalFlight.created_dt).all()
def create(self, db: Session, obj_in: LocalFlightCreate, created_by: str, submitted_via: str = "ADMIN") -> LocalFlight:
def create(self, db: Session, obj_in: LocalFlightCreate, created_by: str, submitted_via: str = "ADMIN", user_ip: Optional[str] = None) -> LocalFlight:
from app.models.local_flight import SubmissionSource
# Set initial status based on submission source
@@ -102,6 +102,17 @@ class CRUDLocalFlight:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log creation in journal
journal.log_change(
db,
EntityType.LOCAL_FLIGHT,
db_obj.id,
f"Local flight created: {db_obj.registration} ({db_obj.flight_type.value})",
created_by,
user_ip
)
return db_obj
def update(self, db: Session, db_obj: LocalFlight, obj_in: LocalFlightUpdate, user: str = "system", user_ip: Optional[str] = None) -> LocalFlight:
@@ -201,13 +212,24 @@ class CRUDLocalFlight:
return db_obj
def cancel(self, db: Session, flight_id: int) -> Optional[LocalFlight]:
def cancel(self, db: Session, flight_id: int, user: str = "system", user_ip: Optional[str] = None) -> Optional[LocalFlight]:
db_obj = self.get(db, flight_id)
if db_obj:
old_status = db_obj.status
db_obj.status = LocalFlightStatus.CANCELLED
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log cancellation in journal
journal.log_change(
db,
EntityType.LOCAL_FLIGHT,
flight_id,
f"Status changed from {old_status.value} to CANCELLED",
user,
user_ip
)
return db_obj
+46 -4
View File
@@ -3,6 +3,8 @@ from sqlalchemy.orm import Session
from app.models.ppr import User
from app.schemas.ppr import UserCreate, UserUpdate
from app.core.security import get_password_hash, verify_password
from app.models.journal import EntityType
from app.crud.crud_journal import journal
class CRUDUser:
@@ -15,7 +17,7 @@ class CRUDUser:
def get_multi(self, db: Session, skip: int = 0, limit: int = 100) -> List[User]:
return db.query(User).offset(skip).limit(limit).all()
def create(self, db: Session, obj_in: UserCreate) -> User:
def create(self, db: Session, obj_in: UserCreate, admin_user: str = "system") -> User:
hashed_password = get_password_hash(obj_in.password)
db_obj = User(
username=obj_in.username,
@@ -25,17 +27,46 @@ class CRUDUser:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log user creation in journal
journal.log_change(
db,
EntityType.USER,
db_obj.id,
f"User created: {obj_in.username} with role {obj_in.role}",
admin_user,
None
)
return db_obj
def update(self, db: Session, db_obj: User, obj_in: UserUpdate) -> User:
def update(self, db: Session, db_obj: User, obj_in: UserUpdate, admin_user: str = "system") -> User:
update_data = obj_in.dict(exclude_unset=True)
changes = []
if "password" in update_data:
update_data["password"] = get_password_hash(update_data["password"])
changes.append("password changed")
for field, value in update_data.items():
setattr(db_obj, field, value)
old_value = getattr(db_obj, field)
if field == "password" or old_value != value:
if field != "password": # Don't log actual password values
changes.append(f"{field} changed from '{old_value}' to '{value}'")
setattr(db_obj, field, value)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log user update in journal
if changes:
journal.log_change(
db,
EntityType.USER,
db_obj.id,
"; ".join(changes),
admin_user,
None
)
return db_obj
def authenticate(self, db: Session, username: str, password: str) -> Optional[User]:
@@ -50,13 +81,24 @@ class CRUDUser:
# For future use if we add user status
return True
def change_password(self, db: Session, db_obj: User, new_password: str) -> User:
def change_password(self, db: Session, db_obj: User, new_password: str, admin_user: str = "system") -> User:
"""Change a user's password (typically used by admins to reset another user's password)"""
hashed_password = get_password_hash(new_password)
db_obj.password = hashed_password
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log password change in journal (security audit)
journal.log_change(
db,
EntityType.USER,
db_obj.id,
f"Password changed by {admin_user}",
admin_user,
None
)
return db_obj