Files
TimeTracker/docker/start-fixed.py
Dries Peeters 7791e6ada0 feat: Add comprehensive issue/bug tracking system
Implement a complete issue management system with client portal integration
and internal admin interface for tracking and resolving client-reported issues.

Features:
- New Issue model with full lifecycle management (open, in_progress, resolved, closed, cancelled)
- Priority levels (low, medium, high, urgent) with visual indicators
- Issue linking to projects and tasks
- Create tasks directly from issues
- Client portal integration for issue reporting and viewing
- Internal admin routes for issue management, filtering, and assignment
- Comprehensive templates for both client and admin views
- Status filtering and search functionality
- Issue assignment to internal users
- Automatic timestamp tracking (created, updated, resolved, closed)

Client Portal:
- Clients can report new issues with project association
- View all issues with status filtering
- View individual issue details
- Submit issues with optional submitter name/email

Admin Interface:
- List all issues with advanced filtering (status, priority, client, project, assignee, search)
- View, edit, and delete issues
- Link issues to existing tasks
- Create tasks from issues
- Update issue status, priority, and assignment
- Issue statistics dashboard

Technical:
- Added Issue model with relationships to Client, Project, Task, and User
- New issues blueprint for internal management
- Extended client_portal routes with issue endpoints
- Updated model imports and relationships
- Added navigation links in base templates
- Version bump to 4.6.0
- Code cleanup in docker scripts and schema verification
2025-12-14 07:25:42 +01:00

207 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Improved Python startup script for TimeTracker
This script ensures proper database initialization order and handles errors gracefully
"""
import os
import sys
import time
import subprocess
import traceback
import psycopg2
from urllib.parse import urlparse
def wait_for_database():
"""Wait for database to be ready with proper connection testing"""
# Logging is handled by main()
# Get database URL from environment
db_url = os.getenv('DATABASE_URL', 'postgresql+psycopg2://timetracker:timetracker@db:5432/timetracker')
# If using SQLite, ensure the database directory exists and return immediately
if db_url.startswith('sqlite:'):
try:
# Normalize file path from URL
db_path = None
prefix_four = 'sqlite:////'
prefix_three = 'sqlite:///'
prefix_mem = 'sqlite://'
if db_url.startswith(prefix_four):
db_path = '/' + db_url[len(prefix_four):]
elif db_url.startswith(prefix_three):
# Relative inside container; keep as-is
db_path = db_url[len(prefix_three):]
# If it's a relative path, make sure directory exists
if not db_path.startswith('/'):
db_path = '/' + db_path
elif db_url.startswith(prefix_mem):
# Could be sqlite:///:memory:
if db_url.endswith(':memory:'):
return True
# Fallback: strip scheme
db_path = db_url[len(prefix_mem):]
if db_path:
import os as _os
import sqlite3 as _sqlite3
dir_path = _os.path.dirname(db_path)
if dir_path:
_os.makedirs(dir_path, exist_ok=True)
# Try to open the database to ensure writability
conn = _sqlite3.connect(db_path)
conn.close()
return True
except Exception as e:
print(f"SQLite path/setup check failed: {e}")
return False
# Parse the URL to get connection details (PostgreSQL)
# Handle both postgresql:// and postgresql+psycopg2:// schemes
if db_url.startswith('postgresql'):
if db_url.startswith('postgresql+psycopg2://'):
parsed_url = urlparse(db_url.replace('postgresql+psycopg2://', 'postgresql://'))
else:
parsed_url = urlparse(db_url)
# Extract connection parameters
user = parsed_url.username or 'timetracker'
password = parsed_url.password or 'timetracker'
host = parsed_url.hostname or 'db'
port = parsed_url.port or 5432
# Remove leading slash from path to get database name
database = parsed_url.path.lstrip('/') or 'timetracker'
else:
# Fallback for other formats
host, port, database, user, password = 'db', '5432', 'timetracker', 'timetracker', 'timetracker'
max_attempts = 30
attempt = 0
while attempt < max_attempts:
try:
conn = psycopg2.connect(
host=host,
port=port,
database=database,
user=user,
password=password,
connect_timeout=5
)
conn.close()
return True
except Exception as e:
attempt += 1
if attempt < max_attempts:
time.sleep(2)
return False
def run_script(script_path, description):
"""Run a Python script with proper error handling"""
try:
result = subprocess.run(
[sys.executable, script_path],
check=True,
capture_output=False, # Let the script output directly
text=True
)
return True
except subprocess.CalledProcessError as e:
log(f"{description} failed with exit code {e.returncode}", "ERROR")
return False
except Exception as e:
log(f"Unexpected error running {description}: {e}", "ERROR")
return False
def display_network_info():
"""Display network information for debugging"""
print("=== Network Information ===")
try:
print(f"Hostname: {os.uname().nodename}")
except:
print("Hostname: N/A (Windows)")
try:
import socket
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
print(f"Local IP: {local_ip}")
except:
print("Local IP: N/A")
print(f"Environment: {os.environ.get('FLASK_APP', 'N/A')}")
print(f"Working Directory: {os.getcwd()}")
print("==========================")
def log(message, level="INFO"):
"""Log message with timestamp and level"""
from datetime import datetime
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
prefix = {
"INFO": "",
"SUCCESS": "",
"WARNING": "",
"ERROR": ""
}.get(level, "")
print(f"[{timestamp}] {prefix} {message}")
def main():
log("=" * 60, "INFO")
log("Starting TimeTracker Application", "INFO")
log("=" * 60, "INFO")
# Set environment
os.environ['FLASK_APP'] = 'app'
os.chdir('/app')
# Wait for database
log("Waiting for database connection...", "INFO")
if not wait_for_database():
log("Database is not available, exiting...", "ERROR")
sys.exit(1)
# Run enhanced database initialization and migration
log("Running database initialization...", "INFO")
if not run_script('/app/docker/init-database-enhanced.py', 'Database initialization'):
log("Database initialization failed, exiting...", "ERROR")
sys.exit(1)
log("Database initialization completed", "SUCCESS")
# Ensure default settings and admin user exist (idempotent)
# Note: Database initialization is already handled by the migration system above
# The flask init_db command is optional and may not be available in all environments
try:
result = subprocess.run(
['flask', 'init_db'],
check=False, # Don't fail if command doesn't exist
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0 and "No such command" not in (result.stderr or ""):
log("flask init_db returned non-zero exit code (continuing)", "WARNING")
except (FileNotFoundError, subprocess.TimeoutExpired, Exception):
# All errors are non-fatal - database is already initialized
pass
log("=" * 60, "INFO")
log("Starting application server", "INFO")
log("=" * 60, "INFO")
# Start gunicorn with access logs
os.execv('/usr/local/bin/gunicorn', [
'gunicorn',
'--bind', '0.0.0.0:8080',
'--worker-class', 'eventlet',
'--workers', '1',
'--timeout', '120',
'--access-logfile', '-',
'--error-logfile', '-',
'--log-level', 'info',
'app:create_app()'
])
if __name__ == '__main__':
main()