Major refactor: deduplicate routes, sync, JS, and fix bugs

- Extract 8 common route patterns into factory functions in routes/shared.py
  (favourite, upload, replace cover, save defaults, clone, save JSON,
  get missing, clear covers) — removes ~1,100 lines across 9 route files
- Extract generic _sync_category() in sync.py — 7 sync functions become
  one-liner wrappers, removing ~350 lines
- Extract shared detail page JS into static/js/detail-common.js — all 9
  detail templates now call initDetailPage() with minimal config
- Extract layout inline JS into static/js/layout-utils.js (~185 lines)
- Extract library toolbar JS into static/js/library-toolbar.js
- Fix finalize missing-image bug: raise RuntimeError instead of logging
  warning so job is marked failed
- Fix missing scheduler default in _default_checkpoint_data()
- Fix N+1 query in Character.get_available_outfits() with batch IN query
- Convert all print() to logger across services and routes
- Add missing tags display to styles, scenes, detailers, checkpoints detail
- Update delete buttons to use trash.png icon with solid red background
- Update CLAUDE.md to reflect new architecture

Net reduction: ~1,600 lines

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Aodhan Collins
2026-03-21 23:06:58 +00:00
parent ed9a7b4b11
commit 55ff58aba6
42 changed files with 1493 additions and 3105 deletions

122
app.py
View File

