diff --git a/routes/__pycache__/admin.cpython-313.pyc b/routes/__pycache__/admin.cpython-313.pyc index b2112e4..cfc61bb 100644 Binary files a/routes/__pycache__/admin.cpython-313.pyc and b/routes/__pycache__/admin.cpython-313.pyc differ diff --git a/routes/admin.py b/routes/admin.py index 248d62c..2ef39f2 100644 --- a/routes/admin.py +++ b/routes/admin.py @@ -62,6 +62,183 @@ def sync_files(): db.session.add(rf) db.session.commit() return jsonify({'success': True, 'message': 'File system synchronized successfully'}) + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 500 + +@admin.route('/api/admin/verify-db-state', methods=['GET']) +@login_required +def verify_db_state(): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + try: + DATA_ROOT = '/data/rooms' + verification_results = { + 'rooms_checked': 0, + 'files_in_db_not_fs': [], + 'files_in_fs_not_db': [], + 'permission_mismatches': [], + 'size_mismatches': [], + 'modified_time_mismatches': [], + 'total_files_checked': 0, + 'total_folders_checked': 0 + } + + rooms = Room.query.all() + for room in rooms: + verification_results['rooms_checked'] += 1 + room_dir = os.path.join(DATA_ROOT, str(room.id)) + + # Get all files and folders from database for this room + db_files = RoomFile.query.filter_by(room_id=room.id, deleted=False).all() + db_paths = {(f.path, f.name): f for f in db_files} + + # Check filesystem if directory exists + if os.path.exists(room_dir): + for root, dirs, files in os.walk(room_dir): + rel_root = os.path.relpath(root, room_dir) + rel_path = '' if rel_root == '.' else rel_root.replace('\\', '/') + + # Check folders + for d in dirs: + verification_results['total_folders_checked'] += 1 + folder_path = os.path.join(root, d) + stat = os.stat(folder_path) + + # Check if folder exists in database + db_file = db_paths.get((rel_path, d)) + if not db_file: + verification_results['files_in_fs_not_db'].append({ + 'room_id': room.id, + 'room_name': room.name, + 'path': rel_path, + 'name': d, + 'type': 'folder' + }) + else: + # Verify folder metadata + if abs(stat.st_mtime - db_file.modified) > 1: # Allow 1 second difference + verification_results['modified_time_mismatches'].append({ + 'room_id': room.id, + 'room_name': room.name, + 'path': rel_path, + 'name': d, + 'type': 'folder', + 'fs_modified': stat.st_mtime, + 'db_modified': db_file.modified + }) + # Remove from db_paths as we've checked it + db_paths.pop((rel_path, d), None) + + # Check files + for f in files: + verification_results['total_files_checked'] += 1 + file_path = os.path.join(root, f) + stat = os.stat(file_path) + + # Check if file exists in database + db_file = db_paths.get((rel_path, f)) + if not db_file: + verification_results['files_in_fs_not_db'].append({ + 'room_id': room.id, + 'room_name': room.name, + 'path': rel_path, + 'name': f, + 'type': 'file' + }) + else: + # Verify file metadata + if abs(stat.st_mtime - db_file.modified) > 1: # Allow 1 second difference + verification_results['modified_time_mismatches'].append({ + 'room_id': room.id, + 'room_name': room.name, + 'path': rel_path, + 'name': f, + 'type': 'file', + 'fs_modified': stat.st_mtime, + 'db_modified': db_file.modified + }) + if stat.st_size != db_file.size: + verification_results['size_mismatches'].append({ + 'room_id': room.id, + 'room_name': room.name, + 'path': rel_path, + 'name': f, + 'type': 'file', + 'fs_size': stat.st_size, + 'db_size': db_file.size + }) + # Remove from db_paths as we've checked it + db_paths.pop((rel_path, f), None) + + # Any remaining items in db_paths are in DB but not in filesystem + for (path, name), db_file in db_paths.items(): + verification_results['files_in_db_not_fs'].append({ + 'room_id': room.id, + 'room_name': room.name, + 'path': path, + 'name': name, + 'type': db_file.type + }) + + # Calculate total issues + total_issues = ( + len(verification_results['files_in_db_not_fs']) + + len(verification_results['files_in_fs_not_db']) + + len(verification_results['permission_mismatches']) + + len(verification_results['size_mismatches']) + + len(verification_results['modified_time_mismatches']) + ) + + # Add summary statistics + verification_results['summary'] = { + 'total_issues': total_issues, + 'status': 'healthy' if total_issues == 0 else 'issues_found' + } + + return jsonify(verification_results) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@admin.route('/api/admin/cleanup-orphaned-records', methods=['POST']) +@login_required +def cleanup_orphaned_records(): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + try: + DATA_ROOT = '/data/rooms' + rooms = Room.query.all() + cleaned_records = [] + + for room in rooms: + room_dir = os.path.join(DATA_ROOT, str(room.id)) + + # Get all files and folders from database for this room + db_files = RoomFile.query.filter_by(room_id=room.id, deleted=False).all() + + for db_file in db_files: + file_path = os.path.join(room_dir, db_file.path, db_file.name) if db_file.path else os.path.join(room_dir, db_file.name) + + # If file doesn't exist in filesystem, mark it as deleted in database + if not os.path.exists(file_path): + db_file.deleted = True + cleaned_records.append({ + 'room_id': room.id, + 'room_name': room.name, + 'path': db_file.path, + 'name': db_file.name, + 'type': db_file.type + }) + + db.session.commit() + + return jsonify({ + 'success': True, + 'message': f'Cleaned up {len(cleaned_records)} orphaned records', + 'cleaned_records': cleaned_records + }) except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 \ No newline at end of file diff --git a/templates/settings/tabs/debugging.html b/templates/settings/tabs/debugging.html index 8a75f7e..8e6e4a6 100644 --- a/templates/settings/tabs/debugging.html +++ b/templates/settings/tabs/debugging.html @@ -2,6 +2,7 @@