v2.0.0: Modularize project into single-responsibility packages
Replaced monolithic modules/ package with a clean architecture:
- core/ config.py, utils.py
- ai/ models.py (ResilientModel), setup.py (init_models)
- story/ planner.py, writer.py, editor.py, style_persona.py, bible_tracker.py
- marketing/ cover.py, blurb.py, fonts.py, assets.py
- export/ exporter.py
- web/ app.py (Flask factory), db.py, helpers.py, tasks.py, routes/{auth,project,run,persona,admin}.py
- cli/ engine.py (run_generation), wizard.py (BookWizard)
Flask routes split into 5 Blueprints; all templates updated with blueprint-
prefixed url_for() calls. Dockerfile and docker-compose updated to use
web.app entry point and new package paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
106
web/app.py
Normal file
106
web/app.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from sqlalchemy import text
|
||||
from flask import Flask
|
||||
from flask_login import LoginManager
|
||||
from werkzeug.security import generate_password_hash
|
||||
from web.db import db, User, Run
|
||||
from web.tasks import huey
|
||||
from core import config
|
||||
|
||||
# Calculate paths relative to this file (web/app.py -> project root is two levels up)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')
|
||||
|
||||
app = Flask(__name__, template_folder=TEMPLATE_DIR)
|
||||
app.url_map.strict_slashes = False
|
||||
app.config['SECRET_KEY'] = config.FLASK_SECRET
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{os.path.join(config.DATA_DIR, "bookapp.db")}'
|
||||
|
||||
db.init_app(app)
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = 'auth.login'
|
||||
login_manager.init_app(app)
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return db.session.get(User, int(user_id))
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_globals():
|
||||
return dict(app_version=config.VERSION)
|
||||
|
||||
|
||||
# Register Blueprints
|
||||
from web.routes.auth import auth_bp
|
||||
from web.routes.project import project_bp
|
||||
from web.routes.run import run_bp
|
||||
from web.routes.persona import persona_bp
|
||||
from web.routes.admin import admin_bp
|
||||
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(project_bp)
|
||||
app.register_blueprint(run_bp)
|
||||
app.register_blueprint(persona_bp)
|
||||
app.register_blueprint(admin_bp)
|
||||
|
||||
|
||||
# --- SETUP ---
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# Auto-create Admin from Environment Variables (Docker/Portainer Setup)
|
||||
if config.ADMIN_USER and config.ADMIN_PASSWORD:
|
||||
admin = User.query.filter_by(username=config.ADMIN_USER).first()
|
||||
if not admin:
|
||||
print(f"🔐 System: Creating Admin User '{config.ADMIN_USER}' from environment variables.")
|
||||
admin = User(username=config.ADMIN_USER, password=generate_password_hash(config.ADMIN_PASSWORD, method='pbkdf2:sha256'), is_admin=True)
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
else:
|
||||
print(f"🔐 System: Syncing Admin User '{config.ADMIN_USER}' settings from environment.")
|
||||
if not admin.is_admin: admin.is_admin = True
|
||||
admin.password = generate_password_hash(config.ADMIN_PASSWORD, method='pbkdf2:sha256')
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
elif not User.query.filter_by(is_admin=True).first():
|
||||
print("ℹ️ System: No Admin credentials found in environment variables. Admin account not created.")
|
||||
|
||||
# Migration: Add 'progress' column if missing
|
||||
try:
|
||||
with db.engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE run ADD COLUMN progress INTEGER DEFAULT 0"))
|
||||
conn.commit()
|
||||
print("✅ System: Added 'progress' column to Run table.")
|
||||
except: pass
|
||||
|
||||
# Reset stuck runs on startup
|
||||
try:
|
||||
stuck_runs = Run.query.filter_by(status='running').all()
|
||||
if stuck_runs:
|
||||
print(f"⚠️ System: Found {len(stuck_runs)} stuck runs. Resetting to 'failed'.")
|
||||
for r in stuck_runs:
|
||||
r.status = 'failed'
|
||||
r.end_time = datetime.utcnow()
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
print(f"⚠️ System: Failed to clean up stuck runs: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import threading
|
||||
from huey.contrib.mini import MiniHuey
|
||||
|
||||
# Start Huey consumer in background thread
|
||||
def run_huey():
|
||||
from huey.consumer import Consumer
|
||||
consumer = Consumer(huey, workers=1, worker_type='thread', loglevel=20)
|
||||
consumer.run()
|
||||
|
||||
t = threading.Thread(target=run_huey, daemon=True)
|
||||
t.start()
|
||||
|
||||
app.run(host='0.0.0.0', port=7070, debug=False)
|
||||
Reference in New Issue
Block a user