Spaces:
Sleeping
Sleeping
Commit
Β·
54fe70d
1
Parent(s):
a3dd63c
cloudinary upload
Browse files- .env +4 -1
- app/__pycache__/config.cpython-311.pyc +0 -0
- app/__pycache__/main.cpython-311.pyc +0 -0
- app/config.py +6 -0
- app/main.py +25 -0
- app/routers/__pycache__/incidents.cpython-311.pyc +0 -0
- app/routers/incidents.py +10 -1
- app/services/fallback_storage.py +61 -0
- app/services/incidents.py +54 -36
- app/uploads/222544d2e2e44e8d812becab341d74ef.txt +1 -0
- app/uploads/becb6a146b764fd6a50cb2b1305ccba6.jpg +1 -0
- create_test_user.py +44 -0
- debug_cloudinary.py +66 -0
- list_incidents.py +78 -0
- test_api_cloudinary.py +118 -0
- test_cloudinary.py +52 -0
- test_cloudinary_improved.py +157 -0
- test_cloudinary_mandatory.py +162 -0
- test_files/cloudinary_test_7574.jpg +0 -0
- test_image.txt +1 -0
.env
CHANGED
|
@@ -1,3 +1,6 @@
|
|
| 1 |
JWT_SECRET_KEY=change_this_secret_in_production
|
| 2 |
MONGODB_URI=mongodb+srv://deepdblm_db_user:IqLKnKhwLLSOP1Ka@cluster0.0u1vpow.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0
|
| 3 |
-
ALLOWED_ORIGINS=http://localhost:8080,https://marine-pollution-detection.vercel.app
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
JWT_SECRET_KEY=change_this_secret_in_production
|
| 2 |
MONGODB_URI=mongodb+srv://deepdblm_db_user:IqLKnKhwLLSOP1Ka@cluster0.0u1vpow.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0
|
| 3 |
+
ALLOWED_ORIGINS=http://localhost:8080,https://marine-pollution-detection.vercel.app
|
| 4 |
+
CLOUDINARY_CLOUD_NAME=dler9jdjf
|
| 5 |
+
CLOUDINARY_API_KEY=414766445792264
|
| 6 |
+
CLOUDINARY_API_SECRET=kV133xtcDbxh4tEMvk5CS4WFx1U
|
app/__pycache__/config.cpython-311.pyc
CHANGED
|
Binary files a/app/__pycache__/config.cpython-311.pyc and b/app/__pycache__/config.cpython-311.pyc differ
|
|
|
app/__pycache__/main.cpython-311.pyc
CHANGED
|
Binary files a/app/__pycache__/main.cpython-311.pyc and b/app/__pycache__/main.cpython-311.pyc differ
|
|
|
app/config.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
from functools import lru_cache
|
| 2 |
from typing import List, Union
|
| 3 |
import os
|
|
|
|
| 4 |
|
| 5 |
from pydantic_settings import BaseSettings
|
| 6 |
|
|
@@ -14,6 +15,11 @@ class Settings(BaseSettings):
|
|
| 14 |
access_token_expire_minutes: int = 60 * 24
|
| 15 |
allowed_origins: Union[str, List[str]] = "http://localhost:5173"
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# Hugging Face Spaces specific settings
|
| 18 |
space_id: str = os.getenv("SPACE_ID", "")
|
| 19 |
|
|
|
|
| 1 |
from functools import lru_cache
|
| 2 |
from typing import List, Union
|
| 3 |
import os
|
| 4 |
+
import cloudinary
|
| 5 |
|
| 6 |
from pydantic_settings import BaseSettings
|
| 7 |
|
|
|
|
| 15 |
access_token_expire_minutes: int = 60 * 24
|
| 16 |
allowed_origins: Union[str, List[str]] = "http://localhost:5173"
|
| 17 |
|
| 18 |
+
# Cloudinary settings
|
| 19 |
+
cloudinary_cloud_name: str
|
| 20 |
+
cloudinary_api_key: str
|
| 21 |
+
cloudinary_api_secret: str
|
| 22 |
+
|
| 23 |
# Hugging Face Spaces specific settings
|
| 24 |
space_id: str = os.getenv("SPACE_ID", "")
|
| 25 |
|
app/main.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
import logging
|
| 2 |
from contextlib import asynccontextmanager
|
|
|
|
|
|
|
| 3 |
|
| 4 |
from fastapi import FastAPI
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
|
@@ -8,6 +10,14 @@ from .config import get_settings
|
|
| 8 |
from .database import get_collection, test_connection
|
| 9 |
from .routers import auth, incidents
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
logger = logging.getLogger(__name__)
|
| 12 |
|
| 13 |
|
|
@@ -35,6 +45,21 @@ async def setup_database_indexes():
|
|
| 35 |
@asynccontextmanager
|
| 36 |
async def lifespan(app: FastAPI):
|
| 37 |
# Startup
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
await setup_database_indexes()
|
| 39 |
yield
|
| 40 |
# Shutdown (if needed in future)
|
|
|
|
| 1 |
import logging
|
| 2 |
from contextlib import asynccontextmanager
|
| 3 |
+
import cloudinary
|
| 4 |
+
import sys
|
| 5 |
|
| 6 |
from fastapi import FastAPI
|
| 7 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 10 |
from .database import get_collection, test_connection
|
| 11 |
from .routers import auth, incidents
|
| 12 |
|
| 13 |
+
# Configure logging
|
| 14 |
+
logging.basicConfig(
|
| 15 |
+
level=logging.INFO,
|
| 16 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 17 |
+
handlers=[
|
| 18 |
+
logging.StreamHandler(sys.stdout)
|
| 19 |
+
]
|
| 20 |
+
)
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
|
| 23 |
|
|
|
|
| 45 |
@asynccontextmanager
|
| 46 |
async def lifespan(app: FastAPI):
|
| 47 |
# Startup
|
| 48 |
+
settings = get_settings()
|
| 49 |
+
|
| 50 |
+
# Initialize Cloudinary
|
| 51 |
+
try:
|
| 52 |
+
# Use environment variables from settings
|
| 53 |
+
cloudinary.config(
|
| 54 |
+
cloud_name=settings.cloudinary_cloud_name,
|
| 55 |
+
api_key=settings.cloudinary_api_key,
|
| 56 |
+
api_secret=settings.cloudinary_api_secret,
|
| 57 |
+
secure=True
|
| 58 |
+
)
|
| 59 |
+
logger.info("Cloudinary initialized successfully with cloud name: %s", settings.cloudinary_cloud_name)
|
| 60 |
+
except Exception as e:
|
| 61 |
+
logger.error(f"Cloudinary initialization failed: {e}")
|
| 62 |
+
|
| 63 |
await setup_database_indexes()
|
| 64 |
yield
|
| 65 |
# Shutdown (if needed in future)
|
app/routers/__pycache__/incidents.cpython-311.pyc
CHANGED
|
Binary files a/app/routers/__pycache__/incidents.cpython-311.pyc and b/app/routers/__pycache__/incidents.cpython-311.pyc differ
|
|
|
app/routers/incidents.py
CHANGED
|
@@ -45,7 +45,16 @@ async def classify_incident_report(
|
|
| 45 |
incident_class, severity = classification_result
|
| 46 |
confidence_scores = None
|
| 47 |
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
document = {
|
| 51 |
"name": name,
|
|
|
|
| 45 |
incident_class, severity = classification_result
|
| 46 |
confidence_scores = None
|
| 47 |
|
| 48 |
+
# Upload image to Cloudinary
|
| 49 |
+
image_path = None
|
| 50 |
+
if image:
|
| 51 |
+
image_path = await store_image(image)
|
| 52 |
+
if not image_path:
|
| 53 |
+
# If Cloudinary upload fails, raise an error since we're not using local fallback
|
| 54 |
+
raise HTTPException(
|
| 55 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 56 |
+
detail="Failed to upload image to cloud storage"
|
| 57 |
+
)
|
| 58 |
|
| 59 |
document = {
|
| 60 |
"name": name,
|
app/services/fallback_storage.py
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Fallback storage service for when Cloudinary uploads fail.
|
| 3 |
+
This provides a more controlled way to handle local storage fallback.
|
| 4 |
+
"""
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from typing import Optional
|
| 7 |
+
import logging
|
| 8 |
+
from uuid import uuid4
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
# Define fallback upload directories
|
| 13 |
+
FALLBACK_PATHS = [
|
| 14 |
+
Path(__file__).resolve().parent.parent / "uploads",
|
| 15 |
+
Path("/tmp/uploads"),
|
| 16 |
+
Path("/app/uploads")
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
# Find a usable directory
|
| 20 |
+
FALLBACK_DIR = None
|
| 21 |
+
for path in FALLBACK_PATHS:
|
| 22 |
+
try:
|
| 23 |
+
path.mkdir(exist_ok=True, parents=True)
|
| 24 |
+
FALLBACK_DIR = path
|
| 25 |
+
logger.info(f"Fallback storage directory ready: {FALLBACK_DIR}")
|
| 26 |
+
break
|
| 27 |
+
except Exception as e:
|
| 28 |
+
logger.warning(f"Cannot use {path} for fallback storage: {e}")
|
| 29 |
+
continue
|
| 30 |
+
|
| 31 |
+
if FALLBACK_DIR is None:
|
| 32 |
+
logger.error("Could not create any fallback storage directory")
|
| 33 |
+
FALLBACK_DIR = Path("/tmp") # Last resort fallback
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
async def store_file_locally(file_content: bytes, original_filename: str) -> Optional[str]:
|
| 37 |
+
"""
|
| 38 |
+
Store a file in the local filesystem as a fallback when cloud storage fails
|
| 39 |
+
|
| 40 |
+
Args:
|
| 41 |
+
file_content: The binary content of the file
|
| 42 |
+
original_filename: The original filename
|
| 43 |
+
|
| 44 |
+
Returns:
|
| 45 |
+
Relative path to the stored file or None if storage failed
|
| 46 |
+
"""
|
| 47 |
+
try:
|
| 48 |
+
# Create a unique filename
|
| 49 |
+
file_extension = Path(original_filename).suffix
|
| 50 |
+
filename = f"{uuid4().hex}{file_extension}"
|
| 51 |
+
file_path = FALLBACK_DIR / filename
|
| 52 |
+
|
| 53 |
+
# Write the file
|
| 54 |
+
file_path.write_bytes(file_content)
|
| 55 |
+
logger.info(f"File saved to fallback storage: {file_path}")
|
| 56 |
+
|
| 57 |
+
# Return a relative path that can be used in URLs
|
| 58 |
+
return str(Path("uploads") / filename)
|
| 59 |
+
except Exception as e:
|
| 60 |
+
logger.error(f"Failed to save file to fallback storage: {e}")
|
| 61 |
+
return None
|
app/services/incidents.py
CHANGED
|
@@ -2,38 +2,18 @@ from pathlib import Path
|
|
| 2 |
from typing import Optional
|
| 3 |
from uuid import uuid4
|
| 4 |
import logging
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
from ..database import get_collection
|
|
|
|
| 7 |
|
| 8 |
logger = logging.getLogger(__name__)
|
| 9 |
|
| 10 |
INCIDENTS_COLLECTION = "incidents"
|
| 11 |
|
| 12 |
-
# Try multiple upload directory locations
|
| 13 |
-
upload_paths = [
|
| 14 |
-
Path(__file__).resolve().parent.parent / "uploads",
|
| 15 |
-
Path("/tmp/uploads"),
|
| 16 |
-
Path("/app/uploads")
|
| 17 |
-
]
|
| 18 |
-
|
| 19 |
-
UPLOAD_DIR = None
|
| 20 |
-
for path in upload_paths:
|
| 21 |
-
try:
|
| 22 |
-
path.mkdir(exist_ok=True, parents=True)
|
| 23 |
-
UPLOAD_DIR = path
|
| 24 |
-
logger.info(f"Upload directory ready: {UPLOAD_DIR}")
|
| 25 |
-
break
|
| 26 |
-
except PermissionError:
|
| 27 |
-
logger.warning(f"Cannot create upload directory at {path}")
|
| 28 |
-
continue
|
| 29 |
-
except Exception as e:
|
| 30 |
-
logger.warning(f"Error creating upload directory at {path}: {e}")
|
| 31 |
-
continue
|
| 32 |
-
|
| 33 |
-
if UPLOAD_DIR is None:
|
| 34 |
-
logger.error("Could not create any upload directory")
|
| 35 |
-
UPLOAD_DIR = Path("/tmp") # Fallback to /tmp
|
| 36 |
-
|
| 37 |
|
| 38 |
async def save_incident_document(document: dict) -> dict:
|
| 39 |
collection = get_collection(INCIDENTS_COLLECTION)
|
|
@@ -57,16 +37,54 @@ async def get_all_incidents() -> list:
|
|
| 57 |
|
| 58 |
|
| 59 |
async def store_image(upload_file) -> Optional[str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
if upload_file is None:
|
| 61 |
return None
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from typing import Optional
|
| 3 |
from uuid import uuid4
|
| 4 |
import logging
|
| 5 |
+
import cloudinary
|
| 6 |
+
import cloudinary.uploader
|
| 7 |
+
import tempfile
|
| 8 |
+
import os
|
| 9 |
|
| 10 |
from ..database import get_collection
|
| 11 |
+
from ..config import get_settings
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
| 14 |
|
| 15 |
INCIDENTS_COLLECTION = "incidents"
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
async def save_incident_document(document: dict) -> dict:
|
| 19 |
collection = get_collection(INCIDENTS_COLLECTION)
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
async def store_image(upload_file) -> Optional[str]:
|
| 40 |
+
"""
|
| 41 |
+
Store an uploaded image using Cloudinary only.
|
| 42 |
+
No local fallback - if Cloudinary upload fails, the function will return None.
|
| 43 |
+
"""
|
| 44 |
if upload_file is None:
|
| 45 |
return None
|
| 46 |
+
|
| 47 |
+
temp_path = None
|
| 48 |
+
try:
|
| 49 |
+
logger.info(f"Starting image upload to Cloudinary for file: {upload_file.filename}")
|
| 50 |
+
|
| 51 |
+
# Read the file content
|
| 52 |
+
contents = await upload_file.read()
|
| 53 |
+
logger.info(f"Read {len(contents)} bytes from uploaded file")
|
| 54 |
+
|
| 55 |
+
# Create a temporary file to use with Cloudinary
|
| 56 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=Path(upload_file.filename).suffix) as temp_file:
|
| 57 |
+
temp_path = temp_file.name
|
| 58 |
+
temp_file.write(contents)
|
| 59 |
+
logger.info(f"Wrote content to temporary file: {temp_path}")
|
| 60 |
+
|
| 61 |
+
# Upload to Cloudinary - ensuring we use the right resource type
|
| 62 |
+
logger.info("Starting Cloudinary upload...")
|
| 63 |
+
|
| 64 |
+
# Add resource_type=auto to handle different file types correctly
|
| 65 |
+
upload_result = cloudinary.uploader.upload(
|
| 66 |
+
temp_path,
|
| 67 |
+
folder="marine_guard_incidents",
|
| 68 |
+
resource_type="auto"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# Return the Cloudinary URL
|
| 72 |
+
cloudinary_url = upload_result["secure_url"]
|
| 73 |
+
logger.info(f"Cloudinary upload successful. URL: {cloudinary_url}")
|
| 74 |
+
|
| 75 |
+
await upload_file.close()
|
| 76 |
+
return cloudinary_url
|
| 77 |
+
|
| 78 |
+
except Exception as e:
|
| 79 |
+
logger.error(f"Failed to upload image to Cloudinary: {e}", exc_info=True)
|
| 80 |
+
if upload_file:
|
| 81 |
+
await upload_file.close()
|
| 82 |
+
return None
|
| 83 |
+
finally:
|
| 84 |
+
# Always clean up the temporary file
|
| 85 |
+
if temp_path and os.path.exists(temp_path):
|
| 86 |
+
try:
|
| 87 |
+
os.unlink(temp_path)
|
| 88 |
+
logger.info(f"Deleted temporary file: {temp_path}")
|
| 89 |
+
except Exception as e:
|
| 90 |
+
logger.error(f"Failed to delete temporary file: {e}")
|
app/uploads/222544d2e2e44e8d812becab341d74ef.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
This is a test image file for the API
|
app/uploads/becb6a146b764fd6a50cb2b1305ccba6.jpg
ADDED
|
create_test_user.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
# API endpoint for user signup
|
| 5 |
+
API_URL = "http://localhost:8000/api/auth/signup"
|
| 6 |
+
|
| 7 |
+
def create_test_user():
|
| 8 |
+
"""Create a test user for API testing"""
|
| 9 |
+
try:
|
| 10 |
+
# Prepare the user data
|
| 11 |
+
user_data = {
|
| 12 |
+
"email": "testuser@example.com",
|
| 13 |
+
"password": "Password123!",
|
| 14 |
+
"display_name": "Test User",
|
| 15 |
+
"organization": "Test Org",
|
| 16 |
+
"role": "citizen"
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
# Make the API request
|
| 20 |
+
print(f"Creating test user at {API_URL}")
|
| 21 |
+
response = requests.post(
|
| 22 |
+
API_URL,
|
| 23 |
+
data=json.dumps(user_data),
|
| 24 |
+
headers={"Content-Type": "application/json"}
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
# Process the response
|
| 28 |
+
if response.status_code == 200:
|
| 29 |
+
result = response.json()
|
| 30 |
+
print("User created successfully!")
|
| 31 |
+
print(f"Token: {result.get('access_token')[:15]}...")
|
| 32 |
+
print(f"User ID: {result.get('user', {}).get('id')}")
|
| 33 |
+
return result
|
| 34 |
+
else:
|
| 35 |
+
print(f"API request failed with status code {response.status_code}")
|
| 36 |
+
print(f"Response: {response.text}")
|
| 37 |
+
return None
|
| 38 |
+
except Exception as e:
|
| 39 |
+
print(f"Error creating test user: {e}")
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
if __name__ == "__main__":
|
| 43 |
+
print("Creating test user for Marine Guard API")
|
| 44 |
+
create_test_user()
|
debug_cloudinary.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import cloudinary
|
| 3 |
+
import cloudinary.uploader
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
# Configure logging
|
| 8 |
+
logging.basicConfig(level=logging.INFO)
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
# Load environment variables
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
# Configure Cloudinary
|
| 15 |
+
cloudinary.config(
|
| 16 |
+
cloud_name=os.getenv("CLOUDINARY_CLOUD_NAME"),
|
| 17 |
+
api_key=os.getenv("CLOUDINARY_API_KEY"),
|
| 18 |
+
api_secret=os.getenv("CLOUDINARY_API_SECRET"),
|
| 19 |
+
secure=True
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
def test_cloudinary_config():
|
| 23 |
+
print("Cloudinary configuration:")
|
| 24 |
+
print(f"Cloud name: {cloudinary.config().cloud_name}")
|
| 25 |
+
print(f"API key: {cloudinary.config().api_key}")
|
| 26 |
+
print(f"API secret: {'*' * len(cloudinary.config().api_secret) if cloudinary.config().api_secret else 'Not set'}")
|
| 27 |
+
|
| 28 |
+
def upload_test_image():
|
| 29 |
+
try:
|
| 30 |
+
# Create a test file
|
| 31 |
+
test_file_path = "cloudinary_test.txt"
|
| 32 |
+
with open(test_file_path, "w") as f:
|
| 33 |
+
f.write("Test content for Cloudinary upload")
|
| 34 |
+
|
| 35 |
+
# Upload to Cloudinary
|
| 36 |
+
print("Uploading test file to Cloudinary...")
|
| 37 |
+
upload_result = cloudinary.uploader.upload(
|
| 38 |
+
test_file_path,
|
| 39 |
+
folder="marine_guard_test",
|
| 40 |
+
resource_type="auto" # Let Cloudinary auto-detect the resource type
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
print(f"Upload successful! URL: {upload_result.get('secure_url')}")
|
| 44 |
+
print(f"Public ID: {upload_result.get('public_id')}")
|
| 45 |
+
print(f"Resource type: {upload_result.get('resource_type')}")
|
| 46 |
+
|
| 47 |
+
# Clean up the test file
|
| 48 |
+
os.unlink(test_file_path)
|
| 49 |
+
print(f"Deleted local test file: {test_file_path}")
|
| 50 |
+
|
| 51 |
+
return upload_result
|
| 52 |
+
|
| 53 |
+
except Exception as e:
|
| 54 |
+
logger.error(f"Error uploading to Cloudinary: {e}", exc_info=True)
|
| 55 |
+
if os.path.exists(test_file_path):
|
| 56 |
+
os.unlink(test_file_path)
|
| 57 |
+
return None
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
print("Testing detailed Cloudinary configuration and upload")
|
| 61 |
+
test_cloudinary_config()
|
| 62 |
+
result = upload_test_image()
|
| 63 |
+
if result:
|
| 64 |
+
print("β
Cloudinary upload test PASSED")
|
| 65 |
+
else:
|
| 66 |
+
print("β Cloudinary upload test FAILED")
|
list_incidents.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
# API endpoint for incident listing
|
| 5 |
+
API_URL = "http://localhost:8000/api/incidents/list"
|
| 6 |
+
|
| 7 |
+
def get_token():
|
| 8 |
+
"""Get a JWT token by authenticating with the API"""
|
| 9 |
+
try:
|
| 10 |
+
login_url = "http://localhost:8000/api/auth/login"
|
| 11 |
+
login_data = {
|
| 12 |
+
"email": "testuser@example.com",
|
| 13 |
+
"password": "Password123!"
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
response = requests.post(login_url, data=json.dumps(login_data),
|
| 17 |
+
headers={"Content-Type": "application/json"})
|
| 18 |
+
|
| 19 |
+
if response.status_code == 200:
|
| 20 |
+
token = response.json().get("access_token")
|
| 21 |
+
print(f"Authentication successful.")
|
| 22 |
+
return token
|
| 23 |
+
else:
|
| 24 |
+
print(f"Authentication failed with status code {response.status_code}")
|
| 25 |
+
print(response.text)
|
| 26 |
+
return None
|
| 27 |
+
except Exception as e:
|
| 28 |
+
print(f"Error getting token: {e}")
|
| 29 |
+
return None
|
| 30 |
+
|
| 31 |
+
def list_incidents():
|
| 32 |
+
"""List all incidents for the authenticated user"""
|
| 33 |
+
token = get_token()
|
| 34 |
+
if not token:
|
| 35 |
+
print("No token available, cannot proceed")
|
| 36 |
+
return
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
print(f"Fetching incidents from {API_URL}")
|
| 40 |
+
response = requests.get(
|
| 41 |
+
API_URL,
|
| 42 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
if response.status_code == 200:
|
| 46 |
+
incidents = response.json().get("incidents", [])
|
| 47 |
+
print(f"Found {len(incidents)} incidents")
|
| 48 |
+
|
| 49 |
+
# Print details of each incident
|
| 50 |
+
for i, incident in enumerate(incidents):
|
| 51 |
+
print(f"\nIncident {i + 1}:")
|
| 52 |
+
print(f" ID: {incident.get('id')}")
|
| 53 |
+
print(f" Type: {incident.get('incident_class')}")
|
| 54 |
+
print(f" Severity: {incident.get('severity')}")
|
| 55 |
+
print(f" Created: {incident.get('created_at')}")
|
| 56 |
+
|
| 57 |
+
# Check if image path is a Cloudinary URL
|
| 58 |
+
image_path = incident.get('image_path')
|
| 59 |
+
if image_path:
|
| 60 |
+
if 'cloudinary.com' in image_path:
|
| 61 |
+
print(f" Image: Cloudinary URL - {image_path}")
|
| 62 |
+
else:
|
| 63 |
+
print(f" Image: Local path - {image_path}")
|
| 64 |
+
else:
|
| 65 |
+
print(" No image attached")
|
| 66 |
+
|
| 67 |
+
return incidents
|
| 68 |
+
else:
|
| 69 |
+
print(f"API request failed with status code {response.status_code}")
|
| 70 |
+
print(f"Response: {response.text}")
|
| 71 |
+
return None
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"Error listing incidents: {e}")
|
| 74 |
+
return None
|
| 75 |
+
|
| 76 |
+
if __name__ == "__main__":
|
| 77 |
+
print("Listing incidents from Marine Guard API")
|
| 78 |
+
list_incidents()
|
test_api_cloudinary.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
import sys
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
# Load environment variables from .env file
|
| 9 |
+
load_dotenv()
|
| 10 |
+
|
| 11 |
+
# API endpoint for incident classification
|
| 12 |
+
API_URL = "http://localhost:8000/api/incidents/classify"
|
| 13 |
+
|
| 14 |
+
# JWT token (replace with your actual token)
|
| 15 |
+
JWT_TOKEN = "" # Set this to test with your token
|
| 16 |
+
|
| 17 |
+
def get_token():
|
| 18 |
+
"""Get a JWT token by authenticating with the API"""
|
| 19 |
+
try:
|
| 20 |
+
login_url = "http://localhost:8000/api/auth/login"
|
| 21 |
+
login_data = {
|
| 22 |
+
"email": "testuser@example.com", # Using our newly created test user
|
| 23 |
+
"password": "Password123!" # Using the password for the test user
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
response = requests.post(login_url, data=json.dumps(login_data),
|
| 27 |
+
headers={"Content-Type": "application/json"})
|
| 28 |
+
|
| 29 |
+
if response.status_code == 200:
|
| 30 |
+
token = response.json().get("access_token")
|
| 31 |
+
print(f"Authentication successful. Token: {token[:15]}..." if token else "No token in response")
|
| 32 |
+
return token
|
| 33 |
+
else:
|
| 34 |
+
print(f"Authentication failed with status code {response.status_code}")
|
| 35 |
+
print(response.text)
|
| 36 |
+
return None
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"Error getting token: {e}")
|
| 39 |
+
return None
|
| 40 |
+
|
| 41 |
+
def create_test_image():
|
| 42 |
+
"""Create a test image file"""
|
| 43 |
+
# Check if we're in the right directory
|
| 44 |
+
current_dir = Path.cwd()
|
| 45 |
+
print(f"Current directory: {current_dir}")
|
| 46 |
+
|
| 47 |
+
# Create a directory for test files if it doesn't exist
|
| 48 |
+
test_dir = current_dir / "test_files"
|
| 49 |
+
test_dir.mkdir(exist_ok=True)
|
| 50 |
+
|
| 51 |
+
# Path to the test image file
|
| 52 |
+
test_image_path = test_dir / "test_image.txt"
|
| 53 |
+
|
| 54 |
+
# Write some content to the file
|
| 55 |
+
with open(test_image_path, "w") as f:
|
| 56 |
+
f.write("This is a test image file for the API")
|
| 57 |
+
|
| 58 |
+
print(f"Created test file at {test_image_path}")
|
| 59 |
+
return test_image_path
|
| 60 |
+
|
| 61 |
+
def test_report_incident_with_image():
|
| 62 |
+
"""Test reporting an incident with an image"""
|
| 63 |
+
|
| 64 |
+
# Create a test image
|
| 65 |
+
image_path = create_test_image()
|
| 66 |
+
|
| 67 |
+
# Get a token if not provided
|
| 68 |
+
token = JWT_TOKEN or get_token()
|
| 69 |
+
if not token:
|
| 70 |
+
print("No token available, cannot proceed with test")
|
| 71 |
+
return
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
# Prepare the form data
|
| 75 |
+
form_data = {
|
| 76 |
+
"description": "Test incident with image upload to Cloudinary",
|
| 77 |
+
"name": "Cloudinary Test",
|
| 78 |
+
"latitude": "37.7749",
|
| 79 |
+
"longitude": "-122.4194"
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
# Prepare the file
|
| 83 |
+
with open(image_path, "rb") as image_file:
|
| 84 |
+
files = {"image": ("test_image.txt", image_file, "text/plain")}
|
| 85 |
+
|
| 86 |
+
# Make the API request
|
| 87 |
+
print(f"Sending request to {API_URL}")
|
| 88 |
+
response = requests.post(
|
| 89 |
+
API_URL,
|
| 90 |
+
data=form_data,
|
| 91 |
+
files=files,
|
| 92 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
# Process the response
|
| 96 |
+
if response.status_code == 200:
|
| 97 |
+
print("Incident report successful!")
|
| 98 |
+
print(f"Response: {response.json()}")
|
| 99 |
+
return response.json()
|
| 100 |
+
else:
|
| 101 |
+
print(f"API request failed with status code {response.status_code}")
|
| 102 |
+
print(f"Response: {response.text}")
|
| 103 |
+
return None
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f"Error testing incident report API: {e}")
|
| 106 |
+
return None
|
| 107 |
+
finally:
|
| 108 |
+
# Clean up test image
|
| 109 |
+
try:
|
| 110 |
+
if os.path.exists(image_path):
|
| 111 |
+
os.remove(image_path)
|
| 112 |
+
print(f"Removed test file: {image_path}")
|
| 113 |
+
except Exception as e:
|
| 114 |
+
print(f"Error cleaning up test file: {e}")
|
| 115 |
+
|
| 116 |
+
if __name__ == "__main__":
|
| 117 |
+
print("Testing Marine Guard Incident API with Cloudinary Integration")
|
| 118 |
+
test_report_incident_with_image()
|
test_cloudinary.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import cloudinary
|
| 3 |
+
import cloudinary.uploader
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
# Load environment variables from .env file
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
# Configure Cloudinary
|
| 10 |
+
cloudinary.config(
|
| 11 |
+
cloud_name=os.getenv("CLOUDINARY_CLOUD_NAME"),
|
| 12 |
+
api_key=os.getenv("CLOUDINARY_API_KEY"),
|
| 13 |
+
api_secret=os.getenv("CLOUDINARY_API_SECRET"),
|
| 14 |
+
secure=True
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
def test_cloudinary_upload():
|
| 18 |
+
"""Test Cloudinary upload functionality"""
|
| 19 |
+
try:
|
| 20 |
+
# Try to upload a simple text as a test
|
| 21 |
+
# Create a simple test file
|
| 22 |
+
test_file = "test_image.txt"
|
| 23 |
+
with open(test_file, "w") as f:
|
| 24 |
+
f.write("This is a test file for Cloudinary upload")
|
| 25 |
+
|
| 26 |
+
# Upload to Cloudinary
|
| 27 |
+
print("Uploading to Cloudinary...")
|
| 28 |
+
result = cloudinary.uploader.upload(
|
| 29 |
+
test_file,
|
| 30 |
+
folder="marine_guard_test",
|
| 31 |
+
public_id="test_upload"
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
print(f"Upload successful! URL: {result['secure_url']}")
|
| 35 |
+
print(f"Full result: {result}")
|
| 36 |
+
|
| 37 |
+
# Clean up the test file
|
| 38 |
+
os.remove(test_file)
|
| 39 |
+
return True
|
| 40 |
+
|
| 41 |
+
except Exception as e:
|
| 42 |
+
print(f"Error testing Cloudinary upload: {e}")
|
| 43 |
+
return False
|
| 44 |
+
|
| 45 |
+
if __name__ == "__main__":
|
| 46 |
+
print("Testing Cloudinary configuration")
|
| 47 |
+
print(f"Cloud name: {os.getenv('CLOUDINARY_CLOUD_NAME')}")
|
| 48 |
+
print(f"API key: {os.getenv('CLOUDINARY_API_KEY')}")
|
| 49 |
+
print(f"API secret: {os.getenv('CLOUDINARY_API_SECRET')[:3]}...")
|
| 50 |
+
|
| 51 |
+
test_result = test_cloudinary_upload()
|
| 52 |
+
print(f"Test {'successful' if test_result else 'failed'}")
|
test_cloudinary_improved.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
import random
|
| 7 |
+
|
| 8 |
+
# Load environment variables
|
| 9 |
+
load_dotenv()
|
| 10 |
+
|
| 11 |
+
# API endpoint for incident classification
|
| 12 |
+
API_URL = "http://localhost:8000/api/incidents/classify"
|
| 13 |
+
|
| 14 |
+
def get_token():
|
| 15 |
+
"""Get a JWT token by authenticating with the API"""
|
| 16 |
+
try:
|
| 17 |
+
login_url = "http://localhost:8000/api/auth/login"
|
| 18 |
+
login_data = {
|
| 19 |
+
"email": "testuser@example.com",
|
| 20 |
+
"password": "Password123!"
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
response = requests.post(login_url, data=json.dumps(login_data),
|
| 24 |
+
headers={"Content-Type": "application/json"})
|
| 25 |
+
|
| 26 |
+
if response.status_code == 200:
|
| 27 |
+
token = response.json().get("access_token")
|
| 28 |
+
print(f"Authentication successful. Token: {token[:15]}...")
|
| 29 |
+
return token
|
| 30 |
+
else:
|
| 31 |
+
print(f"Authentication failed with status code {response.status_code}")
|
| 32 |
+
print(response.text)
|
| 33 |
+
return None
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"Error getting token: {e}")
|
| 36 |
+
return None
|
| 37 |
+
|
| 38 |
+
def create_test_image():
|
| 39 |
+
"""Create a test image file - we'll use a .jpg extension but with text content for testing"""
|
| 40 |
+
# Create a directory for test files if it doesn't exist
|
| 41 |
+
test_dir = Path("test_files")
|
| 42 |
+
test_dir.mkdir(exist_ok=True)
|
| 43 |
+
|
| 44 |
+
# Use a .jpg extension to make sure Cloudinary tries to process it properly
|
| 45 |
+
test_image_path = test_dir / f"test_image_{random.randint(1000, 9999)}.jpg"
|
| 46 |
+
|
| 47 |
+
# Write some content to the file
|
| 48 |
+
with open(test_image_path, "w") as f:
|
| 49 |
+
f.write("This is a fake image file for testing Cloudinary uploads")
|
| 50 |
+
|
| 51 |
+
print(f"Created test file at {test_image_path}")
|
| 52 |
+
return test_image_path
|
| 53 |
+
|
| 54 |
+
def report_incident_with_image():
|
| 55 |
+
"""Report an incident with an image via the API"""
|
| 56 |
+
# Create a test image
|
| 57 |
+
image_path = create_test_image()
|
| 58 |
+
|
| 59 |
+
# Get a token
|
| 60 |
+
token = get_token()
|
| 61 |
+
if not token:
|
| 62 |
+
print("No token available, cannot proceed with test")
|
| 63 |
+
return False
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
# Prepare the form data
|
| 67 |
+
form_data = {
|
| 68 |
+
"description": "Testing Cloudinary integration with improved logging",
|
| 69 |
+
"name": "Cloudinary Test",
|
| 70 |
+
"latitude": "37.7749",
|
| 71 |
+
"longitude": "-122.4194"
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
# Prepare the file
|
| 75 |
+
with open(image_path, "rb") as image_file:
|
| 76 |
+
files = {"image": (image_path.name, image_file, "image/jpeg")}
|
| 77 |
+
|
| 78 |
+
# Make the API request
|
| 79 |
+
print(f"Sending request to {API_URL}")
|
| 80 |
+
response = requests.post(
|
| 81 |
+
API_URL,
|
| 82 |
+
data=form_data,
|
| 83 |
+
files=files,
|
| 84 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
# Process the response
|
| 88 |
+
if response.status_code == 200:
|
| 89 |
+
print("Incident report successful!")
|
| 90 |
+
print(f"Response: {response.json()}")
|
| 91 |
+
|
| 92 |
+
# Get the incident ID from the response
|
| 93 |
+
incident_id = response.json().get("incident_id")
|
| 94 |
+
print(f"Created incident with ID: {incident_id}")
|
| 95 |
+
|
| 96 |
+
# Now check the incident details to see if Cloudinary URL was used
|
| 97 |
+
if incident_id:
|
| 98 |
+
return check_incident_image(token, incident_id)
|
| 99 |
+
return True
|
| 100 |
+
else:
|
| 101 |
+
print(f"API request failed with status code {response.status_code}")
|
| 102 |
+
print(f"Response: {response.text}")
|
| 103 |
+
return False
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f"Error testing incident report API: {e}")
|
| 106 |
+
return False
|
| 107 |
+
finally:
|
| 108 |
+
# Clean up test image
|
| 109 |
+
try:
|
| 110 |
+
if image_path.exists():
|
| 111 |
+
image_path.unlink()
|
| 112 |
+
print(f"Removed test file: {image_path}")
|
| 113 |
+
except Exception as e:
|
| 114 |
+
print(f"Error cleaning up test file: {e}")
|
| 115 |
+
|
| 116 |
+
def check_incident_image(token, incident_id):
|
| 117 |
+
"""Check if the incident has a Cloudinary image URL"""
|
| 118 |
+
try:
|
| 119 |
+
# Get the list of incidents
|
| 120 |
+
list_url = "http://localhost:8000/api/incidents/list"
|
| 121 |
+
print(f"Fetching incidents to check for Cloudinary URL...")
|
| 122 |
+
|
| 123 |
+
response = requests.get(
|
| 124 |
+
list_url,
|
| 125 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
if response.status_code == 200:
|
| 129 |
+
incidents = response.json().get("incidents", [])
|
| 130 |
+
|
| 131 |
+
# Find our incident
|
| 132 |
+
for incident in incidents:
|
| 133 |
+
if incident.get("id") == incident_id:
|
| 134 |
+
image_path = incident.get("image_path")
|
| 135 |
+
print(f"Found incident. Image path: {image_path}")
|
| 136 |
+
|
| 137 |
+
if image_path and "cloudinary.com" in image_path:
|
| 138 |
+
print("β
SUCCESS: Image was uploaded to Cloudinary!")
|
| 139 |
+
return True
|
| 140 |
+
else:
|
| 141 |
+
print("β FAILED: Image was not uploaded to Cloudinary")
|
| 142 |
+
return False
|
| 143 |
+
|
| 144 |
+
print(f"Couldn't find incident with ID: {incident_id}")
|
| 145 |
+
return False
|
| 146 |
+
else:
|
| 147 |
+
print(f"Failed to fetch incidents list: {response.status_code}")
|
| 148 |
+
print(response.text)
|
| 149 |
+
return False
|
| 150 |
+
except Exception as e:
|
| 151 |
+
print(f"Error checking incident: {e}")
|
| 152 |
+
return False
|
| 153 |
+
|
| 154 |
+
if __name__ == "__main__":
|
| 155 |
+
print("Testing Marine Guard incident reporting with Cloudinary integration")
|
| 156 |
+
success = report_incident_with_image()
|
| 157 |
+
print(f"Test {'PASSED' if success else 'FAILED'}")
|
test_cloudinary_mandatory.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test script for Cloudinary-only image storage in the Marine Guard API.
|
| 3 |
+
This script ensures that:
|
| 4 |
+
1. We can authenticate with the API
|
| 5 |
+
2. We can upload an image which gets stored in Cloudinary
|
| 6 |
+
3. We can retrieve the incident which contains the Cloudinary URL
|
| 7 |
+
"""
|
| 8 |
+
import os
|
| 9 |
+
import requests
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
import random
|
| 13 |
+
import time
|
| 14 |
+
from dotenv import load_dotenv
|
| 15 |
+
|
| 16 |
+
# Load environment variables
|
| 17 |
+
load_dotenv()
|
| 18 |
+
|
| 19 |
+
# API endpoint for incident classification
|
| 20 |
+
API_URL = "http://localhost:8000/api/incidents/classify"
|
| 21 |
+
API_LIST_URL = "http://localhost:8000/api/incidents/list"
|
| 22 |
+
API_LOGIN_URL = "http://localhost:8000/api/auth/login"
|
| 23 |
+
|
| 24 |
+
def authenticate():
|
| 25 |
+
"""Authenticate with the API and get a JWT token"""
|
| 26 |
+
try:
|
| 27 |
+
login_data = {
|
| 28 |
+
"email": "testuser@example.com",
|
| 29 |
+
"password": "Password123!"
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
print(f"Authenticating with the API at {API_LOGIN_URL}...")
|
| 33 |
+
response = requests.post(API_LOGIN_URL, json=login_data)
|
| 34 |
+
|
| 35 |
+
if response.status_code == 200:
|
| 36 |
+
token = response.json().get("access_token")
|
| 37 |
+
print("β
Authentication successful!")
|
| 38 |
+
return token
|
| 39 |
+
else:
|
| 40 |
+
print(f"β Authentication failed with status code {response.status_code}")
|
| 41 |
+
print(response.text)
|
| 42 |
+
return None
|
| 43 |
+
except Exception as e:
|
| 44 |
+
print(f"β Error during authentication: {e}")
|
| 45 |
+
return None
|
| 46 |
+
|
| 47 |
+
def create_test_file():
|
| 48 |
+
"""Create a test file to upload"""
|
| 49 |
+
test_dir = Path("test_files")
|
| 50 |
+
test_dir.mkdir(exist_ok=True)
|
| 51 |
+
|
| 52 |
+
# Use a .txt extension since we just need any file that Cloudinary can handle
|
| 53 |
+
file_path = test_dir / f"cloudinary_test_{random.randint(1000, 9999)}.txt"
|
| 54 |
+
|
| 55 |
+
# Write simple text content
|
| 56 |
+
with open(file_path, "w") as f:
|
| 57 |
+
f.write("This is a test file for Cloudinary upload in Marine Guard API")
|
| 58 |
+
|
| 59 |
+
print(f"Created test file at: {file_path}")
|
| 60 |
+
return file_path
|
| 61 |
+
|
| 62 |
+
def report_incident():
|
| 63 |
+
"""Report an incident with an image via the API"""
|
| 64 |
+
token = authenticate()
|
| 65 |
+
if not token:
|
| 66 |
+
print("Cannot proceed with test - authentication failed")
|
| 67 |
+
return False
|
| 68 |
+
|
| 69 |
+
# Create a test image file
|
| 70 |
+
image_path = create_test_file()
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
# Prepare the form data
|
| 74 |
+
form_data = {
|
| 75 |
+
"description": "Cloudinary-only storage test with JPG image",
|
| 76 |
+
"name": "Cloudinary Storage Test",
|
| 77 |
+
"latitude": "42.3601",
|
| 78 |
+
"longitude": "-71.0589"
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
# Prepare the file for upload
|
| 82 |
+
with open(image_path, "rb") as image_file:
|
| 83 |
+
files = {"image": (image_path.name, image_file, "text/plain")}
|
| 84 |
+
|
| 85 |
+
# Make the API request
|
| 86 |
+
print(f"Reporting incident to {API_URL}...")
|
| 87 |
+
response = requests.post(
|
| 88 |
+
API_URL,
|
| 89 |
+
data=form_data,
|
| 90 |
+
files=files,
|
| 91 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
# Check the response
|
| 95 |
+
if response.status_code == 200:
|
| 96 |
+
result = response.json()
|
| 97 |
+
incident_id = result.get("incident_id")
|
| 98 |
+
print(f"β
Incident reported successfully! ID: {incident_id}")
|
| 99 |
+
|
| 100 |
+
# Verify that the image was uploaded to Cloudinary
|
| 101 |
+
return verify_cloudinary_upload(token, incident_id)
|
| 102 |
+
else:
|
| 103 |
+
print(f"β Incident reporting failed with status code {response.status_code}")
|
| 104 |
+
print(f"Error details: {response.text}")
|
| 105 |
+
return False
|
| 106 |
+
except Exception as e:
|
| 107 |
+
print(f"β Error reporting incident: {e}")
|
| 108 |
+
return False
|
| 109 |
+
finally:
|
| 110 |
+
# Clean up the test file
|
| 111 |
+
if os.path.exists(image_path):
|
| 112 |
+
os.unlink(image_path)
|
| 113 |
+
print(f"Deleted test file: {image_path}")
|
| 114 |
+
|
| 115 |
+
def verify_cloudinary_upload(token, incident_id):
|
| 116 |
+
"""Verify that the incident has a Cloudinary URL for its image"""
|
| 117 |
+
try:
|
| 118 |
+
print(f"Fetching incident details to verify Cloudinary URL...")
|
| 119 |
+
# Short delay to ensure the incident is saved in the database
|
| 120 |
+
time.sleep(1)
|
| 121 |
+
|
| 122 |
+
response = requests.get(
|
| 123 |
+
API_LIST_URL,
|
| 124 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
if response.status_code == 200:
|
| 128 |
+
incidents = response.json().get("incidents", [])
|
| 129 |
+
for incident in incidents:
|
| 130 |
+
if incident.get("id") == incident_id:
|
| 131 |
+
image_path = incident.get("image_path")
|
| 132 |
+
|
| 133 |
+
if image_path and "cloudinary.com" in image_path:
|
| 134 |
+
print(f"β
SUCCESS: Image was uploaded to Cloudinary!")
|
| 135 |
+
print(f"Cloudinary URL: {image_path}")
|
| 136 |
+
return True
|
| 137 |
+
else:
|
| 138 |
+
print(f"β FAILED: Image does not have a Cloudinary URL")
|
| 139 |
+
print(f"Image path: {image_path}")
|
| 140 |
+
return False
|
| 141 |
+
|
| 142 |
+
print(f"β FAILED: Could not find incident with ID {incident_id}")
|
| 143 |
+
return False
|
| 144 |
+
else:
|
| 145 |
+
print(f"β FAILED: Could not fetch incidents list. Status: {response.status_code}")
|
| 146 |
+
print(response.text)
|
| 147 |
+
return False
|
| 148 |
+
except Exception as e:
|
| 149 |
+
print(f"β Error verifying Cloudinary upload: {e}")
|
| 150 |
+
return False
|
| 151 |
+
|
| 152 |
+
if __name__ == "__main__":
|
| 153 |
+
print("=" * 60)
|
| 154 |
+
print("MARINE GUARD API - CLOUDINARY-ONLY STORAGE TEST")
|
| 155 |
+
print("=" * 60)
|
| 156 |
+
|
| 157 |
+
success = report_incident()
|
| 158 |
+
|
| 159 |
+
print("\nTEST SUMMARY")
|
| 160 |
+
print("=" * 60)
|
| 161 |
+
print(f"Cloudinary storage test: {'β
PASSED' if success else 'β FAILED'}")
|
| 162 |
+
print("=" * 60)
|
test_files/cloudinary_test_7574.jpg
ADDED
|
test_image.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
This is a test file for Cloudinary upload
|