@@ -66,38 +66,33 @@ if __name__ == '__main__':
ensure_character_mcp_server_running()
init_queue_worker(app)
with app.app_context():
from sqlalchemy import text
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
db.create_all()
# Migration: Add active_outfit column if it doesn't exist
try:
from sqlalchemy import text
db.session.execute(text('ALTER TABLE character ADD COLUMN active_outfit VARCHAR(100) DEFAULT \'default\''))
db.session.commit()
print("Added active_outfit column to character table")
except Exception as e:
if 'duplicate column name' in str(e).lower() or 'already exists' in str(e).lower():
print("active_outfit column already exists")
else:
print(f"Migration note: {e}")
# --- Helper for safe column additions ---
def _add_column(table, column, col_type):
try:
db.session.execute(text(f'ALTER TABLE {table} ADD COLUMN {column} {col_type}'))
db.session.commit()
logger.info("Added %s.%s column", table, column)
except Exception as e:
db.session.rollback()
if 'duplicate column name' not in str(e).lower() and 'already exists' not in str(e).lower():
logger.debug("Migration note (%s.%s): %s", table, column, e)
# Migration: Add default_fields column to action table if it doesn't exist
try:
from sqlalchemy import text
db.session.execute(text('ALTER TABLE action ADD COLUMN default_fields JSON'))
db.session.commit()
print("Added default_fields column to action table")
except Exception as e:
if 'duplicate column name' in str(e).lower() or 'already exists' in str(e).lower():
print("default_fields column already exists in action table")
else:
print(f"Migration action note: {e}")
# --- All migrations (grouped before syncs) ---
_add_column('character', 'active_outfit', "VARCHAR(100) DEFAULT 'default'")
_add_column('action', 'default_fields', 'JSON')
_add_column('checkpoint', 'data', 'JSON')
_add_column('look', 'character_ids', 'JSON')
# Migration: Add new columns to settings table
columns_to_add = [
('llm_provider', "VARCHAR(50) DEFAULT 'openrouter'"),
('local_base_url', "VARCHAR(255)"),
('local_model', "VARCHAR(100)"),
# Settings columns
for col_name, col_type in [
('llm_provider', "VARCHAR(50) DEFAULT 'openrouter'"),
('local_base_url', 'VARCHAR(255)'),
('local_model', 'VARCHAR(100)'),
('lora_dir_characters', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Looks'"),
('lora_dir_outfits', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Clothing'"),
('lora_dir_actions', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Poses'"),
@@ -105,63 +100,33 @@ if __name__ == '__main__':
('lora_dir_scenes', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Backgrounds'"),
('lora_dir_detailers', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Detailers'"),
('checkpoint_dirs', "VARCHAR(1000) DEFAULT '/ImageModels/Stable-diffusion/Illustrious,/ImageModels/Stable-diffusion/Noob'"),
('default_checkpoint', "VARCHAR(500)"),
('api_key', "VARCHAR(255)"),
]
for col_name, col_type in columns_to_add:
try:
db.session.execute(text(f'ALTER TABLE settings ADD COLUMN {col_name} {col_type}'))
db.session.commit()
print(f"Added {col_name} column to settings table")
except Exception as e:
if 'duplicate column name' in str(e).lower() or 'already exists' in str(e).lower():
pass
else:
print(f"Migration settings note ({col_name}): {e}")
('default_checkpoint', 'VARCHAR(500)'),
('api_key', 'VARCHAR(255)'),
]:
_add_column('settings', col_name, col_type)
# Migration: Add is_favourite and is_nsfw columns to all resource tables
_tag_tables = ['character', 'look', 'outfit', 'action', 'style', 'scene', 'detailer', 'checkpoint']
for _tbl in _tag_tables:
for _col, _type in [('is_favourite', 'BOOLEAN DEFAULT 0'), ('is_nsfw', 'BOOLEAN DEFAULT 0')]:
try:
db.session.execute(text(f'ALTER TABLE {_tbl} ADD COLUMN {_col} {_type}'))
db.session.commit()
print(f"Added {_col} column to {_tbl} table")
except Exception as e:
if 'duplicate column name' in str(e).lower() or 'already exists' in str(e).lower():
pass
else:
print(f"Migration note ({_tbl}.{_col}): {e}")
# is_favourite / is_nsfw on all resource tables
for tbl in ['character', 'look', 'outfit', 'action', 'style', 'scene', 'detailer', 'checkpoint']:
_add_column(tbl, 'is_favourite', 'BOOLEAN DEFAULT 0')
_add_column(tbl, 'is_nsfw', 'BOOLEAN DEFAULT 0')
# Ensure settings exist
if not Settings.query.first():
db.session.add(Settings())
db.session.commit()
print("Created default settings")
logger.info("Created default settings")
# Log the default checkpoint on startup
# Log default checkpoint
settings = Settings.query.first()
if settings and settings.default_checkpoint:
logger.info("=" * 80)
logger.info("DEFAULT CHECKPOINT loaded from database: %s", settings.default_checkpoint)
logger.info("=" * 80)
logger.info("Default checkpoint: %s", settings.default_checkpoint)
else:
logger.info("No default checkpoint set in database")
# --- Sync all categories ---
sync_characters()
sync_outfits()
sync_actions()
# Migration: Add data column to checkpoint table
try:
db.session.execute(text('ALTER TABLE checkpoint ADD COLUMN data JSON'))
db.session.commit()
print("Added data column to checkpoint table")
except Exception as e:
if 'duplicate column name' in str(e).lower() or 'already exists' in str(e).lower():
print("data column already exists in checkpoint table")
else:
print(f"Migration checkpoint note: {e}")
sync_styles()
sync_detailers()
sync_scenes()
@@ -169,20 +134,7 @@ if __name__ == '__main__':
sync_checkpoints()
sync_presets()
# Migration: Convert look.character_id to look.character_ids
try:
from sqlalchemy import text
# First ensure the column exists
db.session.execute(text("ALTER TABLE look ADD COLUMN character_ids JSON"))
db.session.commit()
print("Added character_ids column to look table")
except Exception as e:
if 'duplicate column name' in str(e).lower() or 'already exists' in str(e).lower():
pass # Column already exists
else:
print(f"Migration note (character_ids column): {e}")
# Migrate existing character_id to character_ids list
# --- Post-sync data migration: character_id character_ids ---
try:
looks_with_old_field = Look.query.filter(Look.character_id.isnot(None)).all()
migrated_count = 0
@@ -194,8 +146,8 @@ if __name__ == '__main__':
migrated_count += 1
if migrated_count > 0:
db.session.commit()
print(f"Migrated {migrated_count} looks from character_id to character_ids")
logger.info("Migrated %d looks from character_id to character_ids", migrated_count)
except Exception as e:
print(f"Migration note (character_ids data): {e}")
logger.debug("Migration note (character_ids data): %s", e)
app.run(debug=True, host='0.0.0.0', port=5000)