From fcf10bcdffe61aa5fcd197a12f7e1c3710e9c83f Mon Sep 17 00:00:00 2001 From: cedrick2711 Date: Thu, 6 Aug 2026 14:46:51 -0400 Subject: [PATCH] bug fix: Manager ne pouvait pas voir les tryouts. probleme avec discord bot --- app/discord_bot.py | 71 +++++++++------ app/models/_associations.py | 9 +- app/models/tryout/tryout.py | 6 +- app/models/user_model/coach.py | 8 ++ app/models/user_model/manager.py | 5 +- app/routes/tryouts.py | 25 ++++-- app/templates/pages/profile.html | 124 ++++++++++++++++++++------- app/templates/pages/tryout_form.html | 22 +++-- app/templates/pages/users.html | 2 +- app/templates/pages/view_tryout.html | 12 ++- app/templates/pages/view_user.html | 10 +++ migrations/add_tryout_coaches.py | 64 ++++++++++++++ migrations/add_tryout_end_date.py | 38 -------- 13 files changed, 279 insertions(+), 117 deletions(-) create mode 100644 migrations/add_tryout_coaches.py delete mode 100644 migrations/add_tryout_end_date.py diff --git a/app/discord_bot.py b/app/discord_bot.py index e496eb8..6ce8af0 100644 --- a/app/discord_bot.py +++ b/app/discord_bot.py @@ -114,21 +114,36 @@ class TeamTryoutsBot(commands.Bot): """Check if a channel is a DM channel.""" return hasattr(channel, 'recipient') or hasattr(channel, 'recipients') - async def on_reaction_add(self, reaction, user): - """Handle when a reaction is added to a message.""" - if user.bot: + async def on_raw_reaction_add(self, payload): + """Handle when a reaction is added to a message (works even after bot restart).""" + # Ignore bot's own reactions + if payload.user_id == self.user.id: return - if not self.is_dm_channel(reaction.message.channel): + # Check if this is a pending request we're tracking + if payload.message_id not in self.pending_requests: return - message_id = reaction.message.id - - if message_id not in self.pending_requests: + # Fetch the channel and check if it's a DM + try: + channel = await self.fetch_channel(payload.channel_id) + except Exception: return - request_info = self.pending_requests[message_id] - emoji_str = str(reaction.emoji) + if not self.is_dm_channel(channel): + return + + # Fetch the user who reacted + try: + user = await self.fetch_user(payload.user_id) + except Exception: + return + + if user is None: + return + + request_info = self.pending_requests[payload.message_id] + emoji_str = str(payload.emoji) handler_type = request_info.get('type') request_id = request_info.get('id') @@ -137,28 +152,28 @@ class TeamTryoutsBot(commands.Bot): if handler_type == 'one_on_one': if self.flask_app: with self.flask_app.app_context(): - await self.handle_one_on_one_approve(user, message_id, request_id, reaction.message) + await self.handle_one_on_one_approve(user, payload.message_id, request_id, channel) else: - await self.handle_one_on_one_approve(user, message_id, request_id, reaction.message) + await self.handle_one_on_one_approve(user, payload.message_id, request_id, channel) elif handler_type == 'schedule_addition': if self.flask_app: with self.flask_app.app_context(): - await self.handle_attendance_confirm(user, message_id, request_id, reaction.message) + await self.handle_attendance_confirm(user, payload.message_id, request_id, channel) else: - await self.handle_attendance_confirm(user, message_id, request_id, reaction.message) + await self.handle_attendance_confirm(user, payload.message_id, request_id, channel) elif emoji_str == CROSS_EMOJI: if handler_type == 'one_on_one': if self.flask_app: with self.flask_app.app_context(): - await self.handle_one_on_one_reject(user, message_id, request_id, reaction.message) + await self.handle_one_on_one_reject(user, payload.message_id, request_id, channel) else: - await self.handle_one_on_one_reject(user, message_id, request_id, reaction.message) + await self.handle_one_on_one_reject(user, payload.message_id, request_id, channel) elif handler_type == 'schedule_addition': if self.flask_app: with self.flask_app.app_context(): - await self.handle_attendance_decline(user, message_id, request_id, reaction.message) + await self.handle_attendance_decline(user, payload.message_id, request_id, channel) else: - await self.handle_attendance_decline(user, message_id, request_id, reaction.message) + await self.handle_attendance_decline(user, payload.message_id, request_id, channel) async def _send_one_on_one_dm(self, coach_name: str, coach_discord_id: str, player_name: str, team_name: str, date_str: str, start_time: str, end_time: str, @@ -259,7 +274,7 @@ class TeamTryoutsBot(commands.Bot): logger.error(f"Error sending schedule notification: {e}") return None - async def handle_one_on_one_approve(self, coach, message_id, request_id, original_message): + async def handle_one_on_one_approve(self, coach, message_id, request_id, channel): """Handle coach approving a One on One request.""" try: from app.models import OneOnOneRequest @@ -270,7 +285,7 @@ class TeamTryoutsBot(commands.Bot): return if request.coach.discord_user_id != str(coach.id): - await original_message.channel.send("⚠️ You are not the intended recipient.") + await channel.send("⚠️ You are not the intended recipient.") return # Re-attach to current session (object may be detached across app contexts) @@ -285,7 +300,7 @@ class TeamTryoutsBot(commands.Bot): request.responded_at = datetime.utcnow() db.session.commit() - await original_message.channel.send( + await channel.send( f"✅ You have **approved** the One on One session with {player_full_name}." ) @@ -303,7 +318,7 @@ class TeamTryoutsBot(commands.Bot): except Exception as e: logger.error(f"Error handling approval: {e}\n{traceback.format_exc()}") - async def handle_one_on_one_reject(self, coach, message_id, request_id, original_message): + async def handle_one_on_one_reject(self, coach, message_id, request_id, channel): """Handle coach rejecting a One on One request.""" try: from app.models import OneOnOneRequest @@ -314,7 +329,7 @@ class TeamTryoutsBot(commands.Bot): return if request.coach.discord_user_id != str(coach.id): - await original_message.channel.send("⚠️ You are not the intended recipient.") + await channel.send("⚠️ You are not the intended recipient.") return # Re-attach to current session (object may be detached across app contexts) @@ -326,7 +341,7 @@ class TeamTryoutsBot(commands.Bot): refusal_note = None try: - async for reply in original_message.channel.history(limit=20): + async for reply in channel.history(limit=20): if reply.author.id == coach.id and reply.reference and reply.reference.message_id == message_id: refusal_note = reply.content break @@ -345,7 +360,7 @@ class TeamTryoutsBot(commands.Bot): else: rejection_msg += "\n\nℹ️ The player has been notified that you are not available." - await original_message.channel.send(rejection_msg) + await channel.send(rejection_msg) if player_discord_id: await self.notify_player_about_one_on_one_direct( player_discord_id=player_discord_id, @@ -360,7 +375,7 @@ class TeamTryoutsBot(commands.Bot): except Exception as e: logger.error(f"Error handling rejection: {e}\n{traceback.format_exc()}") - async def handle_attendance_confirm(self, player, message_id, reference_id, original_message): + async def handle_attendance_confirm(self, player, message_id, reference_id, channel): """Handle player confirming attendance for a match/tryout.""" try: from app.models import MatchParticipant, TryoutRegistration, Match, Tryout @@ -382,13 +397,13 @@ class TeamTryoutsBot(commands.Bot): db.session.commit() - await original_message.channel.send("✅ Your attendance has been confirmed!") + await channel.send("✅ Your attendance has been confirmed!") del self.pending_requests[message_id] except Exception as e: logger.error(f"Error handling attendance confirmation: {e}\n{traceback.format_exc()}") - async def handle_attendance_decline(self, player, message_id, reference_id, original_message): + async def handle_attendance_decline(self, player, message_id, reference_id, channel): """Handle player declining attendance for a match/tryout.""" try: from app.models import MatchParticipant, TryoutRegistration, Match, Tryout @@ -410,7 +425,7 @@ class TeamTryoutsBot(commands.Bot): db.session.commit() - await original_message.channel.send("❌ Your attendance has been declined.") + await channel.send("❌ Your attendance has been declined.") del self.pending_requests[message_id] except Exception as e: diff --git a/app/models/_associations.py b/app/models/_associations.py index c0d30be..53b2775 100644 --- a/app/models/_associations.py +++ b/app/models/_associations.py @@ -15,4 +15,11 @@ org_team_managers = db.Table('org_team_managers', primary_key=True), db.Column('manager_id', db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), primary_key=True), -) \ No newline at end of file +) + +tryout_coaches = db.Table('tryout_coaches', + db.Column('tryout_id', db.Integer, db.ForeignKey('tryouts.id', ondelete='CASCADE'), + primary_key=True), + db.Column('coach_id', db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), + primary_key=True), +) diff --git a/app/models/tryout/tryout.py b/app/models/tryout/tryout.py index cfcabde..fed9ec5 100644 --- a/app/models/tryout/tryout.py +++ b/app/models/tryout/tryout.py @@ -1,5 +1,6 @@ """Tryout event for player evaluations and team formation.""" from app.extensions import db +from app.models._associations import tryout_coaches from datetime import datetime @@ -18,12 +19,13 @@ class Tryout(db.Model): created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) target_org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True) manager_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) - coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) + coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) # deprecated, kept for migration created_at = db.Column(db.DateTime, default=datetime.utcnow) creator = db.relationship('User', foreign_keys=[created_by], backref='created_tryouts') manager = db.relationship('User', foreign_keys=[manager_id], backref='managed_tryouts') - coach = db.relationship('User', foreign_keys=[coach_id], backref='coached_tryouts') + coach = db.relationship('User', foreign_keys=[coach_id], backref='_deprecated_coached_tryouts') + coaches = db.relationship('User', secondary=tryout_coaches, backref='coached_tryouts') registrations = db.relationship('TryoutRegistration', backref='tryout', lazy='dynamic') evaluations = db.relationship('Evaluation', backref='tryout', lazy='dynamic') teams = db.relationship('Team', backref='tryout', lazy='dynamic') diff --git a/app/models/user_model/coach.py b/app/models/user_model/coach.py index e836e0f..d05a865 100644 --- a/app/models/user_model/coach.py +++ b/app/models/user_model/coach.py @@ -25,6 +25,10 @@ class Coach(User): ).first() is not None if is_coach_of_target: return True + # Check many-to-many coaches relationship + if any(c.id == self.id for c in tryout.coaches): + return True + # Backward compat: check deprecated coach_id if tryout.coach_id == self.id: return True return False @@ -38,9 +42,13 @@ class Coach(User): def get_visible_tryouts(self): from app.models.tryout.tryout import Tryout + from app.models._associations import tryout_coaches team_ids = [t.id for t in self.coached_org_teams.all()] conditions = [] if team_ids: conditions.append(Tryout.target_org_team_id.in_(team_ids)) + # Check many-to-many coaches + conditions.append(Tryout.coaches.any(id=self.id)) + # Backward compat: check deprecated coach_id conditions.append(Tryout.coach_id == self.id) return Tryout.query.filter(db.or_(*conditions)).order_by(Tryout.date).all() diff --git a/app/models/user_model/manager.py b/app/models/user_model/manager.py index 05944d3..7c71b57 100644 --- a/app/models/user_model/manager.py +++ b/app/models/user_model/manager.py @@ -26,4 +26,7 @@ class Manager(User): def get_visible_tryouts(self): from app.models.tryout.tryout import Tryout - return Tryout.query.filter_by(created_by=self.id).order_by(Tryout.date).all() + from sqlalchemy import or_ + return Tryout.query.filter( + or_(Tryout.created_by == self.id, Tryout.manager_id == self.id) + ).order_by(Tryout.date).all() diff --git a/app/routes/tryouts.py b/app/routes/tryouts.py index 8f0f26a..df11f97 100644 --- a/app/routes/tryouts.py +++ b/app/routes/tryouts.py @@ -56,7 +56,7 @@ def create_tryout(): max_players = request.form.get('max_players') target_org_team_id = request.form.get('target_org_team_id') manager_id = request.form.get('manager_id') - coach_id = request.form.get('coach_id') + coach_ids = request.form.getlist('coach_ids') try: date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() @@ -86,9 +86,15 @@ def create_tryout(): created_by=current_user.id, status='upcoming', target_org_team_id=int(target_org_team_id) if target_org_team_id else None, manager_id=int(manager_id) if manager_id else None, - coach_id=int(coach_id) if coach_id else None, ) db.session.add(tryout) + db.session.flush() + + # Assign coaches via many-to-many + if coach_ids: + coach_users = User.query.filter(User.id.in_([int(c) for c in coach_ids])).all() + tryout.coaches = coach_users + db.session.commit() flash('Tryout created successfully!', 'success') return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id)) @@ -125,7 +131,7 @@ def edit_tryout(tryout_id): max_players = request.form.get('max_players') target_org_team_id = request.form.get('target_org_team_id') manager_id = request.form.get('manager_id') - coach_id = request.form.get('coach_id') + coach_ids = request.form.getlist('coach_ids') try: date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() @@ -156,7 +162,14 @@ def edit_tryout(tryout_id): tryout.max_players = int(max_players) if max_players else None tryout.target_org_team_id = int(target_org_team_id) if target_org_team_id else None tryout.manager_id = int(manager_id) if manager_id else None - tryout.coach_id = int(coach_id) if coach_id else None + + # Update coaches via many-to-many + if coach_ids: + coach_users = User.query.filter(User.id.in_([int(c) for c in coach_ids])).all() + tryout.coaches = coach_users + else: + tryout.coaches = [] + db.session.commit() flash('Tryout updated successfully!', 'success') return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id)) @@ -174,8 +187,8 @@ def view_tryout(tryout_id): can_view = False if isinstance(current_user, Admin): can_view = True - elif isinstance(current_user, Manager) and tryout.created_by == current_user.id: - can_view = True + elif isinstance(current_user, Manager): + can_view = tryout.created_by == current_user.id or tryout.manager_id == current_user.id elif isinstance(current_user, Coach): can_view = current_user.can_manage_this_tryout(tryout) elif isinstance(current_user, Player): diff --git a/app/templates/pages/profile.html b/app/templates/pages/profile.html index 663e719..5e859ef 100644 --- a/app/templates/pages/profile.html +++ b/app/templates/pages/profile.html @@ -280,6 +280,22 @@ for (let h = 8; h <= 22; h++) { const COACH_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; let coachSelectedSlots = {}; +// Drag state for coach grid +let coachDragMode = false; +let coachDragAction = null; + +function applyCoachSlotAction(dayOfWeek, timeStr, element) { + if (!coachSelectedSlots[dayOfWeek]) coachSelectedSlots[dayOfWeek] = []; + const index = coachSelectedSlots[dayOfWeek].indexOf(timeStr); + if (coachDragAction === 'select' && index === -1) { + coachSelectedSlots[dayOfWeek].push(timeStr); + element.classList.add('selected'); + } else if (coachDragAction === 'deselect' && index > -1) { + coachSelectedSlots[dayOfWeek].splice(index, 1); + element.classList.remove('selected'); + } +} + function loadCoachAvailability() { {% for av in existing_availability %} if (!coachSelectedSlots[{{ av.day_of_week }}]) coachSelectedSlots[{{ av.day_of_week }}] = []; @@ -296,25 +312,40 @@ function renderCoachGrid() { COACH_TIME_SLOTS.forEach(slot => { const isSelected = coachSelectedSlots[dayIndex] && coachSelectedSlots[dayIndex].includes(slot.time); const cssClass = isSelected ? 'time-slot selected' : 'time-slot'; - html += '
' + slot.display + '
'; + html += '
' + slot.display + '
'; }); html += ''; }); grid.innerHTML = html; -} -function toggleCoachSlot(dayOfWeek, timeStr, element) { - if (!coachSelectedSlots[dayOfWeek]) coachSelectedSlots[dayOfWeek] = []; - const index = coachSelectedSlots[dayOfWeek].indexOf(timeStr); - if (index === -1) { - coachSelectedSlots[dayOfWeek].push(timeStr); - element.classList.add('selected'); - } else { - coachSelectedSlots[dayOfWeek].splice(index, 1); - element.classList.remove('selected'); - } - clearTimeout(window._coachSaveTimeout); - window._coachSaveTimeout = setTimeout(saveCoachAvailability, 1000); + // Drag event listeners + grid.addEventListener('mousedown', function(e) { + const slot = e.target.closest('.time-slot'); + if (!slot) return; + e.preventDefault(); + coachDragMode = true; + const day = parseInt(slot.dataset.day); + const time = slot.dataset.time; + if (!coachSelectedSlots[day]) coachSelectedSlots[day] = []; + const isSelected = coachSelectedSlots[day].indexOf(time) > -1; + coachDragAction = isSelected ? 'deselect' : 'select'; + applyCoachSlotAction(day, time, slot); + }); + + grid.addEventListener('mousemove', function(e) { + if (!coachDragMode) return; + const slot = e.target.closest('.time-slot'); + if (!slot) return; + applyCoachSlotAction(parseInt(slot.dataset.day), slot.dataset.time, slot); + }); + + document.addEventListener('mouseup', function() { + if (coachDragMode) { + coachDragMode = false; + coachDragAction = null; + saveCoachAvailability(); + } + }); } function saveCoachAvailability() { @@ -400,9 +431,27 @@ var DAYS = [ // Store selected slots: {day: [time, time, ...]} var selectedSlots = {}; +// Drag state +var dispDragMode = false; +var dispDragAction = null; // 'select' or 'deselect' + +function applySlotAction(block, action) { + var day = parseInt(block.dataset.day); + var time = block.dataset.time; + if (!selectedSlots[day]) selectedSlots[day] = []; + var index = selectedSlots[day].indexOf(time); + if (action === 'select' && index === -1) { + selectedSlots[day].push(time); + block.classList.add('selected'); + } else if (action === 'deselect' && index > -1) { + selectedSlots[day].splice(index, 1); + block.classList.remove('selected'); + } +} + function renderDisponibilityGrid() { var grid = document.getElementById('disponibilities-grid'); - grid.innerHTML = '
Click time blocks to select your available hours
'; + grid.innerHTML = '
Click or click-and-drag to select your available hours
'; var container = document.createElement('div'); container.className = 'disponibility-grid'; @@ -425,9 +474,6 @@ function renderDisponibilityGrid() { block.dataset.day = day.value; block.dataset.time = slot.time; block.textContent = slot.display; - block.onclick = function() { - toggleSlot(day.value, slot.time, block); - }; timeBlocks.appendChild(block); }); @@ -435,23 +481,39 @@ function renderDisponibilityGrid() { container.appendChild(dayRow); }); + // Drag event listeners on the container + container.addEventListener('mousedown', function(e) { + var block = e.target.closest('.disponibility-time-block'); + if (!block) return; + e.preventDefault(); + dispDragMode = true; + var day = parseInt(block.dataset.day); + var time = block.dataset.time; + if (!selectedSlots[day]) selectedSlots[day] = []; + var isSelected = selectedSlots[day].indexOf(time) > -1; + dispDragAction = isSelected ? 'deselect' : 'select'; + applySlotAction(block, dispDragAction); + }); + + container.addEventListener('mousemove', function(e) { + if (!dispDragMode) return; + var block = e.target.closest('.disponibility-time-block'); + if (!block) return; + applySlotAction(block, dispDragAction); + }); + + document.addEventListener('mouseup', function() { + if (dispDragMode) { + dispDragMode = false; + dispDragAction = null; + saveDisponibilities(); + } + }); + grid.appendChild(container); loadMyDisponibilities(); } -function toggleSlot(day, time, element) { - if (!selectedSlots[day]) selectedSlots[day] = []; - - var index = selectedSlots[day].indexOf(time); - if (index > -1) { - selectedSlots[day].splice(index, 1); - element.classList.remove('selected'); - } else { - selectedSlots[day].push(time); - element.classList.add('selected'); - } -} - function loadMyDisponibilities() { fetch('{{ url_for("users.get_my_disponibilities") }}') .then(function(response) { return response.json(); }) diff --git a/app/templates/pages/tryout_form.html b/app/templates/pages/tryout_form.html index 5ef8e34..ddfc2ac 100644 --- a/app/templates/pages/tryout_form.html +++ b/app/templates/pages/tryout_form.html @@ -80,15 +80,23 @@
- - + {{ coach.username }} + {% endfor %} - +
+ Select one or more coaches for this tryout.
diff --git a/app/templates/pages/users.html b/app/templates/pages/users.html index 70666b8..c46c098 100644 --- a/app/templates/pages/users.html +++ b/app/templates/pages/users.html @@ -31,7 +31,7 @@
{{ u.username[:2] | upper }}
- {{ u.username }} + {{ u.username }}
{{ u.username }} diff --git a/app/templates/pages/view_tryout.html b/app/templates/pages/view_tryout.html index cf7ffa5..13caa2f 100644 --- a/app/templates/pages/view_tryout.html +++ b/app/templates/pages/view_tryout.html @@ -74,8 +74,16 @@ {{ tryout.manager.username if tryout.manager else 'Not assigned' }}
- Coach - {{ tryout.coach.username if tryout.coach else 'Not assigned' }} + Coaches + + {% if tryout.coaches %} + {{ tryout.coaches | map(attribute='username') | join(', ') }} + {% elif tryout.coach %} + {{ tryout.coach.username }} + {% else %} + Not assigned + {% endif %} +
Registered Players diff --git a/app/templates/pages/view_user.html b/app/templates/pages/view_user.html index 630d3fa..4119b3b 100644 --- a/app/templates/pages/view_user.html +++ b/app/templates/pages/view_user.html @@ -34,6 +34,16 @@ {{ profile_user.discord_username }}
{% endif %} + {% if profile_user.league_os_profile %} +
+ League OS + + + {{ profile_user.league_os_profile }} + + +
+ {% endif %} {% set gamertags = profile_user.gamertags %} diff --git a/migrations/add_tryout_coaches.py b/migrations/add_tryout_coaches.py new file mode 100644 index 0000000..a1e26be --- /dev/null +++ b/migrations/add_tryout_coaches.py @@ -0,0 +1,64 @@ +"""Migration: Create tryout_coaches association table and migrate existing data. + +Run this script to create the many-to-many relationship between tryouts and coaches. +Usage: python migrations/add_tryout_coaches.py +""" + +import sys +import os +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app.app import create_app +from app.extensions import db +from sqlalchemy import text + +app = create_app() + +with app.app_context(): + # Check if table already exists + result = db.session.execute(text( + "SELECT COUNT(*) FROM information_schema.tables " + "WHERE table_name = 'tryout_coaches'" + )) + exists = result.scalar() > 0 + + if exists: + print("Table 'tryout_coaches' already exists. Skipping creation.") + else: + db.session.execute(text(""" + CREATE TABLE tryout_coaches ( + tryout_id INTEGER NOT NULL, + coach_id INTEGER NOT NULL, + PRIMARY KEY (tryout_id, coach_id), + FOREIGN KEY (tryout_id) REFERENCES tryouts (id) ON DELETE CASCADE, + FOREIGN KEY (coach_id) REFERENCES users (id) ON DELETE CASCADE + ) + """)) + db.session.commit() + print("Created 'tryout_coaches' association table.") + + # Migrate existing coach_id data into the new table + result = db.session.execute(text( + "SELECT COUNT(*) FROM tryouts WHERE coach_id IS NOT NULL" + )) + count = result.scalar() + + if count > 0: + # Check how many already migrated + migrated = db.session.execute(text( + "SELECT COUNT(*) FROM tryout_coaches" + )).scalar() + + if migrated == 0: + db.session.execute(text(""" + INSERT INTO tryout_coaches (tryout_id, coach_id) + SELECT id, coach_id FROM tryouts WHERE coach_id IS NOT NULL + """)) + db.session.commit() + print(f"Migrated {count} existing coach assignments to tryout_coaches.") + else: + print(f"Skipping data migration — {migrated} rows already exist in tryout_coaches.") + else: + print("No existing coach assignments to migrate.") + + print("Migration complete.") \ No newline at end of file diff --git a/migrations/add_tryout_end_date.py b/migrations/add_tryout_end_date.py deleted file mode 100644 index 42f7158..0000000 --- a/migrations/add_tryout_end_date.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Migration: Add end_date column to tryouts table. - -Run this script to add the end_date column to the tryouts table. -Usage: python migrations/add_tryout_end_date.py -""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -from app.app import create_app -from app.extensions import db -from sqlalchemy import text - -app = create_app() - -with app.app_context(): - # Check if column already exists - result = db.session.execute(text( - "SELECT COUNT(*) FROM information_schema.columns " - "WHERE table_name = 'tryouts' AND column_name = 'end_date'" - )) - exists = result.scalar() > 0 - - if exists: - print("Column 'end_date' already exists in 'tryouts' table. Skipping.") - else: - db.session.execute(text( - "ALTER TABLE tryouts ADD COLUMN end_date DATE NULL" - )) - # Backfill: set end_date = date for existing tryouts - db.session.execute(text( - "UPDATE tryouts SET end_date = date WHERE end_date IS NULL" - )) - db.session.commit() - print("Successfully added 'end_date' column to 'tryouts' table and backfilled existing rows.") - - print("Migration complete.") \ No newline at end of file