145 lines
6.2 KiB
Python
145 lines
6.2 KiB
Python
import re
|
|
from secrets import token_urlsafe
|
|
from magic import Magic
|
|
from datetime import datetime
|
|
from os import makedirs, sep, path, remove
|
|
from modules.hasher import get_phash, get_duplicates
|
|
from modules.utils import configGet
|
|
from modules.app import app, check_project_key, get_api_key
|
|
from modules.database import col_photos, col_albums, col_tokens
|
|
from bson.objectid import ObjectId
|
|
from bson.errors import InvalidId
|
|
|
|
from fastapi import HTTPException, Depends, UploadFile
|
|
from fastapi.responses import UJSONResponse, Response
|
|
from fastapi.openapi.models import APIKey
|
|
from starlette.status import HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_404_NOT_FOUND, HTTP_406_NOT_ACCEPTABLE, HTTP_409_CONFLICT
|
|
|
|
@app.post("/albums/{album}/photos", response_class=UJSONResponse)
|
|
async def photo_upload(file: UploadFile, album: str, ignore_duplicates: bool = False, apikey: APIKey = Depends(get_api_key)):
|
|
|
|
if (check_project_key("photos", apikey)):
|
|
|
|
if col_albums.find_one( {"name": album} ) is None:
|
|
return HTTPException(status_code=HTTP_404_NOT_FOUND, detail=f"Provided album '{album}' does not exist.")
|
|
|
|
# if not file.content_type.startswith("image"):
|
|
# return HTTPException(status_code=HTTP_406_NOT_ACCEPTABLE, detail="Provided file is not an image, not accepting.")
|
|
|
|
makedirs(f'data{sep}users{sep}sample_user{sep}albums{sep}{album}', exist_ok=True)
|
|
|
|
filename = file.filename
|
|
|
|
if path.exists(f'data{sep}users{sep}sample_user{sep}albums{sep}{album}{sep}{file.filename}'):
|
|
base_name = file.filename.split(".")[:-1]
|
|
extension = file.filename.split(".")[-1]
|
|
filename = ".".join(base_name)+f"_{int(datetime.now().timestamp())}."+extension
|
|
|
|
with open(f'data{sep}users{sep}sample_user{sep}albums{sep}{album}{sep}{filename}', "wb") as f:
|
|
f.write(await file.read())
|
|
|
|
file_hash = await get_phash(f'data{sep}users{sep}sample_user{sep}albums{sep}{album}{sep}{filename}')
|
|
duplicates = await get_duplicates(file_hash, album)
|
|
|
|
if len(duplicates) > 0 and ignore_duplicates is False:
|
|
return UJSONResponse(
|
|
{
|
|
"detail": "Image duplicates found. Pass 'ignore_duplicates=true' to ignore.",
|
|
"duplicates": duplicates
|
|
},
|
|
status_code=HTTP_409_CONFLICT
|
|
)
|
|
|
|
uploaded = col_photos.insert_one( {"album": album, "hash": file_hash, "filename": filename} )
|
|
|
|
return UJSONResponse(
|
|
{
|
|
"id": uploaded.inserted_id.__str__(),
|
|
"album": album,
|
|
"hash": file_hash,
|
|
"filename": filename
|
|
}
|
|
)
|
|
|
|
else:
|
|
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail=configGet("key_invalid", "messages"))
|
|
|
|
@app.get("/photos/{id}")
|
|
async def photo_get(id: str, apikey: APIKey = Depends(get_api_key)):
|
|
|
|
if (check_project_key("photos", apikey)):
|
|
|
|
try:
|
|
image = col_photos.find_one( {"_id": ObjectId(id)} )
|
|
if image is None:
|
|
raise InvalidId(id)
|
|
except InvalidId:
|
|
return HTTPException(status_code=HTTP_404_NOT_FOUND, detail="Could not find an image with such id.")
|
|
|
|
image_path = f'data{sep}users{sep}sample_user{sep}albums{sep}{image["album"]}{sep}{image["filename"]}'
|
|
|
|
mime = Magic(mime=True).from_file(image_path)
|
|
|
|
with open(image_path, "rb") as f: image_file = f.read()
|
|
|
|
return Response(image_file, media_type=mime)
|
|
|
|
else:
|
|
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail=configGet("key_invalid", "messages"))
|
|
|
|
@app.delete("/photos/{id}")
|
|
async def photo_delete(id: str, apikey: APIKey = Depends(get_api_key)):
|
|
|
|
if (check_project_key("photos", apikey)):
|
|
|
|
try:
|
|
image = col_photos.find_one_and_delete( {"_id": ObjectId(id)} )
|
|
if image is None:
|
|
raise InvalidId(id)
|
|
except InvalidId:
|
|
return HTTPException(status_code=HTTP_404_NOT_FOUND, detail="Could not find an image with such id.")
|
|
|
|
remove(f'data{sep}users{sep}sample_user{sep}albums{sep}{image["album"]}{sep}{image["filename"]}')
|
|
|
|
return Response(status_code=HTTP_204_NO_CONTENT)
|
|
|
|
else:
|
|
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail=configGet("key_invalid", "messages"))
|
|
|
|
@app.get("/albums/{album}/photos", response_class=UJSONResponse)
|
|
async def photo_find(q: str, album: str, page: int = 1, page_size: int = 100, apikey: APIKey = Depends(get_api_key)):
|
|
|
|
if (check_project_key("photos", apikey)):
|
|
|
|
if col_albums.find_one( {"name": album} ) is None:
|
|
return HTTPException(status_code=HTTP_404_NOT_FOUND, detail=f"Provided album '{album}' does not exist.")
|
|
|
|
if page <= 0 or page_size <= 0:
|
|
return HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="Parameters 'page' and 'page_size' must be greater or equal to 1.")
|
|
|
|
output = {"results": []}
|
|
skip = (page-1)*page_size
|
|
images = list(col_photos.find({"album": album, "filename": re.compile(q)}, limit=page_size, skip=skip))
|
|
|
|
for image in images:
|
|
output["results"].append({"id": image["_id"].__str__(), "filename": image["filename"]})
|
|
|
|
if col_photos.count_documents( {"album": album, "filename": re.compile(q)} ) > 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, "apikey": apikey} )
|
|
output["next_page"] = f"https://api.end-play.xyz/photoFindToken?token={token}" # type: ignore
|
|
|
|
return UJSONResponse(output)
|
|
|
|
else:
|
|
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail=configGet("key_invalid", "messages"))
|
|
|
|
@app.get("/photos/token/{token}", response_class=UJSONResponse)
|
|
async def photo_find_token(token: str):
|
|
|
|
found_record = col_tokens.find_one( {"token": token} )
|
|
|
|
if found_record is None:
|
|
return HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Invalid search token.")
|
|
|
|
return await photo_find(q=found_record["query"], album=found_record["album"], page=found_record["page"], page_size=found_record["page_size"], apikey=found_record["apikey"]) |