diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d465d0b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +.git +.gitignore +.venv +venv +__pycache__ +*.pyc +*.pyo +.pytest_cache +.mypy_cache +.dmypy.json +.ruff_cache +.coverage +htmlcov + +node_modules +frontend/node_modules +frontend/dist +frontend/.vite + +chroma_db +backend/chroma_db +*.log + +docs +evals +.claude +.github +*.md +!README.md + +.env +.env.* +!.env.example +.DS_Store diff --git a/Dockerfile.backend b/Dockerfile.backend index f86976e..9e21eac 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -14,4 +14,5 @@ COPY --from=builder /build/packages /usr/local/lib/python3.12/site-packages COPY backend/ . EXPOSE 8000 ENV PYTHONPATH=/app +ENV PATH="/usr/local/lib/python3.12/site-packages/bin:${PATH}" CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md index f018723..4b5a719 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,13 @@ cd procureai ```bash cp backend/.env.example backend/.env # then fill in your API keys -python backend/data/seed.py # seed MongoDB with sample data +``` + +Sample data (suppliers, bids) is seeded automatically into MongoDB on first startup. +To re-seed manually: + +```bash +cd backend && PYTHONPATH=. python data/seed.py ``` Start the backend server: diff --git a/backend/.env.example b/backend/.env.example index 33eeec3..375e7b8 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -19,6 +19,6 @@ SENTRY_ENVIRONMENT=development APP_VERSION=procureai@dev # LangSmith -LANGCHAIN_TRACING_V2=true -LANGCHAIN_API_KEY=your_langsmith_api_key_here +LANGCHAIN_TRACING_V2=false +LANGCHAIN_API_KEY= LANGCHAIN_PROJECT=procureai diff --git a/backend/data/seed.py b/backend/data/seed.py index f292b72..1b62a9a 100644 --- a/backend/data/seed.py +++ b/backend/data/seed.py @@ -223,24 +223,32 @@ ] -async def seed_database(): - load_dotenv() - mongodb_url = os.getenv("MONGODB_URI", "mongodb://localhost:27017") - client = AsyncIOMotorClient(mongodb_url) - db = client.procureai +async def seed_if_empty(db) -> bool: + if await db.suppliers.count_documents({}, limit=1): + return False - # Insert suppliers supplier_ids = {} for i, supplier in enumerate(mock_suppliers): - result = await db.suppliers.insert_one(supplier.dict(by_alias=True)) + result = await db.suppliers.insert_one(supplier.model_dump(by_alias=True)) supplier_ids[str(i + 1)] = str(result.inserted_id) - # Update bids with actual supplier_ids for bid in mock_bids: bid.supplier_id = supplier_ids[bid.supplier_id] - await db.bids.insert_one(bid.dict(by_alias=True)) + await db.bids.insert_one(bid.model_dump(by_alias=True)) + + return True + + +async def seed_database(): + load_dotenv() + mongodb_url = os.getenv("MONGODB_URI", "mongodb://localhost:27017") + client = AsyncIOMotorClient(mongodb_url) + db = client.procureai - print("Seed data inserted successfully!") + if await seed_if_empty(db): + print("Seed data inserted successfully!") + else: + print("Suppliers collection is not empty, skipping seed.") if __name__ == "__main__": diff --git a/backend/main.py b/backend/main.py index 1dfcf47..21018a1 100644 --- a/backend/main.py +++ b/backend/main.py @@ -55,6 +55,10 @@ async def lifespan(app: FastAPI): await db.audit_logs.create_index([("user_id", 1), ("timestamp", -1)]) # await db.audit_logs.create_index("timestamp", expireAfterSeconds=90*24*60*60) + from data.seed import seed_if_empty + + await seed_if_empty(db) + if is_vectorstore_empty(): pdf_dir = Path(settings.CHROMA_PATH).parent / "data" / "pdfs" if pdf_dir.exists(): @@ -65,23 +69,29 @@ async def lifespan(app: FastAPI): except Exception as exc: log.error("pdf_ingest_failed", file=pdf_path.name, error=str(exc)) # --- Superuser seed --- - from crud.user import create_user, get_user_by_email - from schemas.user import UserCreate - - existing = await get_user_by_email(db, settings.FIRST_SUPERUSER_EMAIL) - if not existing: - superuser_in = UserCreate( - email=settings.FIRST_SUPERUSER_EMAIL, - password=settings.FIRST_SUPERUSER_PASSWORD, - full_name="Admin", - ) - user_doc = await create_user(db, superuser_in) - if user_doc: - await db.users.update_one( - {"email": settings.FIRST_SUPERUSER_EMAIL}, - {"$set": {"is_superuser": True, "role": "admin"}}, - ) - log.info("superuser_created", email=settings.FIRST_SUPERUSER_EMAIL) + from datetime import datetime, timezone + + from auth.security import get_password_hash + from bson import ObjectId + + upsert_result = await db.users.update_one( + {"email": settings.FIRST_SUPERUSER_EMAIL}, + { + "$setOnInsert": { + "_id": str(ObjectId()), + "email": settings.FIRST_SUPERUSER_EMAIL, + "hashed_password": get_password_hash(settings.FIRST_SUPERUSER_PASSWORD), + "full_name": "Admin", + "is_active": True, + "is_superuser": True, + "role": "admin", + "created_at": datetime.now(timezone.utc), + } + }, + upsert=True, + ) + if upsert_result.upserted_id: + log.info("superuser_created", email=settings.FIRST_SUPERUSER_EMAIL) else: log.info("superuser_exists", email=settings.FIRST_SUPERUSER_EMAIL) yield diff --git a/docker-compose.yml b/docker-compose.yml index 437a8cb..46392c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.9" - services: backend: build: @@ -9,9 +7,9 @@ services: INSTALL_RERANK: ${INSTALL_RERANK:-false} ports: - "8000:8000" + env_file: + - backend/.env environment: - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} - OPENAI_API_KEY: ${OPENAI_API_KEY} MONGODB_URI: mongodb://mongo:27017 CHROMA_PATH: /app/chroma_db ALLOWED_ORIGINS: http://localhost:3000,http://frontend