from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash, current_app, session from web.services.stats_service import StatsService from web.services.feature_service import FeatureService from web.services.web_service import WebService from web.database import execute_db from web.config import Config from datetime import datetime import os from werkzeug.utils import secure_filename bp = Blueprint('players', __name__, url_prefix='/players') @bp.route('/') def index(): page = request.args.get('page', 1, type=int) search = request.args.get('search') # Default sort by 'matches' as requested sort_by = request.args.get('sort', 'matches') players, total = FeatureService.get_players_list(page, Config.ITEMS_PER_PAGE, sort_by, search) total_pages = (total + Config.ITEMS_PER_PAGE - 1) // Config.ITEMS_PER_PAGE return render_template('players/list.html', players=players, total=total, page=page, total_pages=total_pages, sort_by=sort_by) @bp.route('/', methods=['GET', 'POST']) def detail(steam_id): if request.method == 'POST': # Check if admin action if 'admin_action' in request.form and session.get('is_admin'): action = request.form.get('admin_action') if action == 'update_profile': notes = request.form.get('notes') # Handle Avatar Upload if 'avatar' in request.files: file = request.files['avatar'] if file and file.filename: try: # Use steam_id as filename to ensure uniqueness per player # Preserve extension ext = os.path.splitext(file.filename)[1].lower() if not ext: ext = '.jpg' filename = f"{steam_id}{ext}" upload_folder = os.path.join(current_app.root_path, 'static', 'avatars') os.makedirs(upload_folder, exist_ok=True) file_path = os.path.join(upload_folder, filename) file.save(file_path) # Generate URL (relative to web root) avatar_url = url_for('static', filename=f'avatars/{filename}') # Update L2 DB directly (Immediate effect) execute_db('l2', "UPDATE dim_players SET avatar_url = ? WHERE steam_id_64 = ?", [avatar_url, steam_id]) flash('Avatar updated successfully.', 'success') except Exception as e: print(f"Avatar upload error: {e}") flash('Error uploading avatar.', 'error') WebService.update_player_metadata(steam_id, notes=notes) flash('Profile updated.', 'success') elif action == 'add_tag': tag = request.form.get('tag') if tag: meta = WebService.get_player_metadata(steam_id) tags = meta.get('tags', []) if tag not in tags: tags.append(tag) WebService.update_player_metadata(steam_id, tags=tags) flash('Tag added.', 'success') elif action == 'remove_tag': tag = request.form.get('tag') if tag: meta = WebService.get_player_metadata(steam_id) tags = meta.get('tags', []) if tag in tags: tags.remove(tag) WebService.update_player_metadata(steam_id, tags=tags) flash('Tag removed.', 'success') return redirect(url_for('players.detail', steam_id=steam_id)) # Add Comment username = request.form.get('username', 'Anonymous') content = request.form.get('content') if content: WebService.add_comment(None, username, 'player', steam_id, content) flash('Comment added!', 'success') return redirect(url_for('players.detail', steam_id=steam_id)) player = StatsService.get_player_info(steam_id) if not player: return "Player not found", 404 features = FeatureService.get_player_features(steam_id) # Ensure basic stats fallback if features missing or incomplete basic = StatsService.get_player_basic_stats(steam_id) if not features: if basic: features = { 'basic_avg_rating': basic.get('rating', 0), 'basic_avg_kd': basic.get('kd', 0), 'basic_avg_kast': basic.get('kast', 0), 'basic_avg_adr': basic.get('adr', 0), # Pass ADR } else: # If features exist but ADR is missing (not in L3), try to patch it from basic if 'basic_avg_adr' not in features: features = dict(features) # Convert to dict if row features['basic_avg_adr'] = basic.get('adr', 0) if basic else 0 comments = WebService.get_comments('player', steam_id) metadata = WebService.get_player_metadata(steam_id) # Roster Distribution Stats distribution = StatsService.get_roster_stats_distribution(steam_id) # History for table (L2 Source) - Fetch ALL for history table/chart history_asc = StatsService.get_player_trend(steam_id, limit=1000) history = history_asc[::-1] if history_asc else [] return render_template('players/profile.html', player=player, features=features, comments=comments, metadata=metadata, history=history, distribution=distribution) @bp.route('/comment//like', methods=['POST']) def like_comment(comment_id): WebService.like_comment(comment_id) return jsonify({'success': True}) @bp.route('//charts_data') def charts_data(steam_id): # ... (existing code) ... # Trend Data trends = StatsService.get_player_trend(steam_id, limit=1000) # Radar Data (Construct from features) features = FeatureService.get_player_features(steam_id) radar_data = {} if features: # Dimensions: STA, BAT, HPS, PTL, T/CT, UTIL radar_data = { 'STA': features['basic_avg_rating'] or 0, 'BAT': features['bat_avg_duel_win_rate'] or 0, 'HPS': features['hps_clutch_win_rate_1v1'] or 0, 'PTL': features['ptl_pistol_win_rate'] or 0, 'SIDE': features['side_rating_ct'] or 0, 'UTIL': features['util_usage_rate'] or 0 } trend_labels = [] trend_values = [] match_indices = [] for i, row in enumerate(trends): t = dict(row) # Convert sqlite3.Row to dict # Format: Match #Index (Map) # Use backend-provided match_index if available, or just index + 1 idx = t.get('match_index', i + 1) map_name = t.get('map_name', 'Unknown') trend_labels.append(f"#{idx} {map_name}") trend_values.append(t['rating']) return jsonify({ 'trend': {'labels': trend_labels, 'values': trend_values}, 'radar': radar_data }) # --- API for Comparison --- @bp.route('/api/search') def api_search(): query = request.args.get('q', '') if len(query) < 2: return jsonify([]) players, _ = FeatureService.get_players_list(page=1, per_page=10, search=query) # Return minimal data results = [{'steam_id': p['steam_id_64'], 'username': p['username'], 'avatar_url': p['avatar_url']} for p in players] return jsonify(results) @bp.route('/api/batch_stats') def api_batch_stats(): steam_ids = request.args.get('ids', '').split(',') stats = [] for sid in steam_ids: if not sid: continue f = FeatureService.get_player_features(sid) p = StatsService.get_player_info(sid) if f and p: stats.append({ 'username': p['username'], 'steam_id': sid, 'radar': { 'STA': f['basic_avg_rating'] or 0, 'BAT': f['bat_avg_duel_win_rate'] or 0, 'HPS': f['hps_clutch_win_rate_1v1'] or 0, 'PTL': f['ptl_pistol_win_rate'] or 0, 'SIDE': f['side_rating_ct'] or 0, 'UTIL': f['util_usage_rate'] or 0 } }) return jsonify(stats)