add events table
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -29,6 +29,20 @@ flask db init
|
|||||||
flask db migrate -m "Initial migration"
|
flask db migrate -m "Initial migration"
|
||||||
flask db upgrade
|
flask db upgrade
|
||||||
|
|
||||||
|
# Create events table
|
||||||
|
echo "Creating events table..."
|
||||||
|
python3 -c "
|
||||||
|
from migrations.add_events_table import upgrade
|
||||||
|
from app import create_app
|
||||||
|
app = create_app()
|
||||||
|
with app.app_context():
|
||||||
|
try:
|
||||||
|
upgrade()
|
||||||
|
print('Events table created successfully')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Error creating events table: {e}')
|
||||||
|
"
|
||||||
|
|
||||||
# Create default site settings if they don't exist
|
# Create default site settings if they don't exist
|
||||||
echo "Creating default site settings..."
|
echo "Creating default site settings..."
|
||||||
python3 -c "
|
python3 -c "
|
||||||
|
|||||||
61
migrations/add_events_table.py
Normal file
61
migrations/add_events_table.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add the parent directory to Python path so we can import from root
|
||||||
|
sys.path.append(str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from extensions import db
|
||||||
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# Create events table
|
||||||
|
with db.engine.connect() as conn:
|
||||||
|
conn.execute(text('''
|
||||||
|
CREATE TABLE IF NOT EXISTS events (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
event_type VARCHAR(50) NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL REFERENCES "user" (id),
|
||||||
|
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
details JSONB,
|
||||||
|
ip_address VARCHAR(45),
|
||||||
|
user_agent VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create index on event_type for faster filtering
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_events_event_type ON events(event_type);
|
||||||
|
|
||||||
|
-- Create index on timestamp for faster date-based queries
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
||||||
|
|
||||||
|
-- Create index on user_id for faster user-based queries
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_events_user_id ON events(user_id);
|
||||||
|
'''))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# Drop events table and its indexes
|
||||||
|
with db.engine.connect() as conn:
|
||||||
|
conn.execute(text('''
|
||||||
|
DROP INDEX IF EXISTS idx_events_event_type;
|
||||||
|
DROP INDEX IF EXISTS idx_events_timestamp;
|
||||||
|
DROP INDEX IF EXISTS idx_events_user_id;
|
||||||
|
DROP TABLE IF EXISTS events;
|
||||||
|
'''))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Use the same database configuration as in app.py
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'postgresql://postgres:1253@localhost:5432/docupulse')
|
||||||
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
|
||||||
|
print("Connecting to database...")
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
upgrade()
|
||||||
59
models.py
59
models.py
@@ -4,6 +4,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from extensions import db
|
from extensions import db
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
# Association table for room members
|
# Association table for room members
|
||||||
room_members = db.Table('room_members',
|
room_members = db.Table('room_members',
|
||||||
@@ -202,3 +203,61 @@ class MessageAttachment(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<MessageAttachment {self.name}>'
|
return f'<MessageAttachment {self.name}>'
|
||||||
|
|
||||||
|
class EventType(Enum):
|
||||||
|
# User events
|
||||||
|
USER_LOGIN = 'user_login'
|
||||||
|
USER_LOGOUT = 'user_logout'
|
||||||
|
USER_CREATE = 'user_create'
|
||||||
|
USER_UPDATE = 'user_update'
|
||||||
|
USER_DELETE = 'user_delete'
|
||||||
|
|
||||||
|
# Room events
|
||||||
|
ROOM_CREATE = 'room_create'
|
||||||
|
ROOM_UPDATE = 'room_update'
|
||||||
|
ROOM_DELETE = 'room_delete'
|
||||||
|
ROOM_MEMBER_ADD = 'room_member_add'
|
||||||
|
ROOM_MEMBER_REMOVE = 'room_member_remove'
|
||||||
|
ROOM_PERMISSION_UPDATE = 'room_permission_update'
|
||||||
|
|
||||||
|
# File events
|
||||||
|
FILE_UPLOAD = 'file_upload'
|
||||||
|
FILE_DOWNLOAD = 'file_download'
|
||||||
|
FILE_DELETE = 'file_delete'
|
||||||
|
FILE_RENAME = 'file_rename'
|
||||||
|
FILE_MOVE = 'file_move'
|
||||||
|
FILE_STAR = 'file_star'
|
||||||
|
FILE_UNSTAR = 'file_unstar'
|
||||||
|
|
||||||
|
# Conversation events
|
||||||
|
CONVERSATION_CREATE = 'conversation_create'
|
||||||
|
CONVERSATION_UPDATE = 'conversation_update'
|
||||||
|
CONVERSATION_DELETE = 'conversation_delete'
|
||||||
|
CONVERSATION_MEMBER_ADD = 'conversation_member_add'
|
||||||
|
CONVERSATION_MEMBER_REMOVE = 'conversation_member_remove'
|
||||||
|
|
||||||
|
# Message events
|
||||||
|
MESSAGE_CREATE = 'message_create'
|
||||||
|
MESSAGE_UPDATE = 'message_update'
|
||||||
|
MESSAGE_DELETE = 'message_delete'
|
||||||
|
MESSAGE_ATTACHMENT_ADD = 'message_attachment_add'
|
||||||
|
MESSAGE_ATTACHMENT_REMOVE = 'message_attachment_remove'
|
||||||
|
|
||||||
|
# Settings events
|
||||||
|
SETTINGS_UPDATE = 'settings_update'
|
||||||
|
|
||||||
|
class Event(db.Model):
|
||||||
|
__tablename__ = 'events'
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
event_type = db.Column(db.String(50), nullable=False)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||||
|
details = db.Column(db.JSON) # Store additional event-specific data
|
||||||
|
ip_address = db.Column(db.String(45)) # IPv6 addresses can be up to 45 chars
|
||||||
|
user_agent = db.Column(db.String(255))
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
user = db.relationship('User', backref='events')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Event {self.event_type} by User {self.user_id} at {self.timestamp}>'
|
||||||
Binary file not shown.
Binary file not shown.
123
utils/event_logger.py
Normal file
123
utils/event_logger.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
from flask import request
|
||||||
|
from models import Event, EventType, db
|
||||||
|
from typing import Optional, Dict, Any, List
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def log_event(
|
||||||
|
event_type: EventType,
|
||||||
|
user_id: int,
|
||||||
|
details: Optional[Dict[str, Any]] = None
|
||||||
|
) -> Event:
|
||||||
|
"""
|
||||||
|
Log an event in the system.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_type: The type of event from EventType enum
|
||||||
|
user_id: The ID of the user performing the action
|
||||||
|
details: Optional dictionary containing additional event-specific data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The created Event object
|
||||||
|
"""
|
||||||
|
event = Event(
|
||||||
|
event_type=event_type.value,
|
||||||
|
user_id=user_id,
|
||||||
|
details=details or {},
|
||||||
|
ip_address=request.remote_addr if request else None,
|
||||||
|
user_agent=request.user_agent.string if request and request.user_agent else None
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(event)
|
||||||
|
db.session.commit()
|
||||||
|
return event
|
||||||
|
|
||||||
|
def get_user_events(
|
||||||
|
user_id: int,
|
||||||
|
event_type: Optional[EventType] = None,
|
||||||
|
start_date: Optional[datetime] = None,
|
||||||
|
end_date: Optional[datetime] = None,
|
||||||
|
limit: int = 100
|
||||||
|
) -> List[Event]:
|
||||||
|
"""
|
||||||
|
Retrieve events for a specific user with optional filtering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: The ID of the user to get events for
|
||||||
|
event_type: Optional event type to filter by
|
||||||
|
start_date: Optional start date to filter events
|
||||||
|
end_date: Optional end date to filter events
|
||||||
|
limit: Maximum number of events to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of Event objects matching the criteria
|
||||||
|
"""
|
||||||
|
query = Event.query.filter_by(user_id=user_id)
|
||||||
|
|
||||||
|
if event_type:
|
||||||
|
query = query.filter_by(event_type=event_type.value)
|
||||||
|
if start_date:
|
||||||
|
query = query.filter(Event.timestamp >= start_date)
|
||||||
|
if end_date:
|
||||||
|
query = query.filter(Event.timestamp <= end_date)
|
||||||
|
|
||||||
|
return query.order_by(Event.timestamp.desc()).limit(limit).all()
|
||||||
|
|
||||||
|
def get_room_events(
|
||||||
|
room_id: int,
|
||||||
|
event_type: Optional[EventType] = None,
|
||||||
|
start_date: Optional[datetime] = None,
|
||||||
|
end_date: Optional[datetime] = None,
|
||||||
|
limit: int = 100
|
||||||
|
) -> List[Event]:
|
||||||
|
"""
|
||||||
|
Retrieve events related to a specific room with optional filtering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id: The ID of the room to get events for
|
||||||
|
event_type: Optional event type to filter by
|
||||||
|
start_date: Optional start date to filter events
|
||||||
|
end_date: Optional end date to filter events
|
||||||
|
limit: Maximum number of events to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of Event objects matching the criteria
|
||||||
|
"""
|
||||||
|
query = Event.query.filter(Event.details['room_id'].astext.cast(Integer) == room_id)
|
||||||
|
|
||||||
|
if event_type:
|
||||||
|
query = query.filter_by(event_type=event_type.value)
|
||||||
|
if start_date:
|
||||||
|
query = query.filter(Event.timestamp >= start_date)
|
||||||
|
if end_date:
|
||||||
|
query = query.filter(Event.timestamp <= end_date)
|
||||||
|
|
||||||
|
return query.order_by(Event.timestamp.desc()).limit(limit).all()
|
||||||
|
|
||||||
|
def get_recent_events(
|
||||||
|
event_type: Optional[EventType] = None,
|
||||||
|
start_date: Optional[datetime] = None,
|
||||||
|
end_date: Optional[datetime] = None,
|
||||||
|
limit: int = 100
|
||||||
|
) -> List[Event]:
|
||||||
|
"""
|
||||||
|
Retrieve recent events across the system with optional filtering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_type: Optional event type to filter by
|
||||||
|
start_date: Optional start date to filter events
|
||||||
|
end_date: Optional end date to filter events
|
||||||
|
limit: Maximum number of events to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of Event objects matching the criteria
|
||||||
|
"""
|
||||||
|
query = Event.query
|
||||||
|
|
||||||
|
if event_type:
|
||||||
|
query = query.filter_by(event_type=event_type.value)
|
||||||
|
if start_date:
|
||||||
|
query = query.filter(Event.timestamp >= start_date)
|
||||||
|
if end_date:
|
||||||
|
query = query.filter(Event.timestamp <= end_date)
|
||||||
|
|
||||||
|
return query.order_by(Event.timestamp.desc()).limit(limit).all()
|
||||||
Reference in New Issue
Block a user