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.logging import setup_logging
|
||||
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()
|
||||
@ -31,6 +33,14 @@ app = FastAPI(
|
||||
try:
|
||||
db.connect_to_database()
|
||||
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:
|
||||
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
|
||||
@ -123,6 +133,9 @@ async def root():
|
||||
async def shutdown_event():
|
||||
logger.info("Application shutting down")
|
||||
db.close_database_connection()
|
||||
# Also disconnect the Firestore provider
|
||||
firestore_db.disconnect()
|
||||
logger.info("Firestore provider disconnected")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
@ -8,6 +8,7 @@ import sys
|
||||
import asyncio
|
||||
import logging
|
||||
import argparse
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
import secrets
|
||||
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_api_key_repository import firestore_api_key_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
|
||||
logging.basicConfig(
|
||||
@ -45,13 +47,22 @@ class CustomJSONEncoder(json.JSONEncoder):
|
||||
return str(obj)
|
||||
return super().default(obj)
|
||||
|
||||
def generate_api_key(length=32):
|
||||
"""Generate a random API key"""
|
||||
return secrets.token_hex(length)
|
||||
def generate_api_key(team_id=None, user_id=None):
|
||||
"""Generate a random API key using the same format as the application"""
|
||||
# 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):
|
||||
"""Hash an API key for storage"""
|
||||
return hashlib.sha256(api_key.encode()).hexdigest()
|
||||
"""Hash an API key for storage using the application's hashing method"""
|
||||
return app_hash_api_key(api_key)
|
||||
|
||||
async def clear_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):
|
||||
"""Connect to Firestore"""
|
||||
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):
|
||||
# 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
|
||||
credentials = service_account.Credentials.from_service_account_file(
|
||||
settings.GCS_CREDENTIALS_FILE
|
||||
@ -37,18 +41,31 @@ class FirestoreProvider:
|
||||
|
||||
# Create client with credentials and database name
|
||||
self.client = firestore.Client(
|
||||
project=settings.FIRESTORE_PROJECT_ID,
|
||||
credentials=credentials,
|
||||
database=settings.FIRESTORE_DATABASE_NAME
|
||||
)
|
||||
logger.info(f"Connected to Firestore with credentials file")
|
||||
else:
|
||||
# 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
|
||||
|
||||
# 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}")
|
||||
return True
|
||||
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
|
||||
|
||||
def disconnect(self):
|
||||
@ -62,9 +79,11 @@ class FirestoreProvider:
|
||||
|
||||
def get_collection(self, collection_name: str):
|
||||
"""Get a Firestore collection reference"""
|
||||
if not self._db:
|
||||
raise ValueError("Not connected to Firestore")
|
||||
return self._db.collection(collection_name)
|
||||
if not self.client:
|
||||
self.connect()
|
||||
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:
|
||||
"""
|
||||
@ -160,16 +179,36 @@ class FirestoreProvider:
|
||||
List of documents
|
||||
"""
|
||||
try:
|
||||
docs = self.get_collection(collection_name).stream()
|
||||
results = []
|
||||
for doc in docs:
|
||||
data = doc.to_dict()
|
||||
data["_id"] = doc.id
|
||||
results.append(data)
|
||||
return results
|
||||
# Get collection reference using the proper method
|
||||
collection_ref = self.get_collection(collection_name)
|
||||
|
||||
# Ensure we're accessing documents correctly
|
||||
try:
|
||||
# Debug log to understand the client state
|
||||
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:
|
||||
logger.error(f"Error listing documents in {collection_name}: {e}")
|
||||
raise
|
||||
logger.error(f"Error listing documents in {collection_name}: {e}", exc_info=True)
|
||||
# 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:
|
||||
"""
|
||||
|
||||
@ -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
|
||||
from typing import List, Optional
|
||||
from bson import ObjectId
|
||||
|
||||
from src.db.repositories.firestore_repository import FirestoreRepository
|
||||
from src.db.models.team import TeamModel
|
||||
|
||||
@ -9,6 +12,59 @@ class FirestoreTeamRepository(FirestoreRepository[TeamModel]):
|
||||
|
||||
def __init__(self):
|
||||
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
|
||||
firestore_team_repository = FirestoreTeamRepository()
|
||||
Loading…
x
Reference in New Issue
Block a user