Update app/database.py
Browse files- app/database.py +570 -1
app/database.py
CHANGED
|
@@ -258,9 +258,11 @@ def _update_daily_count(collection, user_object_id: ObjectId, today: datetime) -
|
|
| 258 |
collection.update_one(
|
| 259 |
{"userId": user_object_id},
|
| 260 |
{
|
|
|
|
| 261 |
"$push": {
|
| 262 |
"ai_edit_daily_count": {
|
| 263 |
-
"$each": dates_to_add
|
|
|
|
| 264 |
}
|
| 265 |
}
|
| 266 |
}
|
|
@@ -563,3 +565,570 @@ def close_connection():
|
|
| 563 |
_admin_db = None
|
| 564 |
logger.info("Admin MongoDB connection closed")
|
| 565 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
collection.update_one(
|
| 259 |
{"userId": user_object_id},
|
| 260 |
{
|
| 261 |
+
# Append new dates and keep only the most recent 32 entries
|
| 262 |
"$push": {
|
| 263 |
"ai_edit_daily_count": {
|
| 264 |
+
"$each": dates_to_add,
|
| 265 |
+
"$slice": -32,
|
| 266 |
}
|
| 267 |
}
|
| 268 |
}
|
|
|
|
| 565 |
_admin_db = None
|
| 566 |
logger.info("Admin MongoDB connection closed")
|
| 567 |
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
# """
|
| 571 |
+
# MongoDB database connection and logging utilities, including admin media click logging.
|
| 572 |
+
# """
|
| 573 |
+
# import os
|
| 574 |
+
# import logging
|
| 575 |
+
# from datetime import datetime, timedelta
|
| 576 |
+
# from typing import Optional, Dict, Any, Union, List
|
| 577 |
+
# from bson import ObjectId
|
| 578 |
+
# from pymongo import MongoClient
|
| 579 |
+
# from pymongo.errors import ConnectionFailure, ServerSelectionTimeoutError
|
| 580 |
+
|
| 581 |
+
# logger = logging.getLogger(__name__)
|
| 582 |
+
|
| 583 |
+
# # MongoDB connection
|
| 584 |
+
# _client: Optional[MongoClient] = None
|
| 585 |
+
# _db = None
|
| 586 |
+
|
| 587 |
+
# # Admin MongoDB connection (media_clicks)
|
| 588 |
+
# _admin_client: Optional[MongoClient] = None
|
| 589 |
+
# _admin_db = None
|
| 590 |
+
|
| 591 |
+
# def get_mongodb_client() -> Optional[MongoClient]:
|
| 592 |
+
# """Get or create MongoDB client"""
|
| 593 |
+
# global _client
|
| 594 |
+
# if _client is None:
|
| 595 |
+
# mongodb_uri = os.getenv("MONGODB_URI")
|
| 596 |
+
# if not mongodb_uri:
|
| 597 |
+
# logger.warning("MONGODB_URI environment variable not set. MongoDB features will be disabled.")
|
| 598 |
+
# return None
|
| 599 |
+
# try:
|
| 600 |
+
# _client = MongoClient(
|
| 601 |
+
# mongodb_uri,
|
| 602 |
+
# serverSelectionTimeoutMS=5000,
|
| 603 |
+
# connectTimeoutMS=5000
|
| 604 |
+
# )
|
| 605 |
+
# # Test connection
|
| 606 |
+
# _client.admin.command('ping')
|
| 607 |
+
# logger.info("MongoDB connection established successfully")
|
| 608 |
+
# except (ConnectionFailure, ServerSelectionTimeoutError) as e:
|
| 609 |
+
# logger.error("Failed to connect to MongoDB: %s", str(e))
|
| 610 |
+
# _client = None
|
| 611 |
+
# return _client
|
| 612 |
+
|
| 613 |
+
# def get_database():
|
| 614 |
+
# """Get database instance"""
|
| 615 |
+
# global _db
|
| 616 |
+
# if _db is None:
|
| 617 |
+
# client = get_mongodb_client()
|
| 618 |
+
# if client:
|
| 619 |
+
# db_name = os.getenv("MONGODB_DB_NAME", "colorization_db")
|
| 620 |
+
# _db = client[db_name]
|
| 621 |
+
# else:
|
| 622 |
+
# logger.warning("MongoDB client not available")
|
| 623 |
+
# return _db
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
# def get_admin_client() -> Optional[MongoClient]:
|
| 627 |
+
# """Get or create admin MongoDB client (for media_clicks collection)."""
|
| 628 |
+
# global _admin_client
|
| 629 |
+
# if _admin_client is None:
|
| 630 |
+
# mongodb_uri = os.getenv("MONGODB_ADMIN")
|
| 631 |
+
# if not mongodb_uri:
|
| 632 |
+
# logger.warning("MONGODB_ADMIN environment variable not set. Admin MongoDB features will be disabled.")
|
| 633 |
+
# return None
|
| 634 |
+
# try:
|
| 635 |
+
# _admin_client = MongoClient(
|
| 636 |
+
# mongodb_uri,
|
| 637 |
+
# serverSelectionTimeoutMS=5000,
|
| 638 |
+
# connectTimeoutMS=5000,
|
| 639 |
+
# )
|
| 640 |
+
# _admin_client.admin.command("ping")
|
| 641 |
+
# logger.info("Admin MongoDB connection established successfully")
|
| 642 |
+
# except (ConnectionFailure, ServerSelectionTimeoutError) as exc:
|
| 643 |
+
# logger.error("Failed to connect to Admin MongoDB: %s", str(exc))
|
| 644 |
+
# _admin_client = None
|
| 645 |
+
# return _admin_client
|
| 646 |
+
|
| 647 |
+
|
| 648 |
+
# def get_admin_database():
|
| 649 |
+
# """Get admin database instance."""
|
| 650 |
+
# global _admin_db
|
| 651 |
+
# if _admin_db is None:
|
| 652 |
+
# client = get_admin_client()
|
| 653 |
+
# if client:
|
| 654 |
+
# db_name = os.getenv("MONGODB_ADMIN_DB_NAME", "adminPanel")
|
| 655 |
+
# _admin_db = client[db_name]
|
| 656 |
+
# logger.info("Using admin database: %s", db_name)
|
| 657 |
+
# else:
|
| 658 |
+
# logger.warning("Admin MongoDB client not available")
|
| 659 |
+
# return _admin_db
|
| 660 |
+
|
| 661 |
+
|
| 662 |
+
# def _normalize_object_id(raw_value: Optional[Union[str, int, ObjectId]]) -> ObjectId:
|
| 663 |
+
# """Normalize user id inputs into a deterministic ObjectId."""
|
| 664 |
+
# if isinstance(raw_value, ObjectId):
|
| 665 |
+
# return raw_value
|
| 666 |
+
|
| 667 |
+
# if raw_value is None:
|
| 668 |
+
# return ObjectId()
|
| 669 |
+
|
| 670 |
+
# # Handle empty strings as None
|
| 671 |
+
# if isinstance(raw_value, str) and not raw_value.strip():
|
| 672 |
+
# return ObjectId()
|
| 673 |
+
|
| 674 |
+
# try:
|
| 675 |
+
# if isinstance(raw_value, int) or (isinstance(raw_value, str) and raw_value.strip().lstrip("-").isdigit()):
|
| 676 |
+
# int_value = int(str(raw_value).strip())
|
| 677 |
+
# hex_str = format(abs(int_value), "x").zfill(24)[-24:]
|
| 678 |
+
# if ObjectId.is_valid(hex_str):
|
| 679 |
+
# return ObjectId(hex_str)
|
| 680 |
+
# except Exception as exc: # pragma: no cover - defensive
|
| 681 |
+
# logger.debug("Numeric user id normalization failed: %s", str(exc))
|
| 682 |
+
|
| 683 |
+
# if isinstance(raw_value, str):
|
| 684 |
+
# candidate = raw_value.strip()
|
| 685 |
+
# if ObjectId.is_valid(candidate):
|
| 686 |
+
# return ObjectId(candidate)
|
| 687 |
+
|
| 688 |
+
# return ObjectId()
|
| 689 |
+
|
| 690 |
+
|
| 691 |
+
# def _objectid_from_any(value: str) -> ObjectId:
|
| 692 |
+
# """Convert arbitrary string into an ObjectId deterministically when possible."""
|
| 693 |
+
# if ObjectId.is_valid(value):
|
| 694 |
+
# return ObjectId(value)
|
| 695 |
+
# try:
|
| 696 |
+
# hex_str = value.encode("utf-8").hex().zfill(24)[-24:]
|
| 697 |
+
# if ObjectId.is_valid(hex_str):
|
| 698 |
+
# return ObjectId(hex_str)
|
| 699 |
+
# except Exception as exc: # pragma: no cover - defensive
|
| 700 |
+
# logger.debug("Category id normalization failed: %s", str(exc))
|
| 701 |
+
# return ObjectId()
|
| 702 |
+
|
| 703 |
+
|
| 704 |
+
# def _resolve_category_id(
|
| 705 |
+
# category_id: Optional[str],
|
| 706 |
+
# endpoint_path: Optional[str],
|
| 707 |
+
# default_category_id: Optional[str],
|
| 708 |
+
# ) -> ObjectId:
|
| 709 |
+
# """Pick category id from explicit value, endpoint default, or fallback."""
|
| 710 |
+
# # Handle empty strings as None
|
| 711 |
+
# if isinstance(category_id, str) and not category_id.strip():
|
| 712 |
+
# category_id = None
|
| 713 |
+
|
| 714 |
+
# endpoint_map = {
|
| 715 |
+
# "colorization": os.getenv("DEFAULT_CATEGORY_COLORIZATION"),
|
| 716 |
+
# "upload": os.getenv("DEFAULT_CATEGORY_COLORIZATION"),
|
| 717 |
+
# "colorize": os.getenv("DEFAULT_CATEGORY_COLORIZATION"),
|
| 718 |
+
# }
|
| 719 |
+
# normalized_endpoint = None
|
| 720 |
+
# if endpoint_path:
|
| 721 |
+
# normalized_endpoint = endpoint_path.strip("/").split("/")[0].lower() or None
|
| 722 |
+
|
| 723 |
+
# chosen = category_id
|
| 724 |
+
# if not chosen and normalized_endpoint and endpoint_map.get(normalized_endpoint):
|
| 725 |
+
# chosen = endpoint_map[normalized_endpoint]
|
| 726 |
+
# if not chosen:
|
| 727 |
+
# chosen = default_category_id or os.getenv("DEFAULT_CATEGORY_FALLBACK", "69368fcd2e46bd68ae1889b2")
|
| 728 |
+
|
| 729 |
+
# return _objectid_from_any(chosen)
|
| 730 |
+
|
| 731 |
+
|
| 732 |
+
# def _get_today_midnight_utc() -> datetime:
|
| 733 |
+
# """Get today's date at midnight UTC (timezone-naive for MongoDB compatibility)."""
|
| 734 |
+
# now = datetime.utcnow()
|
| 735 |
+
# return datetime(now.year, now.month, now.day)
|
| 736 |
+
|
| 737 |
+
|
| 738 |
+
# def _update_daily_count(collection, user_object_id: ObjectId, today: datetime) -> None:
|
| 739 |
+
# """Update ai_edit_daily_count array: add today with count 1 if missing, fill gaps with count 0."""
|
| 740 |
+
# # Ensure today is at midnight
|
| 741 |
+
# today_start = today.replace(hour=0, minute=0, second=0, microsecond=0)
|
| 742 |
+
|
| 743 |
+
# # Check if today's date already exists
|
| 744 |
+
# user_doc = collection.find_one({"userId": user_object_id})
|
| 745 |
+
|
| 746 |
+
# if not user_doc:
|
| 747 |
+
# # User document doesn't exist yet - this shouldn't happen as we create it first
|
| 748 |
+
# # But handle it gracefully by initializing the field
|
| 749 |
+
# logger.warning("User document not found when updating daily count, initializing")
|
| 750 |
+
# collection.update_one(
|
| 751 |
+
# {"userId": user_object_id},
|
| 752 |
+
# {
|
| 753 |
+
# "$setOnInsert": {
|
| 754 |
+
# "ai_edit_daily_count": [{
|
| 755 |
+
# "date": today_start,
|
| 756 |
+
# "count": 1
|
| 757 |
+
# }]
|
| 758 |
+
# }
|
| 759 |
+
# },
|
| 760 |
+
# upsert=False
|
| 761 |
+
# )
|
| 762 |
+
# return
|
| 763 |
+
|
| 764 |
+
# # Get existing daily counts
|
| 765 |
+
# existing_counts = user_doc.get("ai_edit_daily_count", [])
|
| 766 |
+
|
| 767 |
+
# # Check if today's date already exists
|
| 768 |
+
# today_exists = False
|
| 769 |
+
# for entry in existing_counts:
|
| 770 |
+
# entry_date = entry.get("date")
|
| 771 |
+
# if entry_date:
|
| 772 |
+
# # Normalize to midnight for comparison
|
| 773 |
+
# if isinstance(entry_date, datetime):
|
| 774 |
+
# normalized_date = entry_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
| 775 |
+
# if normalized_date == today_start:
|
| 776 |
+
# today_exists = True
|
| 777 |
+
# break
|
| 778 |
+
|
| 779 |
+
# # If today exists, do nothing (leave it as is)
|
| 780 |
+
# if today_exists:
|
| 781 |
+
# logger.debug("Today's date already exists in daily count, leaving unchanged: %s", today_start.isoformat())
|
| 782 |
+
# return
|
| 783 |
+
|
| 784 |
+
# # Today doesn't exist - need to add it and fill missing dates
|
| 785 |
+
# # Find the latest date in existing counts
|
| 786 |
+
# last_date = None
|
| 787 |
+
# if existing_counts:
|
| 788 |
+
# dates = []
|
| 789 |
+
# for entry in existing_counts:
|
| 790 |
+
# entry_date = entry.get("date")
|
| 791 |
+
# if entry_date:
|
| 792 |
+
# # Normalize to midnight for comparison
|
| 793 |
+
# if isinstance(entry_date, datetime):
|
| 794 |
+
# dates.append(entry_date.replace(hour=0, minute=0, second=0, microsecond=0))
|
| 795 |
+
# if dates:
|
| 796 |
+
# last_date = max(dates)
|
| 797 |
+
|
| 798 |
+
# # Generate missing dates between last_date and today
|
| 799 |
+
# dates_to_add = []
|
| 800 |
+
# if last_date:
|
| 801 |
+
# # Normalize last_date to midnight
|
| 802 |
+
# last_date = last_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
| 803 |
+
# current_date = last_date + timedelta(days=1)
|
| 804 |
+
# # Fill missing dates with count 0
|
| 805 |
+
# while current_date < today_start:
|
| 806 |
+
# dates_to_add.append({
|
| 807 |
+
# "date": current_date,
|
| 808 |
+
# "count": 0
|
| 809 |
+
# })
|
| 810 |
+
# current_date += timedelta(days=1)
|
| 811 |
+
|
| 812 |
+
# # Add today's entry with count 1
|
| 813 |
+
# dates_to_add.append({
|
| 814 |
+
# "date": today_start,
|
| 815 |
+
# "count": 1
|
| 816 |
+
# })
|
| 817 |
+
|
| 818 |
+
# # Push all missing dates (including today)
|
| 819 |
+
# if dates_to_add:
|
| 820 |
+
# # Ensure ai_edit_daily_count field exists
|
| 821 |
+
# if "ai_edit_daily_count" not in user_doc:
|
| 822 |
+
# collection.update_one(
|
| 823 |
+
# {"userId": user_object_id},
|
| 824 |
+
# {"$set": {"ai_edit_daily_count": []}}
|
| 825 |
+
# )
|
| 826 |
+
|
| 827 |
+
# collection.update_one(
|
| 828 |
+
# {"userId": user_object_id},
|
| 829 |
+
# {
|
| 830 |
+
# "$push": {
|
| 831 |
+
# "ai_edit_daily_count": {
|
| 832 |
+
# "$each": dates_to_add
|
| 833 |
+
# }
|
| 834 |
+
# }
|
| 835 |
+
# }
|
| 836 |
+
# )
|
| 837 |
+
# logger.debug("Added %d daily count entries (including today with count 1)", len(dates_to_add))
|
| 838 |
+
|
| 839 |
+
# def log_api_call(
|
| 840 |
+
# endpoint: str,
|
| 841 |
+
# method: str,
|
| 842 |
+
# status_code: int = 200,
|
| 843 |
+
# request_data: Optional[Dict[str, Any]] = None,
|
| 844 |
+
# response_data: Optional[Dict[str, Any]] = None,
|
| 845 |
+
# error: Optional[str] = None,
|
| 846 |
+
# user_id: Optional[str] = None,
|
| 847 |
+
# ip_address: Optional[str] = None
|
| 848 |
+
# ) -> bool:
|
| 849 |
+
# """
|
| 850 |
+
# Log API call to MongoDB
|
| 851 |
+
|
| 852 |
+
# Args:
|
| 853 |
+
# endpoint: API endpoint path
|
| 854 |
+
# method: HTTP method (GET, POST, etc.)
|
| 855 |
+
# status_code: HTTP status code
|
| 856 |
+
# request_data: Request data/parameters
|
| 857 |
+
# response_data: Response data
|
| 858 |
+
# error: Error message if any
|
| 859 |
+
# user_id: User ID if authenticated
|
| 860 |
+
# ip_address: Client IP address
|
| 861 |
+
|
| 862 |
+
# Returns:
|
| 863 |
+
# True if logged successfully, False otherwise
|
| 864 |
+
# """
|
| 865 |
+
# try:
|
| 866 |
+
# db = get_database()
|
| 867 |
+
# if db is None:
|
| 868 |
+
# logger.warning("MongoDB not available, skipping API log")
|
| 869 |
+
# return False
|
| 870 |
+
|
| 871 |
+
# collection = db["api_calls"]
|
| 872 |
+
|
| 873 |
+
# log_entry = {
|
| 874 |
+
# "endpoint": endpoint,
|
| 875 |
+
# "method": method,
|
| 876 |
+
# "status_code": status_code,
|
| 877 |
+
# "timestamp": datetime.utcnow(),
|
| 878 |
+
# "request_data": request_data or {},
|
| 879 |
+
# "response_data": response_data or {},
|
| 880 |
+
# "error": error,
|
| 881 |
+
# "user_id": user_id,
|
| 882 |
+
# "ip_address": ip_address
|
| 883 |
+
# }
|
| 884 |
+
|
| 885 |
+
# result = collection.insert_one(log_entry)
|
| 886 |
+
# logger.info("API call logged to MongoDB: %s", result.inserted_id)
|
| 887 |
+
# return True
|
| 888 |
+
# except Exception as e:
|
| 889 |
+
# logger.error("Failed to log API call to MongoDB: %s", str(e))
|
| 890 |
+
# return False
|
| 891 |
+
|
| 892 |
+
# def log_image_upload(
|
| 893 |
+
# image_id: str,
|
| 894 |
+
# filename: str,
|
| 895 |
+
# file_size: int,
|
| 896 |
+
# content_type: str,
|
| 897 |
+
# user_id: Optional[str] = None,
|
| 898 |
+
# ip_address: Optional[str] = None
|
| 899 |
+
# ) -> bool:
|
| 900 |
+
# """
|
| 901 |
+
# Log image upload to MongoDB
|
| 902 |
+
|
| 903 |
+
# Args:
|
| 904 |
+
# image_id: Unique image identifier
|
| 905 |
+
# filename: Original filename
|
| 906 |
+
# file_size: File size in bytes
|
| 907 |
+
# content_type: MIME type
|
| 908 |
+
# user_id: User ID if authenticated
|
| 909 |
+
# ip_address: Client IP address
|
| 910 |
+
|
| 911 |
+
# Returns:
|
| 912 |
+
# True if logged successfully, False otherwise
|
| 913 |
+
# """
|
| 914 |
+
# try:
|
| 915 |
+
# db = get_database()
|
| 916 |
+
# if db is None:
|
| 917 |
+
# logger.warning("MongoDB not available, skipping upload log")
|
| 918 |
+
# return False
|
| 919 |
+
|
| 920 |
+
# collection = db["image_uploads"]
|
| 921 |
+
|
| 922 |
+
# log_entry = {
|
| 923 |
+
# "image_id": image_id,
|
| 924 |
+
# "filename": filename,
|
| 925 |
+
# "file_size": file_size,
|
| 926 |
+
# "content_type": content_type,
|
| 927 |
+
# "uploaded_at": datetime.utcnow(),
|
| 928 |
+
# "user_id": user_id,
|
| 929 |
+
# "ip_address": ip_address
|
| 930 |
+
# }
|
| 931 |
+
|
| 932 |
+
# result = collection.insert_one(log_entry)
|
| 933 |
+
# logger.info("Image upload logged to MongoDB: %s", result.inserted_id)
|
| 934 |
+
# return True
|
| 935 |
+
# except Exception as e:
|
| 936 |
+
# logger.error("Failed to log image upload to MongoDB: %s", str(e))
|
| 937 |
+
# return False
|
| 938 |
+
|
| 939 |
+
# def log_colorization(
|
| 940 |
+
# result_id: Optional[str] = None,
|
| 941 |
+
# image_id: Optional[str] = None,
|
| 942 |
+
# prompt: Optional[str] = None,
|
| 943 |
+
# model_type: Optional[str] = None,
|
| 944 |
+
# processing_time: Optional[float] = None,
|
| 945 |
+
# user_id: Optional[str] = None,
|
| 946 |
+
# ip_address: Optional[str] = None,
|
| 947 |
+
# status: str = "success",
|
| 948 |
+
# error: Optional[str] = None
|
| 949 |
+
# ) -> bool:
|
| 950 |
+
# """
|
| 951 |
+
# Log colorization request to MongoDB (colorization_db -> colorizations collection)
|
| 952 |
+
# Logs both successful and failed API calls.
|
| 953 |
+
|
| 954 |
+
# Args:
|
| 955 |
+
# result_id: Unique result identifier (None for failed requests)
|
| 956 |
+
# image_id: Original image identifier
|
| 957 |
+
# prompt: Text prompt used (if any)
|
| 958 |
+
# model_type: Model type used (fastai, pytorch, sdxl, etc.)
|
| 959 |
+
# processing_time: Time taken to process in seconds
|
| 960 |
+
# user_id: User ID if authenticated
|
| 961 |
+
# ip_address: Client IP address
|
| 962 |
+
# status: Status of the request ("success" or "failed")
|
| 963 |
+
# error: Error message if status is "failed"
|
| 964 |
+
|
| 965 |
+
# Returns:
|
| 966 |
+
# True if logged successfully, False otherwise
|
| 967 |
+
# """
|
| 968 |
+
# try:
|
| 969 |
+
# db = get_database()
|
| 970 |
+
# if db is None:
|
| 971 |
+
# logger.warning("MongoDB not available, skipping colorization log")
|
| 972 |
+
# return False
|
| 973 |
+
|
| 974 |
+
# collection = db["colorizations"]
|
| 975 |
+
|
| 976 |
+
# # Generate result_id if not provided (for failed requests)
|
| 977 |
+
# if not result_id:
|
| 978 |
+
# import uuid
|
| 979 |
+
# result_id = str(uuid.uuid4())
|
| 980 |
+
|
| 981 |
+
# log_entry = {
|
| 982 |
+
# "result_id": result_id,
|
| 983 |
+
# "image_id": image_id,
|
| 984 |
+
# "prompt": prompt,
|
| 985 |
+
# "model_type": model_type,
|
| 986 |
+
# "processing_time": processing_time,
|
| 987 |
+
# "created_at": datetime.utcnow(),
|
| 988 |
+
# "user_id": user_id,
|
| 989 |
+
# "ip_address": ip_address
|
| 990 |
+
# }
|
| 991 |
+
|
| 992 |
+
# # Add status and error fields only if provided (for new documents)
|
| 993 |
+
# # Existing documents won't have these fields, which is fine
|
| 994 |
+
# if status:
|
| 995 |
+
# log_entry["status"] = status
|
| 996 |
+
# if error:
|
| 997 |
+
# log_entry["error"] = error
|
| 998 |
+
|
| 999 |
+
# result = collection.insert_one(log_entry)
|
| 1000 |
+
# logger.info("Colorization logged to MongoDB (status: %s): %s", status, result.inserted_id)
|
| 1001 |
+
# return True
|
| 1002 |
+
# except Exception as e:
|
| 1003 |
+
# logger.error("Failed to log colorization to MongoDB: %s", str(e))
|
| 1004 |
+
# return False
|
| 1005 |
+
|
| 1006 |
+
|
| 1007 |
+
# def log_media_click(
|
| 1008 |
+
# user_id: Optional[Union[str, int, ObjectId]],
|
| 1009 |
+
# category_id: Optional[str],
|
| 1010 |
+
# *,
|
| 1011 |
+
# endpoint_path: Optional[str] = None,
|
| 1012 |
+
# default_category_id: Optional[str] = None,
|
| 1013 |
+
# ) -> bool:
|
| 1014 |
+
# """Log media clicks into the admin MongoDB (media_clicks collection).
|
| 1015 |
+
|
| 1016 |
+
# Only logs if user_id is provided. If user_id is None or empty, returns False without logging.
|
| 1017 |
+
# Regular MongoDB logging (api_calls, image_uploads, colorizations) always happens regardless.
|
| 1018 |
+
# """
|
| 1019 |
+
# # Only log to media_clicks if user_id is provided
|
| 1020 |
+
# if not user_id or (isinstance(user_id, str) and not user_id.strip()):
|
| 1021 |
+
# logger.debug("Skipping media click log - user_id not provided")
|
| 1022 |
+
# return False
|
| 1023 |
+
|
| 1024 |
+
# try:
|
| 1025 |
+
# db = get_admin_database()
|
| 1026 |
+
# if db is None:
|
| 1027 |
+
# logger.warning("Admin MongoDB not available, skipping media click log")
|
| 1028 |
+
# return False
|
| 1029 |
+
|
| 1030 |
+
# collection = db["media_clicks"]
|
| 1031 |
+
|
| 1032 |
+
# # Drop legacy index to avoid duplicate key errors (best effort)
|
| 1033 |
+
# try:
|
| 1034 |
+
# collection.drop_index("user_id_1_header_1_media_id_1")
|
| 1035 |
+
# except Exception as exc:
|
| 1036 |
+
# logger.debug("Legacy index drop skipped: %s", str(exc))
|
| 1037 |
+
|
| 1038 |
+
# user_object_id = _normalize_object_id(user_id)
|
| 1039 |
+
# category_object_id = _resolve_category_id(category_id, endpoint_path, default_category_id)
|
| 1040 |
+
# now = datetime.utcnow()
|
| 1041 |
+
# today = _get_today_midnight_utc()
|
| 1042 |
+
|
| 1043 |
+
# logger.info("Media click - user_id input: %s, normalized: %s, category_id input: %s, normalized: %s",
|
| 1044 |
+
# user_id, str(user_object_id), category_id, str(category_object_id))
|
| 1045 |
+
|
| 1046 |
+
# # Try to update existing category in new schema (categories.categoryId)
|
| 1047 |
+
# update_existing = collection.update_one(
|
| 1048 |
+
# {"userId": user_object_id, "categories.categoryId": category_object_id},
|
| 1049 |
+
# {
|
| 1050 |
+
# "$inc": {
|
| 1051 |
+
# "categories.$.click_count": 1,
|
| 1052 |
+
# "ai_edit_complete": 1 # Increment usage count
|
| 1053 |
+
# },
|
| 1054 |
+
# "$set": {
|
| 1055 |
+
# "categories.$.lastClickedAt": now,
|
| 1056 |
+
# "updatedAt": now,
|
| 1057 |
+
# "ai_edit_last_date": now # Update last used date (MongoDB Date object)
|
| 1058 |
+
# },
|
| 1059 |
+
# },
|
| 1060 |
+
# )
|
| 1061 |
+
|
| 1062 |
+
# if update_existing.matched_count == 0:
|
| 1063 |
+
# # Category not found in new schema, check if user exists
|
| 1064 |
+
# user_exists = collection.find_one({"userId": user_object_id})
|
| 1065 |
+
|
| 1066 |
+
# if user_exists:
|
| 1067 |
+
# # User exists but category doesn't in new schema - add to categories array
|
| 1068 |
+
# collection.update_one(
|
| 1069 |
+
# {"userId": user_object_id},
|
| 1070 |
+
# {
|
| 1071 |
+
# "$inc": {"ai_edit_complete": 1}, # Increment usage count
|
| 1072 |
+
# "$set": {
|
| 1073 |
+
# "updatedAt": now,
|
| 1074 |
+
# "ai_edit_last_date": now # Update last used date (MongoDB Date object)
|
| 1075 |
+
# },
|
| 1076 |
+
# "$push": {
|
| 1077 |
+
# "categories": {
|
| 1078 |
+
# "categoryId": category_object_id,
|
| 1079 |
+
# "click_count": 1,
|
| 1080 |
+
# "lastClickedAt": now,
|
| 1081 |
+
# }
|
| 1082 |
+
# },
|
| 1083 |
+
# },
|
| 1084 |
+
# )
|
| 1085 |
+
# else:
|
| 1086 |
+
# # User doesn't exist - create new document with categories array
|
| 1087 |
+
# # Default ai_edit_complete = 0, then increment to 1 on first use
|
| 1088 |
+
# collection.update_one(
|
| 1089 |
+
# {"userId": user_object_id},
|
| 1090 |
+
# {
|
| 1091 |
+
# "$setOnInsert": {
|
| 1092 |
+
# "createdAt": now,
|
| 1093 |
+
# "ai_edit_complete": 0, # Default 0 for new users
|
| 1094 |
+
# "ai_edit_daily_count": [] # Initialize empty array
|
| 1095 |
+
# },
|
| 1096 |
+
# "$inc": {"ai_edit_complete": 1}, # Increment to 1 on first use
|
| 1097 |
+
# "$set": {
|
| 1098 |
+
# "updatedAt": now,
|
| 1099 |
+
# "ai_edit_last_date": now # Set last used date (MongoDB Date object)
|
| 1100 |
+
# },
|
| 1101 |
+
# "$push": {
|
| 1102 |
+
# "categories": {
|
| 1103 |
+
# "categoryId": category_object_id,
|
| 1104 |
+
# "click_count": 1,
|
| 1105 |
+
# "lastClickedAt": now,
|
| 1106 |
+
# }
|
| 1107 |
+
# },
|
| 1108 |
+
# },
|
| 1109 |
+
# upsert=True,
|
| 1110 |
+
# )
|
| 1111 |
+
|
| 1112 |
+
# # Update daily count (after document is created/updated)
|
| 1113 |
+
# _update_daily_count(collection, user_object_id, today)
|
| 1114 |
+
|
| 1115 |
+
# logger.info("Media click logged for user %s", str(user_object_id))
|
| 1116 |
+
# return True
|
| 1117 |
+
# except Exception as exc:
|
| 1118 |
+
# logger.error("Failed to log media click to admin MongoDB: %s", str(exc))
|
| 1119 |
+
# return False
|
| 1120 |
+
|
| 1121 |
+
# def close_connection():
|
| 1122 |
+
# """Close MongoDB connection"""
|
| 1123 |
+
# global _client, _db, _admin_client, _admin_db
|
| 1124 |
+
# if _client:
|
| 1125 |
+
# _client.close()
|
| 1126 |
+
# _client = None
|
| 1127 |
+
# _db = None
|
| 1128 |
+
# logger.info("MongoDB connection closed")
|
| 1129 |
+
# if _admin_client:
|
| 1130 |
+
# _admin_client.close()
|
| 1131 |
+
# _admin_client = None
|
| 1132 |
+
# _admin_db = None
|
| 1133 |
+
# logger.info("Admin MongoDB connection closed")
|
| 1134 |
+
|