diff --git a/extensions/photos.py b/extensions/photos.py index e3c2aae..0d78c0f 100644 --- a/extensions/photos.py +++ b/extensions/photos.py @@ -1,10 +1,12 @@ import re import pickle from secrets import token_urlsafe +from typing import List, Union from magic import Magic from datetime import datetime, timedelta from os import makedirs, path, remove, system from classes.models import Photo, SearchResults +from modules.exif_reader import extract_location from modules.hasher import get_phash, get_duplicates from modules.scheduler import scheduler from modules.security import User, get_current_active_user @@ -15,7 +17,7 @@ from bson.errors import InvalidId from fastapi import HTTPException, UploadFile, Security from fastapi.responses import UJSONResponse, Response -from starlette.status import HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_404_NOT_FOUND, HTTP_409_CONFLICT +from starlette.status import HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_404_NOT_FOUND, HTTP_409_CONFLICT, HTTP_422_UNPROCESSABLE_ENTITY from modules.utils import logWrite @@ -72,7 +74,8 @@ async def photo_upload(file: UploadFile, album: str, ignore_duplicates: bool = F status_code=HTTP_409_CONFLICT ) - uploaded = col_photos.insert_one( {"user": current_user.user, "album": album, "hash": file_hash, "filename": filename} ) + coords = extract_location(path.join("data", "users", current_user.user, "albums", album, filename)) + uploaded = col_photos.insert_one( {"user": current_user.user, "album": album, "hash": file_hash, "filename": filename, "location": [coords["lng"], coords["lat"], coords["alt"]]} ) if compress is True: scheduler.add_job(compress_image, trigger="date", run_date=datetime.now()+timedelta(seconds=1), args=[path.join("data", "users", current_user.user, "albums", album, filename)]) @@ -124,7 +127,7 @@ async def photo_delete(id: str, current_user: User = Security(get_current_active return Response(status_code=HTTP_204_NO_CONTENT) @app.get("/albums/{album}/photos", response_class=UJSONResponse, response_model=SearchResults, description="Find a photo by filename") -async def photo_find(q: str, album: str, page: int = 1, page_size: int = 100, current_user: User = Security(get_current_active_user, scopes=["photos.list"])): +async def photo_find(album: str, q: Union[str, None] = None, page: int = 1, page_size: int = 100, lat: Union[float, None] = None, lng: Union[float, None] = None, radius: Union[int, None] = None, current_user: User = Security(get_current_active_user, scopes=["photos.list"])): if col_albums.find_one( {"user": current_user.user, "name": album} ) is None: return HTTPException(status_code=HTTP_404_NOT_FOUND, detail=f"Provided album '{album}' does not exist.") @@ -134,12 +137,24 @@ async def photo_find(q: str, album: str, page: int = 1, page_size: int = 100, cu output = {"results": []} skip = (page-1)*page_size - images = list(col_photos.find({"user": current_user.user, "album": album, "filename": re.compile(q)}, limit=page_size, skip=skip)) + + radius = 5000 if radius is None else radius + + if (lat is not None) and (lng is not None): + db_query = {"user": current_user.user, "album": album, "location": { "$nearSphere": {"$geometry": {"type": "Point", "coordinates": [lng, lat]}, "$maxDistance": radius} } } + db_query_count = {"user": current_user.user, "album": album, "location": { "$geoWithin": { "$centerSphere": [ [lng, lat], radius ] } } } + elif q is None: + raise HTTPException(status_code=HTTP_422_UNPROCESSABLE_ENTITY, detail="You must provide query or coordinates to look for photos") + else: + db_query = {"user": current_user.user, "album": album, "filename": re.compile(q)} + db_query_count = {"user": current_user.user, "album": album, "filename": re.compile(q)} + + images = list(col_photos.find(db_query, limit=page_size, skip=skip)) for image in images: output["results"].append({"id": image["_id"].__str__(), "filename": image["filename"]}) - if col_photos.count_documents( {"user": current_user.user, "album": album, "filename": re.compile(q)} ) > page*page_size: + if col_photos.count_documents( db_query_count ) > page*page_size: token = str(token_urlsafe(32)) col_tokens.insert_one( {"token": token, "query": q, "album": album, "page": page+1, "page_size": page_size, "user": pickle.dumps(current_user)} ) output["next_page"] = f"/albums/{album}/photos/token?token={token}" # type: ignore diff --git a/modules/database.py b/modules/database.py index 1f4ab56..b36d6f7 100644 --- a/modules/database.py +++ b/modules/database.py @@ -1,5 +1,5 @@ from modules.utils import configGet -from pymongo import MongoClient +from pymongo import MongoClient, GEOSPHERE db_config = configGet("database") @@ -32,4 +32,6 @@ col_users = db.get_collection("users") col_albums = db.get_collection("albums") col_photos = db.get_collection("photos") col_videos = db.get_collection("videos") -col_tokens = db.get_collection("tokens") \ No newline at end of file +col_tokens = db.get_collection("tokens") + +col_photos.create_index([("location", GEOSPHERE)]) \ No newline at end of file diff --git a/modules/exif_reader.py b/modules/exif_reader.py index 04c7972..8f61c43 100644 --- a/modules/exif_reader.py +++ b/modules/exif_reader.py @@ -13,7 +13,7 @@ def decimal_coords(coords: float, ref: str) -> float: decimal_degrees = coords[0] + coords[1] / 60 + coords[2] / 3600 if ref == "S" or ref == "W": decimal_degrees = -decimal_degrees - return decimal_degrees + return round(decimal_degrees, 5) def extract_location(filepath: str) -> dict: """Get location data from image @@ -22,13 +22,13 @@ def extract_location(filepath: str) -> dict: * filepath (`str`): Path to file location ### Returns: - * dict: `{ "latitude": float, "longitude": float, "altitude": float }` + * dict: `{ "lng": float, "lat": float, "alt": float }` """ output = { - "latitude": 0.0, - "longitude": 0.0, - "altitude": 0.0 + "lng": 0.0, + "lat": 0.0, + "alt": 0.0 } with open(filepath, 'rb') as src: @@ -38,9 +38,9 @@ def extract_location(filepath: str) -> dict: return output try: - output["latitude"] = decimal_coords(img.gps_latitude, img.gps_latitude_ref) - output["longitude"] = decimal_coords(img.gps_longitude, img.gps_longitude_ref) - output["altitude"] = img.gps_altitude + output["lng"] = decimal_coords(img.gps_longitude, img.gps_longitude_ref) + output["lat"] = decimal_coords(img.gps_latitude, img.gps_latitude_ref) + output["alt"] = img.gps_altitude except AttributeError: pass