From fe66775dc8f63b750572cb70e76789e54cfee3df Mon Sep 17 00:00:00 2001 From: Kobe Date: Tue, 27 May 2025 12:07:43 +0200 Subject: [PATCH] Add database cleanup buttons --- routes/__pycache__/admin.cpython-313.pyc | Bin 3880 -> 10730 bytes routes/admin.py | 177 ++++++++++++++++ templates/settings/tabs/debugging.html | 255 +++++++++++++++++++++++ 3 files changed, 432 insertions(+) diff --git a/routes/__pycache__/admin.cpython-313.pyc b/routes/__pycache__/admin.cpython-313.pyc index b2112e45e8025a58135c0b67d9702f39427da6d6..cfc61bbd7b59ffb0a1b98c2d0345ab2c8886b450 100644 GIT binary patch literal 10730 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 delta 185 zcmaDAyh4ueGcPX}0}!0)GR?@~p2#P`G>c)P#w?a}7ESKWW{kht*)k_HGB8w4Hs{o1 z%$%IYX)YiER9++rBBVfs^yIaiWsE|TRk%zUr6xyleG=9N3g6;R%g;$kEs8HlEXgR+ zpB&Gf%jmiJEcYuWCQZS~OEg>=)hEBwkXQ8ts$I$O8Avh|d4TkB*yQG?l;)(`6$Jvh YK&u&weJ5LJdUJhbv0&6M3IGZK0DVp`QUCw| 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 @@
+
File system
+ +
+
+
Database Verification
+
+ + +
+
+ + +
+
{% endmacro %} \ No newline at end of file