Skip to content

Commit ae691c7

Browse files
Fixes
1 parent e0dc063 commit ae691c7

File tree

2 files changed

+261
-47
lines changed

2 files changed

+261
-47
lines changed

app/main.py

Lines changed: 174 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,15 @@ def index():
4848
@login_required
4949
def dashboard():
5050
"""Main dashboard page"""
51-
if not current_user.has_da_config():
52-
# Redirect to settings if DirectAdmin not configured
51+
try:
52+
if not current_user.has_da_config():
53+
# Redirect to settings if DirectAdmin not configured
54+
return redirect(url_for('settings.index'))
55+
return render_template('dashboard.html')
56+
except Exception as e:
57+
print(f"Error in dashboard route: {e}")
58+
# If there's an error (likely due to missing table), redirect to settings
5359
return redirect(url_for('settings.index'))
54-
return render_template('dashboard.html')
5560

5661
# ===== API Routes =====
5762

@@ -61,18 +66,63 @@ def get_user_domains():
6166
"""Get all domains for the current user"""
6267
try:
6368
domains = current_user.get_domains()
69+
6470
return jsonify({
6571
'success': True,
6672
'domains': domains
6773
})
6874
except Exception as e:
6975
print(f"Error in /api/domains: {str(e)}")
7076
traceback.print_exc()
77+
7178
return jsonify({
7279
'error': 'Failed to fetch domains',
7380
'domains': []
7481
}), 500
7582

83+
@app.route('/api/migration-status', methods=['GET'])
84+
@login_required
85+
def get_migration_status():
86+
"""Get migration status for debugging"""
87+
try:
88+
from app.models import UserDomain
89+
90+
status = {
91+
'user_id': current_user.id,
92+
'username': current_user.username,
93+
'legacy_domain': current_user.da_domain,
94+
'has_da_config': current_user.has_da_config()
95+
}
96+
97+
try:
98+
# Check if UserDomain table exists
99+
domain_count = UserDomain.query.filter_by(user_id=current_user.id).count()
100+
user_domains = [d.domain for d in UserDomain.query.filter_by(user_id=current_user.id).all()]
101+
102+
status.update({
103+
'table_exists': True,
104+
'domain_count': domain_count,
105+
'domains': user_domains,
106+
'migration_needed': current_user.da_domain and domain_count == 0
107+
})
108+
except Exception as e:
109+
status.update({
110+
'table_exists': False,
111+
'table_error': str(e),
112+
'migration_needed': True
113+
})
114+
115+
return jsonify({
116+
'success': True,
117+
'status': status
118+
})
119+
120+
except Exception as e:
121+
return jsonify({
122+
'error': f'Migration status check failed: {str(e)}',
123+
'success': False
124+
}), 500
125+
76126
@app.route('/api/email-accounts', methods=['GET'])
77127
@login_required
78128
def get_email_accounts():
@@ -359,7 +409,127 @@ def handle_exception(error):
359409

360410
# ===== Database Initialization =====
361411

