diff --git a/routes/__pycache__/auth.cpython-313.pyc b/routes/__pycache__/auth.cpython-313.pyc index c44381b..608081d 100644 Binary files a/routes/__pycache__/auth.cpython-313.pyc and b/routes/__pycache__/auth.cpython-313.pyc differ diff --git a/routes/__pycache__/contacts.cpython-313.pyc b/routes/__pycache__/contacts.cpython-313.pyc index 07b2291..e9f119d 100644 Binary files a/routes/__pycache__/contacts.cpython-313.pyc and b/routes/__pycache__/contacts.cpython-313.pyc differ diff --git a/routes/__pycache__/conversations.cpython-313.pyc b/routes/__pycache__/conversations.cpython-313.pyc index 5f57099..2950c62 100644 Binary files a/routes/__pycache__/conversations.cpython-313.pyc and b/routes/__pycache__/conversations.cpython-313.pyc differ diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 1bbc838..9d5e64f 100644 Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ diff --git a/routes/__pycache__/room_members.cpython-313.pyc b/routes/__pycache__/room_members.cpython-313.pyc index 84896b2..70dda33 100644 Binary files a/routes/__pycache__/room_members.cpython-313.pyc and b/routes/__pycache__/room_members.cpython-313.pyc differ diff --git a/routes/auth.py b/routes/auth.py index 37170ed..940b815 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -3,7 +3,7 @@ from flask_login import login_user, logout_user, login_required, current_user from models import db, User from functools import wraps from datetime import datetime -from utils import log_event +from utils import log_event, create_notification def require_password_change(f): @wraps(f) @@ -94,6 +94,18 @@ def init_routes(auth_bp): db.session.add(new_user) db.session.commit() + # Create notification for the new user + create_notification( + notif_type='account_created', + user_id=new_user.id, + details={ + 'message': 'Welcome to DocuPulse! Your account has been created successfully.', + 'username': new_user.username, + 'email': new_user.email, + 'timestamp': datetime.utcnow().isoformat() + } + ) + # Log successful registration log_event( event_type='user_create', @@ -169,6 +181,16 @@ def init_routes(auth_bp): current_user.set_password(new_password) + # Create password change notification + create_notification( + notif_type='password_changed', + user_id=current_user.id, + details={ + 'message': 'Your password has been changed successfully.', + 'timestamp': datetime.utcnow().isoformat() + } + ) + # Log successful password change log_event( event_type='user_update', diff --git a/routes/contacts.py b/routes/contacts.py index 3b18bcc..1c8c7c1 100644 --- a/routes/contacts.py +++ b/routes/contacts.py @@ -5,10 +5,11 @@ from forms import UserForm from flask import abort from sqlalchemy import or_ from routes.auth import require_password_change -from utils import log_event +from utils import log_event, create_notification import json import os from werkzeug.utils import secure_filename +from datetime import datetime contacts_bp = Blueprint('contacts', __name__, url_prefix='/contacts') @@ -122,6 +123,20 @@ def new_contact(): db.session.add(user) db.session.commit() + # Create notification for the new user + create_notification( + notif_type='account_created', + user_id=user.id, + sender_id=current_user.id, # Admin who created the account + details={ + 'message': 'Your DocuPulse account has been created by an administrator.', + 'username': user.username, + 'email': user.email, + 'created_by': f"{current_user.username} {current_user.last_name}", + 'timestamp': datetime.utcnow().isoformat() + } + ) + # Log user creation event log_event( event_type='user_create', @@ -304,6 +319,26 @@ def edit_contact(id): db.session.commit() + # Create notification for the user being updated + create_notification( + notif_type='account_updated', + user_id=user.id, + sender_id=current_user.id, + details={ + 'message': 'Your account has been updated by an administrator.', + 'updated_by': f"{current_user.username} {current_user.last_name}", + 'changes': { + 'name': f"{user.username} {user.last_name}", + 'email': user.email, + 'phone': user.phone, + 'company': user.company, + 'position': user.position, + 'password_changed': password_changed + }, + 'timestamp': datetime.utcnow().isoformat() + } + ) + # Log user update event log_event( event_type='user_update', @@ -342,6 +377,18 @@ def delete_contact(id): flash('You cannot delete your own account.', 'error') return redirect(url_for('contacts.contacts_list')) + # Create notification for the user being deleted + create_notification( + notif_type='account_deleted', + user_id=user.id, + sender_id=current_user.id, + details={ + 'message': 'Your account has been deleted by an administrator.', + 'deleted_by': f"{current_user.username} {current_user.last_name}", + 'timestamp': datetime.utcnow().isoformat() + } + ) + # Log user deletion event log_event( event_type='user_delete', diff --git a/routes/conversations.py b/routes/conversations.py index 4f6acea..1070e23 100644 --- a/routes/conversations.py +++ b/routes/conversations.py @@ -3,7 +3,7 @@ from flask_login import login_required, current_user from models import db, Conversation, User, Message, MessageAttachment from forms import ConversationForm from routes.auth import require_password_change -from utils import log_event +from utils import log_event, create_notification import os from werkzeug.utils import secure_filename from datetime import datetime @@ -177,6 +177,20 @@ def add_member(conversation_id): conversation.members.append(user) db.session.commit() + # Create notification for the invited user + create_notification( + notif_type='conversation_invite', + user_id=user.id, + sender_id=current_user.id, + details={ + 'message': f'You have been invited to join conversation "{conversation.name}"', + 'conversation_id': conversation.id, + 'conversation_name': conversation.name, + 'invited_by': f"{current_user.username} {current_user.last_name}", + 'timestamp': datetime.utcnow().isoformat() + } + ) + # Log member addition log_event( event_type='conversation_member_add', @@ -186,7 +200,7 @@ def add_member(conversation_id): 'added_by': current_user.id, 'added_by_name': f"{current_user.username} {current_user.last_name}", 'added_user_id': user.id, - 'added_by_name': f"{current_user.username} {current_user.last_name}", + 'added_user_name': f"{user.username} {user.last_name}", 'added_user_email': user.email } ) @@ -215,6 +229,20 @@ def remove_member(conversation_id, user_id): conversation.members.remove(user) db.session.commit() + # Create notification for the removed user + create_notification( + notif_type='conversation_invite_removed', + user_id=user.id, + sender_id=current_user.id, + details={ + 'message': f'You have been removed from conversation "{conversation.name}"', + 'conversation_id': conversation.id, + 'conversation_name': conversation.name, + 'removed_by': f"{current_user.username} {current_user.last_name}", + 'timestamp': datetime.utcnow().isoformat() + } + ) + # Log member removal log_event( event_type='conversation_member_remove', @@ -437,6 +465,25 @@ def send_message(conversation_id): db.session.commit() + # Create notifications for all conversation members except the sender + for member in conversation.members: + if member.id != current_user.id: + create_notification( + notif_type='conversation_message', + user_id=member.id, + sender_id=current_user.id, + details={ + 'message': f'New message in conversation "{conversation.name}"', + 'conversation_id': conversation.id, + 'conversation_name': conversation.name, + 'sender': f"{current_user.username} {current_user.last_name}", + 'message_preview': message_content[:100] + ('...' if len(message_content) > 100 else ''), + 'has_attachments': len(attachments) > 0, + 'attachment_count': len(attachments), + 'timestamp': datetime.utcnow().isoformat() + } + ) + # Log message creation log_event( event_type='message_create', diff --git a/routes/main.py b/routes/main.py index 69d6d55..c945934 100644 --- a/routes/main.py +++ b/routes/main.py @@ -10,7 +10,7 @@ import logging import sys import time from forms import CompanySettingsForm -from utils import log_event +from utils import log_event, create_notification # Set up logging to show in console logging.basicConfig( @@ -55,7 +55,7 @@ def init_routes(main_bp): Event.user_id == current_user.id, # User's own actions db.and_( Event.event_type.in_(['conversation_create', 'message_create']), # Conversation-related events - Event.details['conversation_id'].astext.in_( + Event.details['conversation_id'].cast(db.Integer).in_( db.session.query(Conversation.id) .join(Conversation.members) .filter(User.id == current_user.id) @@ -370,6 +370,17 @@ def init_routes(main_bp): flash('Passwords do not match.', 'error') return render_template('profile/profile.html') current_user.set_password(new_password) + + # Create password change notification + create_notification( + notif_type='password_changed', + user_id=current_user.id, + details={ + 'message': 'Your password has been changed successfully.', + 'timestamp': datetime.utcnow().isoformat() + } + ) + flash('Password updated successfully.', 'success') elif confirm_password: flash('Please enter a new password.', 'error') @@ -522,7 +533,8 @@ def init_routes(main_bp): 'details': notif.details, 'sender': { 'id': notif.sender.id, - 'username': notif.sender.username + 'username': notif.sender.username, + 'last_name': notif.sender.last_name } if notif.sender else None } for notif in notifications.items], 'total_pages': total_pages, diff --git a/routes/room_members.py b/routes/room_members.py index 91eec30..7a10312 100644 --- a/routes/room_members.py +++ b/routes/room_members.py @@ -1,7 +1,8 @@ from flask import Blueprint, jsonify, request, abort from flask_login import login_required, current_user from models import db, Room, User, RoomMemberPermission -from utils import user_has_permission, log_event +from utils import user_has_permission, log_event, create_notification +from datetime import datetime room_members_bp = Blueprint('room_members', __name__) @@ -69,6 +70,21 @@ def add_room_member(room_id): permission.can_share = permissions.get('can_share', False) db.session.commit() + + # Create notification for the invited user + create_notification( + notif_type='room_invite', + user_id=user_id, + sender_id=current_user.id, + details={ + 'message': f'You have been invited to join room "{room.name}"', + 'room_id': room_id, + 'room_name': room.name, + 'invited_by': f"{current_user.username} {current_user.last_name}", + 'permissions': permissions, + 'timestamp': datetime.utcnow().isoformat() + } + ) log_event( event_type='room_member_add', @@ -77,8 +93,7 @@ def add_room_member(room_id): 'room_name': room.name, 'added_user_id': user_id, 'added_user_name': f"{user.username} {user.last_name}", - 'added_by': f"{current_user.username} {current_user.last_name}", - 'permissions': permissions + 'added_by': f"{current_user.username} {current_user.last_name}" }, user_id=current_user.id ) @@ -104,6 +119,20 @@ def remove_room_member(room_id, user_id): db.session.delete(permission) db.session.commit() + + # Create notification for the removed user + create_notification( + notif_type='room_invite_removed', + user_id=user_id, + sender_id=current_user.id, + details={ + 'message': f'You have been removed from room "{room.name}"', + 'room_id': room_id, + 'room_name': room.name, + 'removed_by': f"{current_user.username} {current_user.last_name}", + 'timestamp': datetime.utcnow().isoformat() + } + ) log_event( event_type='room_member_remove', diff --git a/static/js/notifications.js b/static/js/notifications.js index 1b65785..933eeee 100644 --- a/static/js/notifications.js +++ b/static/js/notifications.js @@ -21,6 +21,9 @@ document.addEventListener('DOMContentLoaded', function() { const notifDetailsModal = document.getElementById('notifDetailsModal'); const notifDetailsContent = document.getElementById('notifDetailsContent'); + // Get CSRF token from meta tag + const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + // Function to update URL with current filters function updateURL() { const params = new URLSearchParams(window.location.search); @@ -31,7 +34,7 @@ document.addEventListener('DOMContentLoaded', function() { } // Function to fetch notifications - function fetchNotifications() { + async function fetchNotifications() { if (isFetching) return; isFetching = true; @@ -45,54 +48,26 @@ document.addEventListener('DOMContentLoaded', function() { ajax: 'true' }); - fetch(`${window.location.pathname}?${params.toString()}`, { - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }) - .then(response => { + try { + const response = await fetch(`${window.location.pathname}?${params.toString()}`, { + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }); + if (!response.ok) { throw new Error('Network response was not ok'); } - return response.text(); - }) - .then(html => { - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const newTableBody = doc.getElementById('notifsTableBody'); - if (newTableBody) { - notifsTableBody.innerHTML = newTableBody.innerHTML; - - // Update pagination - const newCurrentPage = parseInt(doc.getElementById('currentPage').textContent); - const newTotalPages = parseInt(doc.getElementById('totalPages').textContent); - currentPage = newCurrentPage; - totalPages = newTotalPages; - currentPageSpan.textContent = currentPage; - totalPagesSpan.textContent = totalPages; - - // Update pagination buttons - prevPageBtn.disabled = currentPage === 1; - nextPageBtn.disabled = currentPage === totalPages; - - // Update URL - updateURL(); - - // Reattach event listeners - attachEventListeners(); - } else { - console.error('Could not find notifications table in response'); - notifsTableBody.innerHTML = '