Implements the parallel.md workstreams (Agents A-E) toward REFACTOR_GOAL.md. Phase 1 — app factory + blueprints + /api/v1: - app.py -> create_app() factory (no module-level app); entrypoints updated - routes/ (auth, pages, settings, admin, assets) + blueprints/api.py at /api/v1 - config.py / extensions.py / security.py extracted; services/ layer added - endpoint names preserved so template url_for() calls keep resolving (static check: all 27 template url_for endpoints are defined routes) Phase 2 — one pluggable filter system: - filter_pipeline/registry.py: @register_stage / @register_plugin + discover_modules - engine._init_stages() instantiates registered stages (no hardcoded dict); process_batch is AI-aware: only short-circuits to the AI-disabled path when a filterset's stages declare requires_ai, so offline filtersets run with AI off - BaseFilterPlugin gets a consumer (stages/plugins.py); Keyword/Quality re-enabled via filter_config.json plugins config - comment tree modes ported to stages/comment_filter.py + shared rules.py; wired into /api/v1/posts/<uuid> and /api/v1/comments/<uuid> via FilterEngine.filter_comments() (fails open) - offline quality_filter filterset exercises plugins+ranker without AI - legacy filter_lib / comment_lib / html_generation_lib / generate_html / active_html path deleted Phase 3 prep — pluggable fetchers + Postgres models: - Post / Comment SQLAlchemy models added to models.py - migrate_content_to_db.py backfill (idempotent by uuid, batched, --dry-run) - platforms/ fetcher registry (extension point) - live reads/writes still go through PostService (disk JSON); cutover deferred Phase 6 — test harness: - pytest.ini + tests/ (conftest with in-memory SQLite fixture, no Postgres; stubbed polling/filter singletons) - test_app_factory.py (route registration, no module-level app), test_api_contracts.py (posts/post_detail/comments/filters shape with monkeypatched post_service + get_filter_engine), test_filter_pipeline.py + test_plugin_contract.py (Flask-free; validated locally 12/12 incl. drop-in stage/plugin discovered with zero core edits) Other: .gitignore added (__pycache__, data/, secrets); pytest in requirements. Verification: py_compile clean across the project; the Flask-free filter-pipeline and plugin-contract tests pass locally. App-factory / API-contract tests need deps+docker to run; runtime flask routes / Auth0-repeated-create_app also gated on docker. Co-Authored-By: Claude <noreply@anthropic.com>
167 lines
5.0 KiB
Python
Executable File
167 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
BalanceBoard - Startup Script
|
|
Starts the Flask server with PostgreSQL/SQLAlchemy
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import signal
|
|
from pathlib import Path
|
|
|
|
# Configuration
|
|
FLASK_PORT = 5021
|
|
|
|
|
|
def print_color(text, color='blue'):
|
|
"""Print colored text"""
|
|
colors = {
|
|
'red': '\033[0;31m',
|
|
'green': '\033[0;32m',
|
|
'yellow': '\033[1;33m',
|
|
'blue': '\033[0;34m',
|
|
'reset': '\033[0m'
|
|
}
|
|
print(f"{colors.get(color, '')}{text}{colors['reset']}")
|
|
|
|
|
|
def cleanup(signum=None, frame=None):
|
|
"""Cleanup: stop Flask server"""
|
|
print()
|
|
print_color("Shutting down...", 'yellow')
|
|
print_color("Goodbye!", 'green')
|
|
sys.exit(0)
|
|
|
|
|
|
def is_port_in_use(port):
|
|
"""Check if a port is already in use"""
|
|
try:
|
|
import socket
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
return s.connect_ex(('localhost', port)) == 0
|
|
except:
|
|
return False
|
|
|
|
|
|
def check_postgres_connection():
|
|
"""Check if PostgreSQL is available"""
|
|
try:
|
|
import psycopg2
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
|
|
# Get database connection details
|
|
db_host = os.getenv('POSTGRES_HOST', 'localhost')
|
|
db_port = os.getenv('POSTGRES_PORT', '5432')
|
|
db_name = os.getenv('POSTGRES_DB', 'balanceboard')
|
|
db_user = os.getenv('POSTGRES_USER', 'balanceboard')
|
|
db_password = os.getenv('POSTGRES_PASSWORD', 'changeme')
|
|
|
|
# Try to connect
|
|
conn = psycopg2.connect(
|
|
host=db_host,
|
|
port=db_port,
|
|
database=db_name,
|
|
user=db_user,
|
|
password=db_password
|
|
)
|
|
conn.close()
|
|
return True
|
|
|
|
except Exception as e:
|
|
print_color(f"PostgreSQL connection error: {e}", 'red')
|
|
return False
|
|
|
|
|
|
def start_flask():
|
|
"""Start Flask server"""
|
|
print_color("Starting Flask server...", 'blue')
|
|
|
|
# Check virtual environment
|
|
if not Path('venv').exists():
|
|
print_color("Error: Virtual environment not found!", 'red')
|
|
print("Run: python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt")
|
|
return False
|
|
|
|
# Check PostgreSQL connection
|
|
if not check_postgres_connection():
|
|
print_color("Error: Cannot connect to PostgreSQL!", 'red')
|
|
print()
|
|
print("Please ensure PostgreSQL is running and configured:")
|
|
print("1. Install PostgreSQL: sudo apt install postgresql postgresql-contrib")
|
|
print("2. Create database: sudo -u postgres createdb balanceboard")
|
|
print("3. Create user: sudo -u postgres createuser balanceboard")
|
|
print("4. Set password: sudo -u postgres psql -c \"ALTER USER balanceboard PASSWORD 'changeme';\"")
|
|
print("5. Update .env file with your database settings")
|
|
print()
|
|
return False
|
|
|
|
# Create .env if it doesn't exist
|
|
if not Path('.env').exists():
|
|
print_color("Creating .env from .env.example...", 'yellow')
|
|
import secrets
|
|
with open('.env.example', 'r') as f:
|
|
env_content = f.read()
|
|
secret_key = secrets.token_hex(32)
|
|
env_content = env_content.replace('your-secret-key-here-change-this', secret_key)
|
|
with open('.env', 'w') as f:
|
|
f.write(env_content)
|
|
print_color("✓ .env created with random SECRET_KEY", 'green')
|
|
|
|
print()
|
|
print_color("=" * 60, 'green')
|
|
print_color("BalanceBoard is running!", 'green')
|
|
print_color("=" * 60, 'green')
|
|
print()
|
|
print_color(f" Main Feed: http://localhost:{FLASK_PORT}", 'blue')
|
|
print_color(f" Login: http://localhost:{FLASK_PORT}/login", 'blue')
|
|
print_color(f" Sign Up: http://localhost:{FLASK_PORT}/signup", 'blue')
|
|
print_color(f" Admin Panel: http://localhost:{FLASK_PORT}/admin", 'blue')
|
|
print()
|
|
print_color("Database: PostgreSQL with SQLAlchemy", 'blue')
|
|
print_color("Authentication: bcrypt + Flask-Login", 'blue')
|
|
print()
|
|
print_color("Press Ctrl+C to stop the server", 'yellow')
|
|
print()
|
|
|
|
# Import and run Flask app
|
|
try:
|
|
from app import create_app
|
|
print_color("✓ Flask app imported successfully", 'green')
|
|
print_color("✓ Database initialized with SQLAlchemy", 'green')
|
|
print_color("✓ User authentication ready", 'green')
|
|
print()
|
|
|
|
# Run Flask
|
|
app = create_app()
|
|
app.run(host='0.0.0.0', port=FLASK_PORT, debug=True, use_reloader=False)
|
|
|
|
except Exception as e:
|
|
print_color(f"✗ Failed to start Flask app: {e}", 'red')
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main entry point"""
|
|
# Register signal handlers
|
|
signal.signal(signal.SIGINT, cleanup)
|
|
signal.signal(signal.SIGTERM, cleanup)
|
|
|
|
print_color("=" * 60, 'blue')
|
|
print_color("BalanceBoard - PostgreSQL + SQLAlchemy", 'blue')
|
|
print_color("=" * 60, 'blue')
|
|
print()
|
|
|
|
# Start Flask (blocks until Ctrl+C)
|
|
try:
|
|
start_flask()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
finally:
|
|
cleanup()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|