commit 1eeea9f83ad9230a5c1f7a75662770eaab0df837 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 21:15:41 2025 +0200 Disable resuming of old time entries. commit 3e3ec2f01cb7943622b819a19179388078ae1315 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 20:59:19 2025 +0200 Refactor db migrations. commit 15a51a569da36c6b7c9e01ab17b6fdbdee6ad994 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 19:58:04 2025 +0200 Apply new style for Time Tracking view. commit 77e5278b303e060d2b03853b06277f8aa567ae68 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 18:06:04 2025 +0200 Allow direct registrations as a Company. commit 188a8772757cbef374243d3a5f29e4440ddecabe Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 18:04:45 2025 +0200 Add email invitation feature. commit d9ebaa02aa01b518960a20dccdd5a327d82f30c6 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 17:12:32 2025 +0200 Apply common style for Company, User, Team management pages. commit 81149caf4d8fc6317e2ab1b4f022b32fc5aa6d22 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 16:44:32 2025 +0200 Move export functions to own module. commit 1a26e19338e73f8849c671471dd15cc3c1b1fe82 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 15:51:15 2025 +0200 Split up models.py. commit 61f1ccd10f721b0ff4dc1eccf30c7a1ee13f204d Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 12:05:28 2025 +0200 Move utility function into own modules. commit 84b341ed35e2c5387819a8b9f9d41eca900ae79f Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 11:44:24 2025 +0200 Refactor auth functions use. commit 923e311e3da5b26d85845c2832b73b7b17c48adb Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 11:35:52 2025 +0200 Refactor route nameing and fix bugs along the way. commit f0a5c4419c340e62a2615c60b2a9de28204d2995 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 10:34:33 2025 +0200 Fix URL endpoints in announcement template. commit b74d74542a1c8dc350749e4788a9464d067a88b5 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 09:25:53 2025 +0200 Move announcements to own module. commit 9563a28021ac46c82c04fe4649b394dbf96f92c7 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 09:16:30 2025 +0200 Combine Company view and edit templates. commit 6687c373e681d54e4deab6b2582fed5cea9aadf6 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 08:17:42 2025 +0200 Move Users, Company and System Administration to own modules. commit 8b7894a2e3eb84bb059f546648b6b9536fea724e Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 07:40:57 2025 +0200 Move Teams and Projects to own modules. commit d11bf059d99839ecf1f5d7020b8c8c8a2454c00b Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 07:09:33 2025 +0200 Move Tasks and Sprints to own modules.
166 lines
4.9 KiB
Python
Executable File
166 lines
4.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Run code migrations during startup - updates code to match model changes
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
from pathlib import Path
|
|
import hashlib
|
|
import json
|
|
from datetime import datetime
|
|
|
|
MIGRATION_STATE_FILE = '/data/code_migrations_state.json'
|
|
|
|
def get_migration_hash(script_path):
|
|
"""Get hash of migration script to detect changes"""
|
|
with open(script_path, 'rb') as f:
|
|
return hashlib.md5(f.read()).hexdigest()
|
|
|
|
def load_migration_state():
|
|
"""Load state of previously run migrations"""
|
|
if os.path.exists(MIGRATION_STATE_FILE):
|
|
try:
|
|
with open(MIGRATION_STATE_FILE, 'r') as f:
|
|
return json.load(f)
|
|
except:
|
|
return {}
|
|
return {}
|
|
|
|
def save_migration_state(state):
|
|
"""Save migration state"""
|
|
os.makedirs(os.path.dirname(MIGRATION_STATE_FILE), exist_ok=True)
|
|
with open(MIGRATION_STATE_FILE, 'w') as f:
|
|
json.dump(state, f, indent=2)
|
|
|
|
def should_run_migration(script_path, state):
|
|
"""Check if migration should run based on state"""
|
|
script_name = os.path.basename(script_path)
|
|
current_hash = get_migration_hash(script_path)
|
|
|
|
if script_name not in state:
|
|
return True
|
|
|
|
# Re-run if script has changed
|
|
if state[script_name].get('hash') != current_hash:
|
|
return True
|
|
|
|
# Skip if already run successfully
|
|
if state[script_name].get('status') == 'success':
|
|
return False
|
|
|
|
return True
|
|
|
|
def run_migration(script_path, state):
|
|
"""Run a single migration script"""
|
|
script_name = os.path.basename(script_path)
|
|
print(f"\n{'='*60}")
|
|
print(f"Running code migration: {script_name}")
|
|
print('='*60)
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[sys.executable, script_path],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
timeout=300 # 5 minute timeout
|
|
)
|
|
|
|
print(result.stdout)
|
|
if result.stderr:
|
|
print("Warnings:", result.stderr)
|
|
|
|
# Update state
|
|
state[script_name] = {
|
|
'hash': get_migration_hash(script_path),
|
|
'status': 'success',
|
|
'last_run': str(datetime.now()),
|
|
'output': result.stdout[-1000:] if result.stdout else '' # Last 1000 chars
|
|
}
|
|
save_migration_state(state)
|
|
return True
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"❌ Error running {script_name}:")
|
|
print(e.stdout)
|
|
print(e.stderr)
|
|
|
|
# Update state with failure
|
|
state[script_name] = {
|
|
'hash': get_migration_hash(script_path),
|
|
'status': 'failed',
|
|
'last_run': str(datetime.now()),
|
|
'error': str(e)
|
|
}
|
|
save_migration_state(state)
|
|
return False
|
|
except subprocess.TimeoutExpired:
|
|
print(f"❌ Migration {script_name} timed out!")
|
|
state[script_name] = {
|
|
'hash': get_migration_hash(script_path),
|
|
'status': 'timeout',
|
|
'last_run': str(datetime.now())
|
|
}
|
|
save_migration_state(state)
|
|
return False
|
|
|
|
def main():
|
|
"""Run all code migrations that need to be run"""
|
|
|
|
print("🔄 Checking for code migrations...")
|
|
|
|
# Get migration state
|
|
state = load_migration_state()
|
|
|
|
# Get all migration scripts
|
|
migrations_dir = Path(__file__).parent
|
|
migration_scripts = sorted([
|
|
str(p) for p in migrations_dir.glob('*.py')
|
|
if p.name.startswith(('11_', '12_', '13_', '14_', '15_'))
|
|
and 'template' not in p.name.lower()
|
|
])
|
|
|
|
if not migration_scripts:
|
|
print("No code migration scripts found.")
|
|
return 0
|
|
|
|
# Check which migrations need to run
|
|
to_run = []
|
|
for script in migration_scripts:
|
|
if should_run_migration(script, state):
|
|
to_run.append(script)
|
|
|
|
if not to_run:
|
|
print("✅ All code migrations are up to date.")
|
|
return 0
|
|
|
|
print(f"\n📋 Found {len(to_run)} code migrations to run:")
|
|
for script in to_run:
|
|
print(f" - {Path(script).name}")
|
|
|
|
# Run migrations
|
|
failed = []
|
|
for script in to_run:
|
|
if not run_migration(script, state):
|
|
failed.append(script)
|
|
# Continue with other migrations even if one fails
|
|
print(f"\n⚠️ Migration {Path(script).name} failed, continuing with others...")
|
|
|
|
# Summary
|
|
print("\n" + "="*60)
|
|
if failed:
|
|
print(f"⚠️ {len(failed)} code migrations failed:")
|
|
for script in failed:
|
|
print(f" - {Path(script).name}")
|
|
print("\nThe application may not work correctly.")
|
|
print("Check the logs and fix the issues.")
|
|
# Don't exit with error - let the app start anyway
|
|
return 0
|
|
else:
|
|
print("✅ All code migrations completed successfully!")
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main()) |