362-
# Ensure DB initialization only once (important with multi-worker if --preload not used)\n if not app.config.get('_DB_INITIALIZED', False):\n with app.app_context():\n print(f\"Initializing database at URI: {app.config['SQLALCHEMY_DATABASE_URI']}\")\n db.create_all()\n\n # Migrate existing users from single domain to multi-domain\n print(\"Checking for users to migrate to multi-domain...\")\n users_to_migrate = User.query.filter(\n User.da_domain.isnot(None),\n ~User.domains.any()\n ).all()\n \n for user in users_to_migrate:\n print(f\"Migrating user {user.username} with domain {user.da_domain}\")\n success, message = user.add_domain(user.da_domain)\n if success:\n print(f\" ✓ Migrated {user.username}: {message}\")\n else:\n print(f\" ✗ Failed to migrate {user.username}: {message}\")\n \n if users_to_migrate:\n try:\n db.session.commit()\n print(f\"Successfully migrated {len(users_to_migrate)} users to multi-domain.\")\n except Exception as e:\n print(f\"Error during migration: {e}\")\n db.session.rollback()\n\n # Create default admin user only if no administrators exist\n admin_count = User.query.filter_by(is_admin=True).count()\n if admin_count == 0:\n # No administrators exist, create default admin user\n admin_user = User(username='admin', is_admin=True)\n admin_user.set_password('changeme') # Default password\n db.session.add(admin_user)\n try:\n db.session.commit()\n print(\"=\" * 50)\n print(\"Default admin user created!\")\n print(\"Username: admin\")\n print(\"Password: changeme\")\n print(\"PLEASE CHANGE THIS PASSWORD IMMEDIATELY!\")\n print(\"=\" * 50)\n except Exception as e:\n print(f\"Error creating admin user: {e}\")\n db.session.rollback()\n else:\n print(f\"Found {admin_count} administrator(s) - skipping default admin creation\")\n\n app.config['_DB_INITIALIZED'] = True
412+
# Ensure DB initialization only once (important with multi-worker if --preload not used)
413+
if not app.config.get('_DB_INITIALIZED', False):
414+
with app.app_context():
415+
print(f"Initializing database at URI: {app.config['SQLALCHEMY_DATABASE_URI']}")
416+
417+
# Import models to ensure they're registered
418+
from app.models import User, UserDomain
419+
420+
# Check if we need to handle schema migration
421+
needs_migration = False
422+
try:
423+
# Test if UserDomain table exists by trying a simple query
424+
db.session.execute(db.text("SELECT COUNT(*) FROM user_domain")).scalar()
425+
print("UserDomain table exists")
426+
except Exception as e:
427+
print(f"UserDomain table doesn't exist: {e}")
428+
needs_migration = True
429+
430+
# Always call create_all() to ensure all tables exist
431+
db.create_all()
432+
print("Database tables created/updated")
433+
434+
# If we needed migration, migrate existing users
435+
if needs_migration:
436+
print("Performing automatic migration to multi-domain...")
437+
438+
# Find all users with da_domain set
439+
try:
440+
users_with_domains = User.query.filter(User.da_domain.isnot(None)).all()
441+
print(f"Found {len(users_with_domains)} users with domains to migrate")
442+
443+
for user in users_with_domains:
444+
print(f"Migrating user {user.username} with domain {user.da_domain}")
445+
try:
446+
# Create UserDomain entry directly to avoid circular dependency
447+
existing_domain = UserDomain.query.filter_by(
448+
user_id=user.id,
449+
domain=user.da_domain
450+
).first()
451+
452+
if not existing_domain:
453+
user_domain = UserDomain(
454+
user_id=user.id,
455+
domain=user.da_domain,
456+
order_index=0
457+
)
458+
db.session.add(user_domain)
459+
print(f" ✓ Created domain entry for {user.username}: {user.da_domain}")
460+
else:
461+
print(f" - Domain already exists for {user.username}: {user.da_domain}")
462+
463+
except Exception as e:
464+
print(f" ✗ Error migrating {user.username}: {e}")
465+
466+
# Commit migration changes
467+
try:
468+
db.session.commit()
469+
print(f"✓ Successfully migrated {len(users_with_domains)} users to multi-domain.")
470+
except Exception as e:
471+
print(f"✗ Error during migration commit: {e}")
472+
db.session.rollback()
473+
474+
except Exception as e:
475+
print(f"Error during user migration: {e}")
476+
db.session.rollback()
477+
else:
478+
# Check for users that have da_domain but no UserDomain entries
479+
try:
480+
users_to_migrate = User.query.filter(
481+
User.da_domain.isnot(None),
482+
~User.domains.any()
483+
).all()
484+
485+
if users_to_migrate:
486+
print(f"Found {len(users_to_migrate)} users needing domain migration...")
487+
488+
for user in users_to_migrate:
489+
print(f"Migrating user {user.username} with domain {user.da_domain}")
490+
try:
491+
user_domain = UserDomain(
492+
user_id=user.id,
493+
domain=user.da_domain,
494+
order_index=0
495+
)
496+
db.session.add(user_domain)
497+
print(f" ✓ Created domain entry for {user.username}: {user.da_domain}")
498+
except Exception as e:
499+
print(f" ✗ Error migrating {user.username}: {e}")
500+
501+
try:
502+
db.session.commit()
503+
print(f"✓ Successfully migrated {len(users_to_migrate)} users to multi-domain.")
504+
except Exception as e:
505+
print(f"✗ Error during migration commit: {e}")
506+
db.session.rollback()
507+
508+
except Exception as e:
509+
print(f"Error checking for migration: {e}")
510+
511+
# Create default admin user only if no administrators exist
512+
admin_count = User.query.filter_by(is_admin=True).count()
513+
if admin_count == 0:
514+
# No administrators exist, create default admin user
515+
admin_user = User(username='admin', is_admin=True)
516+
admin_user.set_password('changeme') # Default password
517+
db.session.add(admin_user)
518+
try:
519+
db.session.commit()
520+
print("=" * 50)
521+
print("Default admin user created!")
522+
print("Username: admin")
523+
print("Password: changeme")
524+
print("PLEASE CHANGE THIS PASSWORD IMMEDIATELY!")
525+
print("=" * 50)
526+
except Exception as e:
527+
print(f"Error creating admin user: {e}")
528+
db.session.rollback()
529+
else:
530+
print(f"Found {admin_count} administrator(s) - skipping default admin creation")
531+
532+
app.config['_DB_INITIALIZED'] = True
363533

364534
# ===== Additional App Configuration =====
365535

app/models.py

Lines changed: 87 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -114,70 +114,114 @@ def get_da_password(self):
114114

115115
def has_da_config(self):
116116
"""Check if user has configured DirectAdmin settings"""
117+
try:
118+
# Check if we have domains configured
119+
domains = self.get_domains()
120+
has_domains = len(domains) > 0
121+
except Exception:
122+
# If domains relationship fails, fall back to legacy da_domain
123+
has_domains = bool(self.da_domain)
124+
117125
return all([
118126
self.da_server,
119127
self.da_username,
120128
self.da_password_encrypted
121-
]) and len(self.get_domains()) > 0
129+
]) and has_domains
122130

