Files
ERP/bot/app/storage.py
whqreza 638076dbaa 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.
2026-10-02 12:52:25 +03:30

54 lines
1.7 KiB
Python

"""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(),
}