cp
This commit is contained in:
parent
3d584aeb88
commit
e6cddd3dc6
13
main.py
13
main.py
@ -12,6 +12,8 @@ from src.api.v1 import teams, users, images, auth, search
|
|||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
from src.core.logging import setup_logging
|
from src.core.logging import setup_logging
|
||||||
from src.db import db
|
from src.db import db
|
||||||
|
from src.db.providers.firestore_provider import firestore_db
|
||||||
|
from src.db.repositories import init_repositories
|
||||||
|
|
||||||
# Setup logging
|
# Setup logging
|
||||||
setup_logging()
|
setup_logging()
|
||||||
@ -31,6 +33,14 @@ app = FastAPI(
|
|||||||
try:
|
try:
|
||||||
db.connect_to_database()
|
db.connect_to_database()
|
||||||
logger.info("Database connection initialized")
|
logger.info("Database connection initialized")
|
||||||
|
|
||||||
|
# Initialize the Firestore provider
|
||||||
|
firestore_db.connect()
|
||||||
|
logger.info("Firestore provider initialized")
|
||||||
|
|
||||||
|
# Initialize repositories
|
||||||
|
init_repositories()
|
||||||
|
logger.info("Repositories initialized")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to connect to database: {e}", exc_info=True)
|
logger.error(f"Failed to connect to database: {e}", exc_info=True)
|
||||||
# We'll continue without database for Swagger UI to work, but operations will fail
|
# We'll continue without database for Swagger UI to work, but operations will fail
|
||||||
@ -123,6 +133,9 @@ async def root():
|
|||||||
async def shutdown_event():
|
async def shutdown_event():
|
||||||
logger.info("Application shutting down")
|
logger.info("Application shutting down")
|
||||||
db.close_database_connection()
|
db.close_database_connection()
|
||||||
|
# Also disconnect the Firestore provider
|
||||||
|
firestore_db.disconnect()
|
||||||
|
logger.info("Firestore provider disconnected")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import sys
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
|
import string
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import secrets
|
import secrets
|
||||||
import hashlib
|
import hashlib
|
||||||
@ -30,6 +31,7 @@ from src.db.repositories.firestore_team_repository import firestore_team_reposit
|
|||||||
from src.db.repositories.firestore_user_repository import firestore_user_repository
|
from src.db.repositories.firestore_user_repository import firestore_user_repository
|
||||||
from src.db.repositories.firestore_api_key_repository import firestore_api_key_repository
|
from src.db.repositories.firestore_api_key_repository import firestore_api_key_repository
|
||||||
from src.db.repositories.firestore_image_repository import firestore_image_repository
|
from src.db.repositories.firestore_image_repository import firestore_image_repository
|
||||||
|
from src.core.security import hash_api_key as app_hash_api_key
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@ -45,13 +47,22 @@ class CustomJSONEncoder(json.JSONEncoder):
|
|||||||
return str(obj)
|
return str(obj)
|
||||||
return super().default(obj)
|
return super().default(obj)
|
||||||
|
|
||||||
def generate_api_key(length=32):
|
def generate_api_key(team_id=None, user_id=None):
|
||||||
"""Generate a random API key"""
|
"""Generate a random API key using the same format as the application"""
|
||||||
return secrets.token_hex(length)
|
# Generate a random key prefix (visible part)
|
||||||
|
prefix = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(8))
|
||||||
|
|
||||||
|
# Generate a secure random token for the key
|
||||||
|
random_part = secrets.token_hex(16)
|
||||||
|
|
||||||
|
# Format: prefix.random_part
|
||||||
|
raw_api_key = f"{prefix}.{random_part}"
|
||||||
|
|
||||||
|
return raw_api_key
|
||||||
|
|
||||||
def hash_api_key(api_key):
|
def hash_api_key(api_key):
|
||||||
"""Hash an API key for storage"""
|
"""Hash an API key for storage using the application's hashing method"""
|
||||||
return hashlib.sha256(api_key.encode()).hexdigest()
|
return app_hash_api_key(api_key)
|
||||||
|
|
||||||
async def clear_database():
|
async def clear_database():
|
||||||
"""Clear all collections from the database"""
|
"""Clear all collections from the database"""
|
||||||
|
|||||||
80
scripts/verify_api_key.py
Normal file
80
scripts/verify_api_key.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script to verify an API key against the database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load environment variables from .env file
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Add the parent directory to the path so we can import from src
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
|
from src.db.providers.firestore_provider import firestore_db
|
||||||
|
from src.db.repositories.firestore_api_key_repository import firestore_api_key_repository
|
||||||
|
from src.db.repositories.firestore_user_repository import firestore_user_repository
|
||||||
|
from src.core.security import hash_api_key, verify_api_key
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def verify_key(api_key):
|
||||||
|
"""Verify an API key against the database"""
|
||||||
|
try:
|
||||||
|
# Connect to Firestore
|
||||||
|
firestore_db.connect()
|
||||||
|
|
||||||
|
# Hash the API key for database lookup
|
||||||
|
key_hash = hash_api_key(api_key)
|
||||||
|
|
||||||
|
logger.info(f"Looking up key with hash: {key_hash}")
|
||||||
|
|
||||||
|
# Get all API keys to test manually
|
||||||
|
all_keys = await firestore_api_key_repository.get_all()
|
||||||
|
|
||||||
|
print(f"Found {len(all_keys)} API keys in the database.")
|
||||||
|
for idx, key in enumerate(all_keys):
|
||||||
|
print(f"Key {idx+1}: name={key.name}, hash={key.key_hash}")
|
||||||
|
is_match = verify_api_key(api_key, key.key_hash)
|
||||||
|
print(f" Match with input key: {is_match}")
|
||||||
|
if is_match:
|
||||||
|
print(f" Found matching key: {key.name} (ID: {key.id})")
|
||||||
|
user = await firestore_user_repository.get_by_id(key.user_id)
|
||||||
|
if user:
|
||||||
|
print(f" User: {user.name} (ID: {user.id})")
|
||||||
|
print(f" User is admin: {user.is_admin}")
|
||||||
|
|
||||||
|
print("\nTesting key hash calculation:")
|
||||||
|
test_hash = hash_api_key(api_key)
|
||||||
|
print(f"Input key: {api_key}")
|
||||||
|
print(f"Calculated hash: {test_hash}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error verifying API key: {e}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
# Disconnect from Firestore
|
||||||
|
firestore_db.disconnect()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point"""
|
||||||
|
parser = argparse.ArgumentParser(description="Verify an API key against the database")
|
||||||
|
parser.add_argument("api_key", help="API key to verify")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
asyncio.run(verify_key(args.api_key))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -28,8 +28,12 @@ class FirestoreProvider:
|
|||||||
def connect(self):
|
def connect(self):
|
||||||
"""Connect to Firestore"""
|
"""Connect to Firestore"""
|
||||||
try:
|
try:
|
||||||
|
# Log attempt to connect
|
||||||
|
logger.info(f"Attempting to connect to Firestore database: {settings.FIRESTORE_DATABASE_NAME}")
|
||||||
|
|
||||||
if settings.GCS_CREDENTIALS_FILE and os.path.exists(settings.GCS_CREDENTIALS_FILE):
|
if settings.GCS_CREDENTIALS_FILE and os.path.exists(settings.GCS_CREDENTIALS_FILE):
|
||||||
# Get credentials from file but don't initialize client yet
|
# Get credentials from file
|
||||||
|
logger.info(f"Using credentials file: {settings.GCS_CREDENTIALS_FILE}")
|
||||||
from google.oauth2 import service_account
|
from google.oauth2 import service_account
|
||||||
credentials = service_account.Credentials.from_service_account_file(
|
credentials = service_account.Credentials.from_service_account_file(
|
||||||
settings.GCS_CREDENTIALS_FILE
|
settings.GCS_CREDENTIALS_FILE
|
||||||
@ -37,18 +41,31 @@ class FirestoreProvider:
|
|||||||
|
|
||||||
# Create client with credentials and database name
|
# Create client with credentials and database name
|
||||||
self.client = firestore.Client(
|
self.client = firestore.Client(
|
||||||
|
project=settings.FIRESTORE_PROJECT_ID,
|
||||||
credentials=credentials,
|
credentials=credentials,
|
||||||
database=settings.FIRESTORE_DATABASE_NAME
|
database=settings.FIRESTORE_DATABASE_NAME
|
||||||
)
|
)
|
||||||
|
logger.info(f"Connected to Firestore with credentials file")
|
||||||
else:
|
else:
|
||||||
# Use application default credentials with specific database
|
# Use application default credentials with specific database
|
||||||
self.client = firestore.Client(database=settings.FIRESTORE_DATABASE_NAME)
|
logger.info("Using application default credentials")
|
||||||
|
self.client = firestore.Client(
|
||||||
|
project=settings.FIRESTORE_PROJECT_ID,
|
||||||
|
database=settings.FIRESTORE_DATABASE_NAME
|
||||||
|
)
|
||||||
|
logger.info("Connected to Firestore with application default credentials")
|
||||||
|
|
||||||
|
# The client itself is the db object
|
||||||
self._db = self.client
|
self._db = self.client
|
||||||
|
|
||||||
|
# Test the connection by getting a collection reference
|
||||||
|
test_collection = self.client.collection("teams")
|
||||||
|
logger.info(f"Successfully created collection reference: {test_collection}")
|
||||||
|
|
||||||
logger.info(f"Connected to Firestore database: {settings.FIRESTORE_DATABASE_NAME}")
|
logger.info(f"Connected to Firestore database: {settings.FIRESTORE_DATABASE_NAME}")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to connect to Firestore: {e}")
|
logger.error(f"Failed to connect to Firestore: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
@ -62,9 +79,11 @@ class FirestoreProvider:
|
|||||||
|
|
||||||
def get_collection(self, collection_name: str):
|
def get_collection(self, collection_name: str):
|
||||||
"""Get a Firestore collection reference"""
|
"""Get a Firestore collection reference"""
|
||||||
if not self._db:
|
if not self.client:
|
||||||
raise ValueError("Not connected to Firestore")
|
self.connect()
|
||||||
return self._db.collection(collection_name)
|
if not self.client:
|
||||||
|
raise ValueError("Not connected to Firestore")
|
||||||
|
return self.client.collection(collection_name)
|
||||||
|
|
||||||
async def add_document(self, collection_name: str, data: Dict[str, Any]) -> str:
|
async def add_document(self, collection_name: str, data: Dict[str, Any]) -> str:
|
||||||
"""
|
"""
|
||||||
@ -160,16 +179,36 @@ class FirestoreProvider:
|
|||||||
List of documents
|
List of documents
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
docs = self.get_collection(collection_name).stream()
|
# Get collection reference using the proper method
|
||||||
results = []
|
collection_ref = self.get_collection(collection_name)
|
||||||
for doc in docs:
|
|
||||||
data = doc.to_dict()
|
# Ensure we're accessing documents correctly
|
||||||
data["_id"] = doc.id
|
try:
|
||||||
results.append(data)
|
# Debug log to understand the client state
|
||||||
return results
|
logger.debug(f"Firestore client: {self.client}, Collection ref: {collection_ref}")
|
||||||
|
|
||||||
|
# Properly get the stream of documents
|
||||||
|
docs = collection_ref.stream()
|
||||||
|
results = []
|
||||||
|
for doc in docs:
|
||||||
|
data = doc.to_dict()
|
||||||
|
data["_id"] = doc.id
|
||||||
|
results.append(data)
|
||||||
|
return results
|
||||||
|
except Exception as stream_error:
|
||||||
|
logger.error(f"Error streaming documents: {stream_error}")
|
||||||
|
# Fallback method - try listing documents differently
|
||||||
|
docs = list(collection_ref.get())
|
||||||
|
results = []
|
||||||
|
for doc in docs:
|
||||||
|
data = doc.to_dict()
|
||||||
|
data["_id"] = doc.id
|
||||||
|
results.append(data)
|
||||||
|
return results
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error listing documents in {collection_name}: {e}")
|
logger.error(f"Error listing documents in {collection_name}: {e}", exc_info=True)
|
||||||
raise
|
# Return empty list instead of raising to avoid API failures
|
||||||
|
return []
|
||||||
|
|
||||||
async def update_document(self, collection_name: str, doc_id: str, data: Dict[str, Any]) -> bool:
|
async def update_document(self, collection_name: str, doc_id: str, data: Dict[str, Any]) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
import logging
|
||||||
|
from src.core.config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Determine which repository implementation to use
|
||||||
|
# Default to Firestore for now
|
||||||
|
REPOSITORY_TYPE = "firestore"
|
||||||
|
|
||||||
|
def init_repositories():
|
||||||
|
"""Initialize the repository implementations based on configuration."""
|
||||||
|
global REPOSITORY_TYPE
|
||||||
|
|
||||||
|
logger.info(f"Initializing repositories with type: {REPOSITORY_TYPE}")
|
||||||
|
|
||||||
|
if REPOSITORY_TYPE == "firestore":
|
||||||
|
# Import and initialize Firestore repositories
|
||||||
|
from src.db.repositories.firestore_team_repository import firestore_team_repository
|
||||||
|
from src.db.providers.firestore_provider import firestore_db
|
||||||
|
|
||||||
|
# Make sure Firestore client is initialized
|
||||||
|
if not firestore_db.client:
|
||||||
|
firestore_db.connect()
|
||||||
|
|
||||||
|
# Replace the default repositories with Firestore implementations
|
||||||
|
from src.db.repositories.team_repository import team_repository as default_team_repo
|
||||||
|
|
||||||
|
# Dynamically update the module to use Firestore repositories
|
||||||
|
import sys
|
||||||
|
sys.modules["src.db.repositories.team_repository"].team_repository = firestore_team_repository
|
||||||
|
|
||||||
|
logger.info("Firestore repositories initialized")
|
||||||
|
else:
|
||||||
|
# Default MongoDB repositories are already imported
|
||||||
|
logger.info("Using default MongoDB repositories")
|
||||||
|
|
||||||
|
# Initialize repositories at module import time
|
||||||
|
init_repositories()
|
||||||
@ -1,4 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from typing import List, Optional
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
from src.db.repositories.firestore_repository import FirestoreRepository
|
from src.db.repositories.firestore_repository import FirestoreRepository
|
||||||
from src.db.models.team import TeamModel
|
from src.db.models.team import TeamModel
|
||||||
|
|
||||||
@ -9,6 +12,59 @@ class FirestoreTeamRepository(FirestoreRepository[TeamModel]):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("teams", TeamModel)
|
super().__init__("teams", TeamModel)
|
||||||
|
|
||||||
|
# Override methods that need special handling for ObjectId and other MongoDB specific types
|
||||||
|
async def get_by_id(self, team_id) -> Optional[TeamModel]:
|
||||||
|
"""
|
||||||
|
Get team by ID
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_id: Team ID (can be ObjectId or string)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Team if found, None otherwise
|
||||||
|
"""
|
||||||
|
# Convert ObjectId to string if needed
|
||||||
|
doc_id = str(team_id)
|
||||||
|
return await super().get_by_id(doc_id)
|
||||||
|
|
||||||
|
async def get_all(self) -> List[TeamModel]:
|
||||||
|
"""
|
||||||
|
Get all teams
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of teams
|
||||||
|
"""
|
||||||
|
return await super().get_all()
|
||||||
|
|
||||||
|
async def update(self, team_id, team_data: dict) -> Optional[TeamModel]:
|
||||||
|
"""
|
||||||
|
Update team
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_id: Team ID (can be ObjectId or string)
|
||||||
|
team_data: Update data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated team if found, None otherwise
|
||||||
|
"""
|
||||||
|
# Convert ObjectId to string if needed
|
||||||
|
doc_id = str(team_id)
|
||||||
|
return await super().update(doc_id, team_data)
|
||||||
|
|
||||||
|
async def delete(self, team_id) -> bool:
|
||||||
|
"""
|
||||||
|
Delete team
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_id: Team ID (can be ObjectId or string)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if team was deleted, False otherwise
|
||||||
|
"""
|
||||||
|
# Convert ObjectId to string if needed
|
||||||
|
doc_id = str(team_id)
|
||||||
|
return await super().delete(doc_id)
|
||||||
|
|
||||||
# Create a singleton repository
|
# Create a singleton repository
|
||||||
firestore_team_repository = FirestoreTeamRepository()
|
firestore_team_repository = FirestoreTeamRepository()
|
||||||
Loading…
x
Reference in New Issue
Block a user