refactor(bot)!: rename 'ai procurement' -> 'bot/' so the compose build context is committed

The bot service in docker-compose.yml builds from ./bot, but the agent code lived in 'ai procurement/' and ./bot was only a Windows junction on reza.g's machine (and was gitignored) — exactly as the team reported: 'bot/ was never committed — prod-v7's bot is un-buildable from the repo.'

- junction removed; 'ai procurement/' renamed to 'bot/' via git mv (all 41 files keep their history as pure renames)
- 'bot/' dropped from .gitignore (voice/ stays ignored — runtime voice notes; bot/.env stays ignored by bot/.gitignore)
- compose needs no change: context: ./bot and ./bot/app now resolve to committed files
- bot/INTEGRATION-REPORT.md added: full story (problem, root cause, git state, security note on the committed BALE_BOT_TOKEN / AI_API_KEY compose defaults, and usage instructions for the team)

Verified: 41 renames staged with zero secret/cache files; voice/ and bot/.env still ignored.
This commit is contained in:
2026-10-02 12:52:25 +03:30
parent 07643dbdf5
commit 638076dbaa
43 changed files with 150 additions and 1 deletions
+53
View File
@@ -0,0 +1,53 @@
"""Persistent storage for draft attachments (proforma files)."""
from datetime import datetime, timezone
import logging
import os
from pathlib import Path
import re
from uuid import UUID, uuid4
logger = logging.getLogger("bot.storage")
def sanitize_filename(filename: str) -> str:
"""Sanitize filename preserving Persian/Arabic/English letters, numbers, and extensions."""
raw = (filename or "proforma.pdf").strip()
name = Path(raw).name
# Remove control characters and path traversal
clean = re.sub(r'[\/\\:\*\?"<>\|\r\n\t]', "_", name)
clean = clean.strip(". ")
return clean or f"proforma_{uuid4().hex[:8]}.pdf"
async def save_proforma_file(
draft_id: str | UUID,
file_name: str,
content: bytes,
mime_type: str | None = None,
storage_dir: str = "data/attachments/proformas",
) -> dict:
"""Save proforma bytes permanently to disk.
Returns dict metadata for persisting in draft payload.
"""
clean_name = sanitize_filename(file_name)
target_dir = Path(storage_dir) / str(draft_id)
target_dir.mkdir(parents=True, exist_ok=True)
unique_prefix = uuid4().hex[:8]
saved_filename = f"{unique_prefix}_{clean_name}"
target_path = target_dir / saved_filename
# Write bytes to disk
target_path.write_bytes(content)
logger.info("Persisted proforma (%d bytes) to %s", len(content), target_path)
return {
"file_name": clean_name,
"saved_filename": saved_filename,
"saved_path": str(target_path.resolve()),
"relative_path": str(target_path.as_posix()),
"size_bytes": len(content),
"mime_type": mime_type or "application/octet-stream",
"saved_at": datetime.now(timezone.utc).isoformat(),
}