Security: deep /api/health (DB+MinIO probes, 503 on failure) + /api/live; rate-limited public forms with honeypots; real client IP behind Traefik (--proxy-headers); TipTap URL sanitization on write (nh3); non-root containers + slim .dockerignore; prod MinIO console bound to 127.0.0.1; HSTS/nosniff on TLS routers; JWT 8h; no default credentials in code. Reliability: Alembic migrations (create_all baseline); graceful shutdown (exec uvicorn); hardened backups (checksums, sanity check, freshness gauge, optional rclone off-site) + restore.sh + hourly db-backup sidecar (ops profile); stack.sh ops tooling (deploy/logs/health/backup/restore/monitoring); .gitattributes LF for shell scripts. Orchestration: json-file log rotation, pinned images, per-service memory limits, edge/internal networks, Traefik ping/accesslog/metrics + flood limiter, conditional minio-setup, pgvector db image, one-shot db-init (umami + aramesh_chatbot databases). Chatbot: unified from site-chatbot branch (RAG + pgvector, SSE, ChatWidget). Monitoring: Prometheus/Alertmanager/Loki/Promtail/Grafana + cAdvisor, node/postgres/blackbox exporters, provisioned dashboards and 10 alert rules; alerts delivered to Bale (tapi.bale.ai — Telegram-compatible). Analytics: Umami on shared Postgres, build-time tracker injection, custom events (contact/newsletter/phone/chat/lang) via frontend/src/analytics.ts. Hygiene: CSV export for contact messages and subscribers; upload image optimization (Pillow); CI script + GitHub-compatible workflow; audit.sh; gen-env.sh for strong credentials.
56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
"""ایندکس ترکیبی روی pgvector + BM25 در حافظه.
|
|
|
|
بردارها در pgvector (دیتابیس اختصاصی چتبات) بهصورت افزایشی همگام میشوند —
|
|
فقط سطرهای جدید یا تغییر یافته امبد میشوند. BM25 چون سبک است در هر شروع
|
|
از روی همان سطرها ساخته میشود و نیازی به کش دیسکی ندارد.
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
|
|
from rank_bm25 import BM25Okapi
|
|
|
|
from . import content as content_mod
|
|
from .config import settings
|
|
from .persian import tokenize
|
|
from . import vectorstore
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class SiteIndex:
|
|
"""حالت در حافظهٔ بازیابی — سندها (برای BM25) و وضعیت آماده بودن."""
|
|
|
|
def __init__(self) -> None:
|
|
self.docs: list[dict] = []
|
|
self.bm25: BM25Okapi | None = None
|
|
|
|
@property
|
|
def ready(self) -> bool:
|
|
return self.bm25 is not None and bool(self.docs)
|
|
|
|
|
|
INDEX = SiteIndex()
|
|
|
|
|
|
def sync_index(force_rebuild: bool = False) -> dict:
|
|
"""همگامسازی کامل: محتوا → قطعه → pgvector (افزایشی) → ساخت BM25.
|
|
|
|
خروجی: آمار همگامسازی. خطای اتصال به دیتابیس بالا فرستاده میشود.
|
|
"""
|
|
start = time.time()
|
|
|
|
vectorstore.ensure_schema()
|
|
|
|
docs = content_mod.fetch_site_documents(settings.database_url)
|
|
chunks = content_mod.chunk_documents(docs)
|
|
stats = vectorstore.sync_chunks(chunks, force=force_rebuild)
|
|
|
|
INDEX.docs = vectorstore.fetch_all_chunks()
|
|
corpus_tokens = [tokenize(d["text"]) for d in INDEX.docs]
|
|
INDEX.bm25 = BM25Okapi(corpus_tokens) if corpus_tokens else None
|
|
|
|
log.info("Index ready in %.1fs (%d vectors, %d docs, %s)",
|
|
time.time() - start, len(INDEX.docs), len(chunks), stats)
|
|
return stats
|