import os import tempfile import zipfile import threading import uuid from functools import wraps from flask import Flask, request, render_template_string, redirect, url_for, session, send_from_directory from werkzeug.utils import secure_filename from PyPDF2 import PdfReader from authlib.integrations.flask_client import OAuth app = Flask(__name__) app.config['UPLOAD_FOLDER'] = tempfile.mkdtemp() app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB limit app.secret_key = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') # Entra ID Configuration TENANT_ID = os.getenv('TENANT_ID', 'your-tenant-id') CLIENT_ID = os.getenv('CLIENT_ID', 'your-client-id') CLIENT_SECRET = os.getenv('CLIENT_SECRET', 'your-client-secret') # Admin Configuration ADMIN_USERNAME = os.getenv('ADMIN_USERNAME', 'trbcit') ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD', 'atdrmbicn') oauth = OAuth(app) microsoft = oauth.register( name='microsoft', client_id=CLIENT_ID, client_secret=CLIENT_SECRET, server_metadata_url=f'https://login.microsoftonline.com/{TENANT_ID}/v2.0/.well-known/openid_configuration', client_kwargs={'scope': 'openid profile email'} ) ALLOWED_EXTENSIONS = {'pdf', 'zip'} # Global jobs storage (in production, use database) jobs = {} def login_required(f): def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'user' not in session and 'admin' not in session: return redirect(url_for('login')) return f(*args, **kwargs) return decorated_function def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def extract_names_from_pdf(pdf_path): try: reader = PdfReader(pdf_path) fields = reader.get_fields() last_name = fields.get('Last Name', {}).get('/V', '') first_name = fields.get('First Name', {}).get('/V', '') if last_name and first_name: return f"{last_name}_{first_name}.pdf" else: return None except Exception as e: return None def process_job(job_id, files): jobs[job_id]['status'] = 'processing' processed_files = [] temp_extract_dirs = [] for file_info in files: filepath = file_info['path'] filename = file_info['name'] if filename.lower().endswith('.zip'): # Extract ZIP extract_dir = os.path.join(app.config['UPLOAD_FOLDER'], f'extract_{job_id}_{os.path.splitext(filename)[0]}') os.makedirs(extract_dir, exist_ok=True) temp_extract_dirs.append(extract_dir) with zipfile.ZipFile(filepath, 'r') as zip_ref: zip_ref.extractall(extract_dir) # Process PDFs in extract_dir for root, dirs, files_in_zip in os.walk(extract_dir): for f in files_in_zip: if f.lower().endswith('.pdf'): pdf_path = os.path.join(root, f) new_name = extract_names_from_pdf(pdf_path) if new_name: new_path = os.path.join(root, new_name) counter = 1 while os.path.exists(new_path): base, ext = os.path.splitext(new_name) new_path = os.path.join(root, f"{base}_{counter}{ext}") counter += 1 os.rename(pdf_path, new_path) processed_files.append(('success', os.path.relpath(new_path, extract_dir), new_path)) else: processed_files.append(('skipped', f, None)) else: # Single PDF new_name = extract_names_from_pdf(filepath) if new_name: new_filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"{job_id}_{new_name}") counter = 1 while os.path.exists(new_filepath): base, ext = os.path.splitext(new_name) new_filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"{job_id}_{base}_{counter}{ext}") counter += 1 os.rename(filepath, new_filepath) processed_files.append(('success', os.path.basename(new_filepath), new_filepath)) else: processed_files.append(('skipped', filename, None)) # Create ZIP of all successful renames successful_files = [p for p in processed_files if p[0] == 'success'] if successful_files: zip_output = os.path.join(app.config['UPLOAD_FOLDER'], f'renamed_{job_id}.zip') with zipfile.ZipFile(zip_output, 'w') as zip_out: for _, rel_name, full_path in successful_files: zip_out.write(full_path, rel_name) jobs[job_id]['zip_path'] = zip_output jobs[job_id]['zip_name'] = f'renamed_{job_id}.zip' jobs[job_id]['status'] = 'completed' jobs[job_id]['processed_files'] = processed_files jobs[job_id]['skipped'] = [p[1] for p in processed_files if p[0] == 'skipped'] @app.route('/login') def login(): return render_template_string(''' Login Logo

PDF Renamer Login

Microsoft Entra ID

Login with Microsoft

Admin Login



''') @app.route('/entra-login') def entra_login(): redirect_uri = url_for('auth', _external=True) return microsoft.authorize_redirect(redirect_uri) @app.route('/admin-login', methods=['POST']) def admin_login(): username = request.form.get('username') password = request.form.get('password') if username == ADMIN_USERNAME and password == ADMIN_PASSWORD: session['admin'] = True return redirect(url_for('upload_files')) else: return render_template_string(''' Login Failed

Login Failed

Invalid username or password.

Try Again
''') @app.route('/auth') def auth(): token = microsoft.authorize_access_token() user = microsoft.parse_id_token(token) session['user'] = user return redirect(url_for('upload_files')) @app.route('/logout') @login_required def logout(): session.pop('user', None) session.pop('admin', None) return redirect(url_for('login')) @app.route('/', methods=['GET', 'POST']) @login_required def upload_files(): if request.method == 'POST': files = request.files.getlist('files') if not files or all(not f.filename for f in files): return redirect(url_for('upload_files')) job_id = str(uuid.uuid4()) jobs[job_id] = { 'status': 'queued', 'files': [], 'zip_path': None, 'zip_name': None, 'processed_files': [], 'skipped': [] } file_list = [] for file in files: if file and allowed_file(file.filename): filename = secure_filename(file.filename) filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"{job_id}_{filename}") file.save(filepath) file_list.append({'name': filename, 'path': filepath}) jobs[job_id]['files'] = file_list # Start processing in background thread = threading.Thread(target=process_job, args=(job_id, file_list)) thread.daemon = True thread.start() return redirect(url_for('jobs')) return render_template_string(''' PDF Renamer Logout Logo

Upload PDF Files or ZIP to Rename



''') @app.route('/jobs') @login_required def jobs(): return render_template_string(''' Jobs Logout Logo

Processing Jobs

{% for job_id, job in jobs.items() %}

Job {{ job_id[:8] }}

Status: {{ job.status.title() }}

Files: {{ job.files|length }}

{% if job.status == 'completed' %} {% if job.zip_name %}

Download ZIP

{% endif %} {% if job.skipped %}

Skipped Files:

{% endif %} {% endif %}
{% endfor %} {% if not jobs %}

No jobs yet. Upload some files to get started.

{% endif %} ''', jobs=jobs) if __name__ == '__main__': app.run(debug=True, port=3000)