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 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
|
||||
echo "Creating default site settings..."
|
||||
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 sqlalchemy.orm import relationship
|
||||
from extensions import db
|
||||
from enum import Enum
|
||||
|
||||
# Association table for room members
|
||||
room_members = db.Table('room_members',
|
||||
@@ -202,3 +203,61 @@ class MessageAttachment(db.Model):
|
||||
|
||||
def __repr__(self):
|
||||
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