From 7e38864e6bab9a35b074318e37e2b83c8a536822 Mon Sep 17 00:00:00 2001 From: Christopher Odoom Date: Fri, 24 Jul 2026 19:24:18 -0400 Subject: [PATCH] Fix AI integration readiness --- routes/admin_routes.py | 94 ++++++++++++++++++++++++++------ routes/chatbot_routes.py | 4 +- templates/admin/ai_settings.html | 63 +++++++++++++++++++-- tests/test_integrations.py | 65 +++++++++++++++++++++- utils/ai_usage.py | 21 +++++++ 5 files changed, 222 insertions(+), 25 deletions(-) diff --git a/routes/admin_routes.py b/routes/admin_routes.py index 194dc5a..e3f57f8 100644 --- a/routes/admin_routes.py +++ b/routes/admin_routes.py @@ -1,14 +1,27 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify from flask_login import login_required, current_user -from models import db, User, ExpertApplication, Analysis, Consultation +from models import ( + db, + User, + ExpertApplication, + Analysis, + Consultation, +) from functools import wraps from datetime import datetime, timedelta, timezone from utils.email_service import send_expert_approved_email, send_expert_rejected_email from utils.ai_service import ( call_openai_api, is_ai_enabled, OpenAIServiceError, get_openai_config ) +from utils.ai_usage import ( + ai_usage_storage_ready, + initialize_ai_usage_storage, +) +from sqlalchemy.exc import SQLAlchemyError +import logging import re admin = Blueprint('admin', __name__, url_prefix='/admin') +logger = logging.getLogger(__name__) # Custom decorator for admin access def admin_required(f): @wraps(f) @@ -239,28 +252,39 @@ def ai_settings(): """Display non-secret AI integration status.""" current_api_key, current_model = get_openai_config() current_enabled = is_ai_enabled() + storage_ready = ai_usage_storage_ready() current_settings = { 'api_key_configured': bool(current_api_key), 'model': current_model, 'enabled': current_enabled, + 'storage_ready': storage_ready, } - api_status = { - 'status': ( - 'configured' - if current_enabled and current_api_key - else 'disabled' - if not current_enabled - else 'no_key' - ), - 'last_error': ( - None - if current_enabled and current_api_key - else 'AI enhancement is disabled.' - if not current_enabled - else 'OPENAI_API_KEY is missing.' - ), - 'credit_warning': False, - } + if not current_enabled: + api_status = { + 'status': 'disabled', + 'label': 'Disabled', + 'last_error': 'AI enhancement is disabled.', + } + elif not current_api_key: + api_status = { + 'status': 'no_key', + 'label': 'Missing API token', + 'last_error': 'OPENAI_API_KEY is missing.', + } + elif not storage_ready: + api_status = { + 'status': 'needs_setup', + 'label': 'Needs initialization', + 'last_error': ( + 'The AI usage database table has not been initialized.' + ), + } + else: + api_status = { + 'status': 'ready', + 'label': 'Ready', + 'last_error': None, + } return render_template( 'admin/ai_settings.html', current_settings=current_settings, @@ -268,6 +292,40 @@ def ai_settings(): ) +@admin.route('/initialize-ai-storage', methods=['POST']) +@login_required +@admin_required +def initialize_ai_storage(): + """Initialize durable AI usage storage for serverless deployments.""" + data = request.get_json(silent=True) + if not isinstance(data, dict) or data.get('confirm') is not True: + return jsonify({ + 'success': False, + 'error': 'Explicit initialization confirmation is required.', + }), 400 + + try: + initialize_ai_usage_storage() + except SQLAlchemyError: + logger.exception( + "AI storage initialization failed for admin user %s.", + current_user.id, + ) + return jsonify({ + 'success': False, + 'error': ( + 'The AI usage table could not be created. Verify that ' + 'DATABASE_URL is correct and permits schema changes.' + ), + }), 503 + + return jsonify({ + 'success': True, + 'message': 'AI usage storage is initialized.', + 'storage_ready': ai_usage_storage_ready(), + }) + + @admin.route('/test-ai-integration', methods=['POST']) @login_required @admin_required diff --git a/routes/chatbot_routes.py b/routes/chatbot_routes.py index d25b309..99c6a66 100644 --- a/routes/chatbot_routes.py +++ b/routes/chatbot_routes.py @@ -64,8 +64,8 @@ def ask_question(): success=False, message="AI usage tracking is unavailable.", response=( - "The AI assistant is not initialized yet. " - "Please contact the administrator." + "The AI assistant's usage storage is not ready. " + "An administrator can initialize it from AI Integration." ), ), 503 diff --git a/templates/admin/ai_settings.html b/templates/admin/ai_settings.html index 253df36..ef2a887 100644 --- a/templates/admin/ai_settings.html +++ b/templates/admin/ai_settings.html @@ -7,7 +7,7 @@

AI Integration

-

OpenAI Responses API configuration and connection test.

+

OpenAI configuration, usage-storage readiness, and live provider test.

@@ -43,10 +43,19 @@

Server-side configuration

Model
{{ current_settings.model }}
-
Status
+
Usage storage
- - {{ api_status.status }} + {% if current_settings.storage_ready %} + Ready + {% else %} + Not initialized + {% endif %} +
+ +
Overall readiness
+
+ + {{ api_status.label }} {% if api_status.last_error %} {{ api_status.last_error }} @@ -62,6 +71,25 @@

Server-side configuration

+ {% if not current_settings.storage_ready %} +
+
+

Initialize AI usage storage

+
+
+

+ The assistant requires a durable PostgreSQL table to enforce + per-user usage limits. This action creates only the missing + ai_usage_events table and its indexes. +

+ +
+
+
+ {% endif %} +

Connection test

@@ -80,10 +108,37 @@

Connection test