from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify from flask_login import login_required, current_user from models import db, Room, User, RoomMemberPermission, RoomFile, Notif, DocuPulseSettings from forms import RoomForm from routes.room_files import user_has_permission from routes.auth import require_password_change from utils import log_event, create_notification, get_unread_count import os import shutil from datetime import datetime rooms_bp = Blueprint('rooms', __name__, url_prefix='/rooms') @rooms_bp.context_processor def inject_unread_notifications(): if current_user.is_authenticated: unread_count = get_unread_count(current_user.id) return {'unread_notifications': unread_count} return {'unread_notifications': 0} @rooms_bp.route('/') @login_required @require_password_change def rooms(): search = request.args.get('search', '').strip() if current_user.is_admin: query = Room.query else: # Get rooms where user is a member and has view permissions query = Room.query.join( RoomMemberPermission, (Room.id == RoomMemberPermission.room_id) & (RoomMemberPermission.user_id == current_user.id) & (RoomMemberPermission.can_view == True) ) if search: query = query.filter(Room.name.ilike(f'%{search}%')) rooms = query.order_by(Room.created_at.desc()).all() # Get usage stats usage_stats = DocuPulseSettings.get_usage_stats() return render_template('rooms/rooms.html', rooms=rooms, search=search, usage_stats=usage_stats) @rooms_bp.route('/create', methods=['GET', 'POST']) @login_required @require_password_change def create_room(): form = RoomForm() if form.validate_on_submit(): room = Room( name=form.name.data, description=form.description.data, created_by=current_user.id ) # Add creator as a member with full permissions room.members.append(current_user) creator_permission = RoomMemberPermission( room=room, user=current_user, can_view=True, can_upload=True, can_delete=True, can_share=True ) db.session.add(room) db.session.add(creator_permission) db.session.commit() log_event( event_type='room_create', details={ 'room_id': room.id, 'room_name': room.name, 'description': room.description, 'created_by': f"{current_user.username} {current_user.last_name}" }, user_id=current_user.id ) db.session.commit() flash('Room created successfully!', 'success') return redirect(url_for('rooms.rooms')) return render_template('rooms/create_room.html', form=form) @rooms_bp.route('/') @login_required @require_password_change def room(room_id): room = Room.query.get_or_404(room_id) # Admins always have access, managers need to be members if not current_user.is_admin: is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None if not is_member: flash('You do not have access to this room.', 'error') return redirect(url_for('rooms.rooms')) can_download = user_has_permission(room, 'can_download') can_upload = user_has_permission(room, 'can_upload') can_delete = user_has_permission(room, 'can_delete') can_rename = user_has_permission(room, 'can_rename') can_move = user_has_permission(room, 'can_move') can_share = user_has_permission(room, 'can_share') log_event( event_type='room_open', details={ 'room_id': room_id, 'room_name': room.name, 'accessed_by': f"{current_user.username} {current_user.last_name}" }, user_id=current_user.id ) db.session.commit() return render_template('rooms/room.html', room=room, can_download=can_download, can_upload=can_upload, can_delete=can_delete, can_rename=can_rename, can_move=can_move, can_share=can_share) @rooms_bp.route('//members') @login_required @require_password_change def room_members(room_id): room = Room.query.get_or_404(room_id) # Check if user is a member if not current_user.is_admin: is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None if not is_member: flash('You do not have access to this room.', 'error') return redirect(url_for('rooms.rooms')) # Only admins and managers who are members can manage room members if not (current_user.is_admin or (current_user.is_manager and is_member)): flash('Only administrators and managers can manage room members.', 'error') return redirect(url_for('rooms.room', room_id=room_id)) member_permissions = {p.user_id: p for p in room.member_permissions} available_users = User.query.filter(~User.id.in_(member_permissions.keys())).all() return render_template('rooms/room_members.html', room=room, available_users=available_users, member_permissions=member_permissions) @rooms_bp.route('//members/add', methods=['POST']) @login_required @require_password_change def add_member(room_id): room = Room.query.get_or_404(room_id) # Membership check using RoomMemberPermission is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None if not is_member: flash('You do not have access to this room.', 'error') return redirect(url_for('rooms.rooms')) # Only admins and managers who are members can manage room members if not (current_user.is_admin or (current_user.is_manager and is_member)): flash('Only administrators and managers can manage room members.', 'error') return redirect(url_for('rooms.room', room_id=room_id)) user_id = request.form.get('user_id') if not user_id: flash('Please select a user to add.', 'error') return redirect(url_for('rooms.room_members', room_id=room_id)) user = User.query.get_or_404(user_id) # Check if already a member if RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user.id).first(): flash('User is already a member of this room.', 'error') else: perm = RoomMemberPermission(room_id=room_id, user_id=user.id, can_view=True) db.session.add(perm) # Ensure user is added to the room.members relationship if user not in room.members: room.members.append(user) try: # 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': { 'can_view': True, 'can_download': False, 'can_upload': False, 'can_delete': False, 'can_rename': False, 'can_move': False, 'can_share': False }, 'timestamp': datetime.utcnow().isoformat() } ) log_event( event_type='room_member_add', details={ 'room_id': room_id, 'room_name': room.name, 'added_user': f"{user.username} {user.last_name}", 'added_by': f"{current_user.username} {current_user.last_name}" }, user_id=current_user.id ) db.session.commit() flash(f'{user.username} has been added to the room.', 'success') except Exception as e: db.session.rollback() flash('An error occurred while adding the member.', 'error') print(f"Error adding member: {str(e)}") return redirect(url_for('rooms.room_members', room_id=room_id)) @rooms_bp.route('//members//remove', methods=['POST']) @login_required @require_password_change def remove_member(room_id, user_id): room = Room.query.get_or_404(room_id) # Membership check using RoomMemberPermission is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None if not is_member: flash('You do not have access to this room.', 'error') return redirect(url_for('rooms.rooms')) # Only admins and managers who are members can manage room members if not (current_user.is_admin or (current_user.is_manager and is_member)): flash('Only administrators and managers can manage room members.', 'error') return redirect(url_for('rooms.room', room_id=room_id)) if user_id == room.created_by: flash('Cannot remove the room creator.', 'error') else: perm = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user_id).first() if perm: db.session.delete(perm) db.session.commit() flash('Member has been removed from the room.', 'success') else: flash('Member not found.', 'error') return redirect(url_for('rooms.room_members', room_id=room_id)) @rooms_bp.route('//members//permissions', methods=['POST']) @login_required def update_member_permissions(room_id, user_id): room = Room.query.get_or_404(room_id) # Check if user is a member is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None if not (current_user.is_admin or (current_user.is_manager and is_member)): flash('Only administrators and managers can update permissions.', 'error') return redirect(url_for('rooms.room_members', room_id=room_id)) perm = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user_id).first() if not perm: flash('Member not found.', 'error') return redirect(url_for('rooms.room_members', room_id=room_id)) # Get the user object user = User.query.get(user_id) if not user: flash('User not found.', 'error') return redirect(url_for('rooms.room_members', room_id=room_id)) perm.can_view = bool(request.form.get('can_view')) perm.can_download = bool(request.form.get('can_download')) perm.can_upload = bool(request.form.get('can_upload')) perm.can_delete = bool(request.form.get('can_delete')) perm.can_rename = bool(request.form.get('can_rename')) perm.can_move = bool(request.form.get('can_move')) perm.can_share = bool(request.form.get('can_share')) db.session.commit() log_event( event_type='room_permission_update', details={ 'room_id': room_id, 'room_name': room.name, 'user': f"{user.username} {user.last_name}", 'updated_by': f"{current_user.username} {current_user.last_name}", 'permissions': { 'can_view': perm.can_view, 'can_download': perm.can_download, 'can_upload': perm.can_upload, 'can_delete': perm.can_delete, 'can_rename': perm.can_rename, 'can_move': perm.can_move, 'can_share': perm.can_share } }, user_id=current_user.id ) db.session.commit() flash('Permissions updated.', 'success') return redirect(url_for('rooms.room_members', room_id=room_id)) @rooms_bp.route('//edit', methods=['GET', 'POST']) @login_required def edit_room(room_id): room = Room.query.get_or_404(room_id) # Check if user is a member is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None if not (current_user.is_admin or (current_user.is_manager and is_member)): flash('Only administrators and managers can edit rooms.', 'error') return redirect(url_for('rooms.rooms')) form = RoomForm() if form.validate_on_submit(): old_name = room.name old_description = room.description room.name = form.name.data room.description = form.description.data db.session.commit() log_event( event_type='room_update', details={ 'room_id': room.id, 'old_name': old_name, 'new_name': room.name, 'old_description': old_description, 'new_description': room.description, 'updated_by': f"{current_user.username} {current_user.last_name}" }, user_id=current_user.id ) db.session.commit() flash('Room updated successfully!', 'success') return redirect(url_for('rooms.rooms')) # Pre-populate form with existing room data if request.method == 'GET': form.name.data = room.name form.description.data = room.description return render_template('rooms/edit_room.html', form=form, room=room) @rooms_bp.route('//delete', methods=['POST']) @login_required def delete_room(room_id): room = Room.query.get_or_404(room_id) # Check if user is a member is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None if not (current_user.is_admin or (current_user.is_manager and is_member)): flash('Only administrators and managers can delete rooms.', 'error') return redirect(url_for('rooms.rooms')) room_name = room.name try: print(f"Attempting to delete room {room_id} ({room_name})") # Delete physical files room_dir = os.path.join('/app/uploads/rooms', str(room_id)) if os.path.exists(room_dir): shutil.rmtree(room_dir) print(f"Deleted room directory: {room_dir}") # Delete the room (cascade will handle the rest) db.session.delete(room) db.session.commit() print("Room deleted successfully") log_event( event_type='room_delete', details={ 'room_id': room_id, 'room_name': room_name, 'deleted_by': f"{current_user.username} {current_user.last_name}" }, user_id=current_user.id ) db.session.commit() flash(f'Room "{room_name}" has been deleted.', 'success') except Exception as e: db.session.rollback() flash('An error occurred while deleting the room. Please try again.', 'error') print(f"Error deleting room: {str(e)}") return redirect(url_for('rooms.rooms')) @rooms_bp.route('/room//view/') @login_required def view_file(room_id, file_path): room = Room.query.get_or_404(room_id) # Check if user has access to the room if not current_user.is_admin and current_user not in room.members: flash('You do not have access to this room.', 'error') return redirect(url_for('rooms.rooms')) file = RoomFile.query.filter_by(room_id=room_id, path=file_path).first_or_404() # Continue with file viewing logic...