Documentation in room files py
This commit is contained in:
Binary file not shown.
@@ -1,3 +1,24 @@
|
|||||||
|
"""
|
||||||
|
Room Files Management Module
|
||||||
|
|
||||||
|
This module provides a Flask Blueprint for managing files within rooms in the DocuPulse application.
|
||||||
|
It handles file operations such as uploading, downloading, moving, deleting, and organizing files
|
||||||
|
within room contexts. The module implements permission-based access control and supports various
|
||||||
|
file operations including file organization, starring, and trash management.
|
||||||
|
|
||||||
|
Key Features:
|
||||||
|
- File upload and download with permission checks
|
||||||
|
- Folder creation and management
|
||||||
|
- File operations (move, rename, delete)
|
||||||
|
- File starring system
|
||||||
|
- Trash management with restore capability
|
||||||
|
- Bulk operations (zip download)
|
||||||
|
- File search functionality
|
||||||
|
|
||||||
|
The module uses a combination of database records (RoomFile model) and actual file system storage
|
||||||
|
to maintain file metadata and content.
|
||||||
|
"""
|
||||||
|
|
||||||
from flask import Blueprint, jsonify, request, abort, send_from_directory, send_file
|
from flask import Blueprint, jsonify, request, abort, send_from_directory, send_file
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
import os
|
import os
|
||||||
@@ -9,10 +30,13 @@ import io
|
|||||||
import zipfile
|
import zipfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Blueprint for room file operations
|
||||||
room_files_bp = Blueprint('room_files', __name__, url_prefix='/api/rooms')
|
room_files_bp = Blueprint('room_files', __name__, url_prefix='/api/rooms')
|
||||||
|
|
||||||
|
# Root directory for storing room files
|
||||||
DATA_ROOT = '/data/rooms' # This should be a Docker volume
|
DATA_ROOT = '/data/rooms' # This should be a Docker volume
|
||||||
|
|
||||||
|
# Set of allowed file extensions for upload
|
||||||
ALLOWED_EXTENSIONS = {
|
ALLOWED_EXTENSIONS = {
|
||||||
# Documents
|
# Documents
|
||||||
'pdf', 'docx', 'doc', 'txt', 'rtf', 'odt', 'md', 'csv',
|
'pdf', 'docx', 'doc', 'txt', 'rtf', 'odt', 'md', 'csv',
|
||||||
@@ -37,18 +61,55 @@ ALLOWED_EXTENSIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_room_dir(room_id):
|
def get_room_dir(room_id):
|
||||||
|
"""
|
||||||
|
Get the absolute path to a room's directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): The ID of the room
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Absolute path to the room's directory
|
||||||
|
"""
|
||||||
return os.path.join(DATA_ROOT, str(room_id))
|
return os.path.join(DATA_ROOT, str(room_id))
|
||||||
|
|
||||||
def user_has_permission(room, perm_name):
|
def user_has_permission(room, perm_name):
|
||||||
|
"""
|
||||||
|
Check if the current user has a specific permission in a room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room (Room): The room object to check permissions for
|
||||||
|
perm_name (str): Name of the permission to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if user has permission, False otherwise
|
||||||
|
"""
|
||||||
if current_user.is_admin:
|
if current_user.is_admin:
|
||||||
return True
|
return True
|
||||||
perm = RoomMemberPermission.query.filter_by(room_id=room.id, user_id=current_user.id).first()
|
perm = RoomMemberPermission.query.filter_by(room_id=room.id, user_id=current_user.id).first()
|
||||||
return getattr(perm, perm_name, False) if perm else False
|
return getattr(perm, perm_name, False) if perm else False
|
||||||
|
|
||||||
def allowed_file(filename):
|
def allowed_file(filename):
|
||||||
|
"""
|
||||||
|
Check if a file's extension is in the allowed extensions list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): Name of the file to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if file extension is allowed, False otherwise
|
||||||
|
"""
|
||||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||||
|
|
||||||
def clean_path(path):
|
def clean_path(path):
|
||||||
|
"""
|
||||||
|
Clean a path string by removing leading/trailing slashes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): Path string to clean
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Cleaned path string
|
||||||
|
"""
|
||||||
if not path:
|
if not path:
|
||||||
return ''
|
return ''
|
||||||
return path.strip('/\\')
|
return path.strip('/\\')
|
||||||
@@ -56,6 +117,15 @@ def clean_path(path):
|
|||||||
@room_files_bp.route('/<int:room_id>/files', methods=['GET'])
|
@room_files_bp.route('/<int:room_id>/files', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def list_room_files(room_id):
|
def list_room_files(room_id):
|
||||||
|
"""
|
||||||
|
List all files in a room's directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room to list files from
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response containing list of files with their metadata
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_view'):
|
if not user_has_permission(room, 'can_view'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -170,6 +240,16 @@ def upload_room_file(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/files/<filename>', methods=['GET'])
|
@room_files_bp.route('/<int:room_id>/files/<filename>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def download_room_file(room_id, filename):
|
def download_room_file(room_id, filename):
|
||||||
|
"""
|
||||||
|
Download a file from a room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room containing the file
|
||||||
|
filename (str): Name of the file to download
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
File download response or error message
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_download'):
|
if not user_has_permission(room, 'can_download'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -187,6 +267,16 @@ def download_room_file(room_id, filename):
|
|||||||
@room_files_bp.route('/<int:room_id>/files/<path:filename>', methods=['DELETE'])
|
@room_files_bp.route('/<int:room_id>/files/<path:filename>', methods=['DELETE'])
|
||||||
@login_required
|
@login_required
|
||||||
def delete_file(room_id, filename):
|
def delete_file(room_id, filename):
|
||||||
|
"""
|
||||||
|
Delete a file from a room (moves to trash).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room containing the file
|
||||||
|
filename (str): Name of the file to delete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response indicating success or error
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_delete'):
|
if not user_has_permission(room, 'can_delete'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -209,6 +299,15 @@ def delete_file(room_id, filename):
|
|||||||
@room_files_bp.route('/<int:room_id>/folders', methods=['POST'])
|
@room_files_bp.route('/<int:room_id>/folders', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def create_room_folder(room_id):
|
def create_room_folder(room_id):
|
||||||
|
"""
|
||||||
|
Create a new folder in a room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room to create folder in
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response indicating success or error
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_upload'):
|
if not user_has_permission(room, 'can_upload'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -271,6 +370,15 @@ def create_room_folder(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/rename', methods=['POST'])
|
@room_files_bp.route('/<int:room_id>/rename', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def rename_room_file(room_id):
|
def rename_room_file(room_id):
|
||||||
|
"""
|
||||||
|
Rename a file or folder in a room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room containing the file/folder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response indicating success or error
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
# Allow rename if user can upload or delete
|
# Allow rename if user can upload or delete
|
||||||
if not (user_has_permission(room, 'can_upload') or user_has_permission(room, 'can_delete')):
|
if not (user_has_permission(room, 'can_upload') or user_has_permission(room, 'can_delete')):
|
||||||
@@ -326,6 +434,15 @@ def rename_room_file(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/download-zip', methods=['POST'])
|
@room_files_bp.route('/<int:room_id>/download-zip', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def download_zip(room_id):
|
def download_zip(room_id):
|
||||||
|
"""
|
||||||
|
Download multiple files as a zip archive.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room containing the files
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ZIP file download response
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_view'):
|
if not user_has_permission(room, 'can_view'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -356,6 +473,15 @@ def download_zip(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/search', methods=['GET'])
|
@room_files_bp.route('/<int:room_id>/search', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def search_room_files(room_id):
|
def search_room_files(room_id):
|
||||||
|
"""
|
||||||
|
Search for files in a room by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room to search in
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response containing matching files
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_view'):
|
if not user_has_permission(room, 'can_view'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -379,6 +505,15 @@ def search_room_files(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/move', methods=['POST'])
|
@room_files_bp.route('/<int:room_id>/move', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def move_room_file(room_id):
|
def move_room_file(room_id):
|
||||||
|
"""
|
||||||
|
Move a file or folder to a different location within a room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room containing the file/folder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response indicating success or error
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_move'):
|
if not user_has_permission(room, 'can_move'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -424,6 +559,15 @@ def move_room_file(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/folders', methods=['GET'])
|
@room_files_bp.route('/<int:room_id>/folders', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def list_room_folders(room_id):
|
def list_room_folders(room_id):
|
||||||
|
"""
|
||||||
|
List all folders in a room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room to list folders from
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response containing list of folder paths
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_view'):
|
if not user_has_permission(room, 'can_view'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -446,6 +590,15 @@ def list_room_folders(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/star', methods=['POST'])
|
@room_files_bp.route('/<int:room_id>/star', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def toggle_star(room_id):
|
def toggle_star(room_id):
|
||||||
|
"""
|
||||||
|
Toggle the starred status of a file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room containing the file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response indicating success and new starred status
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_view'):
|
if not user_has_permission(room, 'can_view'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -479,6 +632,15 @@ def toggle_star(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/starred', methods=['GET'])
|
@room_files_bp.route('/<int:room_id>/starred', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def get_starred_files(room_id):
|
def get_starred_files(room_id):
|
||||||
|
"""
|
||||||
|
Get all starred files in a room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room to get starred files from
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response containing list of starred files
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
if not user_has_permission(room, 'can_view'):
|
if not user_has_permission(room, 'can_view'):
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -512,6 +674,12 @@ def get_starred_files(room_id):
|
|||||||
@room_files_bp.route('/starred', methods=['GET'])
|
@room_files_bp.route('/starred', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def get_all_starred_files():
|
def get_all_starred_files():
|
||||||
|
"""
|
||||||
|
Get all starred files across all accessible rooms.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response containing list of all starred files
|
||||||
|
"""
|
||||||
# Get all rooms the user has access to
|
# Get all rooms the user has access to
|
||||||
if current_user.is_admin:
|
if current_user.is_admin:
|
||||||
rooms = Room.query.all()
|
rooms = Room.query.all()
|
||||||
@@ -555,6 +723,12 @@ def get_all_starred_files():
|
|||||||
@room_files_bp.route('/trash', methods=['GET'])
|
@room_files_bp.route('/trash', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def get_trash_files():
|
def get_trash_files():
|
||||||
|
"""
|
||||||
|
Get all deleted files from accessible rooms.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response containing list of deleted files
|
||||||
|
"""
|
||||||
# Get all rooms the user has access to
|
# Get all rooms the user has access to
|
||||||
if current_user.is_admin:
|
if current_user.is_admin:
|
||||||
rooms = Room.query.all()
|
rooms = Room.query.all()
|
||||||
@@ -614,6 +788,15 @@ def get_trash_files():
|
|||||||
@room_files_bp.route('/<int:room_id>/restore', methods=['POST'])
|
@room_files_bp.route('/<int:room_id>/restore', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def restore_file(room_id):
|
def restore_file(room_id):
|
||||||
|
"""
|
||||||
|
Restore a deleted file from trash.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room containing the deleted file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response indicating success or error
|
||||||
|
"""
|
||||||
room = Room.query.get_or_404(room_id)
|
room = Room.query.get_or_404(room_id)
|
||||||
# Check for delete permission instead of view permission
|
# Check for delete permission instead of view permission
|
||||||
if not user_has_permission(room, 'can_delete'):
|
if not user_has_permission(room, 'can_delete'):
|
||||||
@@ -644,6 +827,15 @@ def restore_file(room_id):
|
|||||||
@room_files_bp.route('/<int:room_id>/delete-permanent', methods=['POST'])
|
@room_files_bp.route('/<int:room_id>/delete-permanent', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def delete_permanent(room_id):
|
def delete_permanent(room_id):
|
||||||
|
"""
|
||||||
|
Permanently delete files from trash (admin only).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id (int): ID of the room containing the files to delete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response indicating success or error
|
||||||
|
"""
|
||||||
# Only allow admin users to permanently delete files
|
# Only allow admin users to permanently delete files
|
||||||
if not current_user.is_admin:
|
if not current_user.is_admin:
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|||||||
Reference in New Issue
Block a user