v1.7.1: fix all P1-P4, C1-C6, H1-H6, M1-M7, L1-L3 from error.md — async migrations, CSS variables, SQL validation, event loop, prompt injection, VCF parsing, race conditions, unused imports, gitignore
This commit is contained in:
+37
-31
@@ -1,15 +1,13 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import json
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from sqlalchemy import select, text
|
||||
|
||||
from app.config import settings
|
||||
from app.database import engine, async_session
|
||||
from app.models import Base
|
||||
from app.models import Base, BotConversation
|
||||
from app.max_api import max_api
|
||||
from app.settings_cache import settings_cache
|
||||
from app.yandex_gpt import yandex_gpt
|
||||
@@ -72,32 +70,48 @@ async def _apply_migrations():
|
||||
logger.warning(f"Migration note: {e}")
|
||||
|
||||
# BotUser new columns
|
||||
_add_column_if_not_exists(conn, "bot_users", "patronymic", "VARCHAR(255)")
|
||||
_add_column_if_not_exists(conn, "bot_users", "email", "VARCHAR(255)")
|
||||
_add_column_if_not_exists(conn, "bot_users", "organization", "VARCHAR(255)")
|
||||
_add_column_if_not_exists(conn, "bot_users", "address", "TEXT")
|
||||
_add_column_if_not_exists(conn, "bot_users", "vcf_raw", "TEXT")
|
||||
_add_column_if_not_exists(conn, "bot_users", "contact_hash", "VARCHAR(255)")
|
||||
_add_column_if_not_exists(conn, "bot_users", "phone_verified", "BOOLEAN DEFAULT FALSE")
|
||||
_add_column_if_not_exists(conn, "bot_users", "email_verified", "BOOLEAN DEFAULT FALSE")
|
||||
_add_column_if_not_exists(conn, "bot_users", "last_interaction", "TIMESTAMP")
|
||||
_add_column_if_not_exists(conn, "bot_users", "total_conversations", "INTEGER DEFAULT 0")
|
||||
_add_column_if_not_exists(conn, "bot_users", "total_tickets", "INTEGER DEFAULT 0")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "patronymic", "VARCHAR(255)")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "email", "VARCHAR(255)")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "organization", "VARCHAR(255)")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "address", "TEXT")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "vcf_raw", "TEXT")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "contact_hash", "VARCHAR(255)")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "phone_verified", "BOOLEAN DEFAULT FALSE")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "email_verified", "BOOLEAN DEFAULT FALSE")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "last_interaction", "TIMESTAMP")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "total_conversations", "INTEGER DEFAULT 0")
|
||||
await _add_column_if_not_exists(conn, "bot_users", "total_tickets", "INTEGER DEFAULT 0")
|
||||
|
||||
|
||||
def _add_column_if_not_exists(conn, table: str, column: str, col_type: str):
|
||||
async def _add_column_if_not_exists(conn, table: str, column: str, col_type: str):
|
||||
from sqlalchemy import text as sa_text
|
||||
_validate_sql_name(table)
|
||||
_validate_sql_name(column)
|
||||
_validate_sql_type(col_type)
|
||||
try:
|
||||
conn.execute(sa_text(f"""
|
||||
await conn.execute(sa_text(f"""
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name='{table}' AND column_name='{column}'
|
||||
) THEN
|
||||
ALTER TABLE {table} ADD COLUMN {column} {col_type};
|
||||
ALTER TABLE "{table}" ADD COLUMN "{column}" {col_type};
|
||||
END IF;
|
||||
END $$;
|
||||
"""))
|
||||
|
||||
|
||||
def _validate_sql_name(name: str) -> None:
|
||||
import re
|
||||
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_ ]*$', name):
|
||||
raise ValueError(f"Invalid SQL identifier: {name}")
|
||||
|
||||
|
||||
def _validate_sql_type(col_type: str) -> None:
|
||||
import re
|
||||
allowed = r'^(VARCHAR\(\d+\)|TEXT|INTEGER|BOOLEAN( DEFAULT (TRUE|FALSE))?|TIMESTAMP)$'
|
||||
if not re.match(allowed, col_type.strip()):
|
||||
raise ValueError(f"Invalid SQL type: {col_type}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Migration add column {table}.{column}: {e}")
|
||||
|
||||
@@ -457,8 +471,8 @@ async def api_update_ticket(ticket_id: int, request: Request):
|
||||
if not ticket:
|
||||
return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)
|
||||
|
||||
old_status = ticket.status
|
||||
if status:
|
||||
if status and status != ticket.status:
|
||||
old_status = ticket.status
|
||||
ticket.status = status
|
||||
db.add(BotTicketStatus(
|
||||
ticket_id=ticket_id,
|
||||
@@ -473,17 +487,9 @@ async def api_update_ticket(ticket_id: int, request: Request):
|
||||
|
||||
def _extract_phone_from_vcf(vcf_info: str) -> str:
|
||||
import re
|
||||
match = re.search(r"TEL[^:]*:(\+?\d+)", vcf_info)
|
||||
match = re.search(r"TEL[^:]*:(\+?\d[\d\s\-()]*)", vcf_info)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return ""
|
||||
|
||||
|
||||
def _extract_name_from_vcf(vcf_info: str) -> str:
|
||||
import re
|
||||
match = re.search(r"FN:(.+)", vcf_info)
|
||||
if match:
|
||||
return match.group(1).strip()
|
||||
return re.sub(r"[\s\-()]", "", match.group(1).strip())
|
||||
return ""
|
||||
|
||||
|
||||
@@ -513,7 +519,7 @@ def _parse_vcf(vcf_info: str) -> dict:
|
||||
result["first_name"] = parts[0]
|
||||
|
||||
# N: structured name (last;first;middle;prefix;suffix)
|
||||
n_match = re.search(r"N(?:;[^:]*)?:([^;\r\n]+)", vcf_info)
|
||||
n_match = re.search(r"N(?:;[^:]*)?:([^\r\n]+)", vcf_info)
|
||||
if n_match:
|
||||
n_parts = n_match.group(1).split(";")
|
||||
if len(n_parts) >= 1 and n_parts[0]:
|
||||
@@ -535,7 +541,7 @@ def _parse_vcf(vcf_info: str) -> dict:
|
||||
result["email"] = email_match.group(1).strip()
|
||||
|
||||
# ORG
|
||||
org_match = re.search(r"ORG[^:]*:([^\r\n;]+)", vcf_info)
|
||||
org_match = re.search(r"ORG[^:]*:([^\r\n]+)", vcf_info)
|
||||
if org_match:
|
||||
result["organization"] = org_match.group(1).strip()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user