123131
# ===== Domain Management =====
124132

125133
def get_domains(self):
126134
"""Get all domains for this user in order"""
127-
return [d.domain for d in self.domains]
135+
try:
136+
# Try to get domains from the new multi-domain table
137+
domains = [d.domain for d in self.domains]
138+
139+
# If no domains in new table but legacy domain exists, include it
140+
if not domains and self.da_domain:
141+
return [self.da_domain]
142+
143+
return domains
144+
except Exception as e:
145+
# If domains relationship fails (table doesn't exist or other error),
146+
# fall back to legacy da_domain if available
147+
if self.da_domain:
148+
return [self.da_domain]
149+
return []
128150

129151
def get_first_domain(self):
130152
"""Get the first domain (default) for this user"""
131-
domains = self.get_domains()
132-
return domains[0] if domains else None
153+
try:
154+
domains = self.get_domains()
155+
if domains:
156+
return domains[0]
157+
# Fall back to legacy da_domain if no domains in new table
158+
return self.da_domain
159+
except Exception:
160+
# If anything fails, return legacy da_domain
161+
return self.da_domain
133162

134163
def add_domain(self, domain):
135164
"""Add a new domain for this user"""
136-
# Check if domain already exists
137-
existing = UserDomain.query.filter_by(user_id=self.id, domain=domain).first()
138-
if existing:
139-
return False, "Domain already exists"
140-
141-
# Get next order index
142-
max_order = db.session.query(db.func.max(UserDomain.order_index)).filter_by(user_id=self.id).scalar()
143-
next_order = (max_order or -1) + 1
144-
145-
# Create new domain
146-
user_domain = UserDomain(
147-
user_id=self.id,
148-
domain=domain,
149-
order_index=next_order
150-
)
151-
152-
db.session.add(user_domain)
153-
return True, "Domain added successfully"
165+
try:
166+
# Check if domain already exists
167+
existing = UserDomain.query.filter_by(user_id=self.id, domain=domain).first()
168+
if existing:
169+
return False, "Domain already exists"
170+
171+
# Get next order index
172+
max_order = db.session.query(db.func.max(UserDomain.order_index)).filter_by(user_id=self.id).scalar()
173+
next_order = (max_order or -1) + 1
174+
175+
# Create new domain
176+
user_domain = UserDomain(
177+
user_id=self.id,
178+
domain=domain,
179+
order_index=next_order
180+
)
181+
182+
db.session.add(user_domain)
183+
return True, "Domain added successfully"
184+
185+
except Exception as e:
186+
print(f"Error adding domain {domain} for user {self.username}: {e}")
187+
return False, f"Failed to add domain: {str(e)}"
154188

155189
def remove_domain(self, domain):
156190
"""Remove a domain for this user"""
157-
user_domain = UserDomain.query.filter_by(user_id=self.id, domain=domain).first()
158-
if not user_domain:
159-
return False, "Domain not found"
160-
161-
db.session.delete(user_domain)
162-
163-
# Reorder remaining domains
164-
remaining_domains = UserDomain.query.filter_by(user_id=self.id).filter(
165-
UserDomain.order_index > user_domain.order_index
166-
).all()
167-
168-
for domain_obj in remaining_domains:
169-
domain_obj.order_index -= 1
170-
171-
return True, "Domain removed successfully"
191+
try:
192+
user_domain = UserDomain.query.filter_by(user_id=self.id, domain=domain).first()
193+
if not user_domain:
194+
return False, "Domain not found"
195+
196+
db.session.delete(user_domain)
197+
198+
# Reorder remaining domains
199+
remaining_domains = UserDomain.query.filter_by(user_id=self.id).filter(
200+
UserDomain.order_index > user_domain.order_index
201+
).all()
202+
203+
for domain_obj in remaining_domains:
204+
domain_obj.order_index -= 1
205+
206+
return True, "Domain removed successfully"
207+
208+
except Exception as e:
209+
print(f"Error removing domain {domain} for user {self.username}: {e}")
210+
return False, f"Failed to remove domain: {str(e)}"
172211

173212
def reorder_domains(self, domain_list):
174213
"""Reorder domains based on provided list"""
175-
for i, domain in enumerate(domain_list):
176-
user_domain = UserDomain.query.filter_by(user_id=self.id, domain=domain).first()
177-
if user_domain:
178-
user_domain.order_index = i
179-
180-
return True, "Domains reordered successfully"
214+
try:
215+
for i, domain in enumerate(domain_list):
216+
user_domain = UserDomain.query.filter_by(user_id=self.id, domain=domain).first()
217+
if user_domain:
218+
user_domain.order_index = i
219+
220+
return True, "Domains reordered successfully"
221+
222+
except Exception as e:
223+
print(f"Error reordering domains for user {self.username}: {e}")
224+
return False, f"Failed to reorder domains: {str(e)}"
181225

182226
# ===== TOTP/2FA Management =====
183227

0 commit comments

Comments
 (0)