PhotosAPI/extensions/albums.py

230 lines
6.8 KiB
Python
Raw Permalink Normal View History

2022-12-20 02:22:32 +02:00
import re
2023-06-23 11:51:42 +03:00
from os import makedirs, rename
from pathlib import Path
2022-12-20 02:22:32 +02:00
from shutil import rmtree
from typing import Union
2023-03-12 15:59:13 +02:00
from bson.errors import InvalidId
from bson.objectid import ObjectId
2023-02-16 15:58:27 +02:00
from fastapi import Security
2023-03-12 15:59:13 +02:00
from fastapi.responses import Response, UJSONResponse
2023-02-16 15:58:27 +02:00
from starlette.status import HTTP_204_NO_CONTENT
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
from classes.exceptions import (
AlbumAlreadyExistsError,
AlbumIncorrectError,
AlbumNotFoundError,
)
from classes.models import Album, AlbumModified, SearchResultsAlbum
from modules.app import app
from modules.database import col_albums, col_photos
from modules.security import User, get_current_active_user
album_create_responses = {
406: AlbumIncorrectError("name", "error").openapi,
2023-03-12 15:59:13 +02:00
409: AlbumAlreadyExistsError("name").openapi,
}
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
@app.post(
"/albums",
description="Create album with name and title",
response_class=UJSONResponse,
response_model=Album,
responses=album_create_responses,
)
async def album_create(
name: str,
title: str,
current_user: User = Security(get_current_active_user, scopes=["albums.write"]),
):
if re.search(re.compile("^[a-z,0-9,_]*$"), name) is False:
raise AlbumIncorrectError("name", "can only contain a-z, 0-9 and _ characters.")
2023-03-12 15:59:13 +02:00
2022-12-20 14:28:50 +02:00
if 2 > len(name) > 20:
raise AlbumIncorrectError("name", "must be >2 and <20 characters.")
2022-12-20 02:22:32 +02:00
2022-12-20 14:28:50 +02:00
if 2 > len(title) > 40:
raise AlbumIncorrectError("title", "must be >2 and <40 characters.")
2022-12-20 02:22:32 +02:00
2023-08-14 14:44:07 +03:00
if (await col_albums.find_one({"name": name})) is not None:
raise AlbumAlreadyExistsError(name)
2022-12-20 02:22:32 +02:00
2023-06-23 11:51:42 +03:00
makedirs(Path(f"data/users/{current_user.user}/albums/{name}"), exist_ok=True)
2022-12-20 02:22:32 +02:00
2023-08-14 14:44:07 +03:00
uploaded = await col_albums.insert_one(
2023-03-12 15:59:13 +02:00
{"user": current_user.user, "name": name, "title": title, "cover": None}
)
2022-12-20 02:22:32 +02:00
2022-12-20 14:28:50 +02:00
return UJSONResponse(
2023-03-12 15:59:13 +02:00
{"id": uploaded.inserted_id.__str__(), "name": name, "title": title}
2022-12-20 14:28:50 +02:00
)
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
@app.get("/albums", description="Find album by name", response_model=SearchResultsAlbum)
async def album_find(
q: str,
current_user: User = Security(get_current_active_user, scopes=["albums.list"]),
):
2022-12-20 12:37:32 +02:00
output = {"results": []}
2022-12-20 02:22:32 +02:00
2023-08-14 14:44:07 +03:00
async for album in col_albums.find(
{"user": current_user.user, "name": re.compile(q)}
):
2023-03-12 15:59:13 +02:00
output["results"].append(
{
"id": album["_id"].__str__(),
"name": album["name"],
"title": album["title"],
}
)
2022-12-20 02:22:32 +02:00
2022-12-20 12:37:32 +02:00
return UJSONResponse(output)
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
album_patch_responses = {
404: AlbumNotFoundError("id").openapi,
2023-03-12 15:59:13 +02:00
406: AlbumIncorrectError("name", "error").openapi,
}
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
@app.patch(
"/albums/{id}",
description="Modify album's name or title by id",
response_class=UJSONResponse,
response_model=AlbumModified,
responses=album_patch_responses,
)
async def album_patch(
id: str,
name: Union[str, None] = None,
title: Union[str, None] = None,
cover: Union[str, None] = None,
current_user: User = Security(get_current_active_user, scopes=["albums.write"]),
):
2022-12-20 14:28:50 +02:00
try:
2023-08-14 14:44:07 +03:00
album = await col_albums.find_one({"_id": ObjectId(id)})
2022-12-20 14:28:50 +02:00
if album is None:
raise InvalidId(id)
2023-08-14 14:44:07 +03:00
except InvalidId as exc:
raise AlbumNotFoundError(id) from exc
2022-12-20 02:22:32 +02:00
2023-06-23 13:17:01 +03:00
if title is None:
2022-12-20 14:28:50 +02:00
title = album["title"]
2022-12-20 02:22:32 +02:00
2023-06-23 13:17:01 +03:00
elif 2 > len(title) > 40:
raise AlbumIncorrectError("title", "must be >2 and <40 characters.")
2022-12-20 14:28:50 +02:00
if name is not None:
2023-03-12 15:59:13 +02:00
if re.search(re.compile("^[a-z,0-9,_]*$"), name) is False:
raise AlbumIncorrectError(
"name", "can only contain a-z, 0-9 and _ characters."
)
2022-12-20 02:22:32 +02:00
if 2 > len(name) > 20:
raise AlbumIncorrectError("name", "must be >2 and <20 characters.")
2022-12-20 14:28:50 +02:00
rename(
2023-06-23 11:51:42 +03:00
Path(f"data/users/{current_user.user}/albums/{album['name']}"),
Path(f"data/users/{current_user.user}/albums/{name}"),
2023-03-12 15:59:13 +02:00
)
2023-08-14 14:44:07 +03:00
await col_photos.update_many(
2023-03-12 15:59:13 +02:00
{"user": current_user.user, "album": album["name"]},
{"$set": {"album": name}},
2022-12-20 14:28:50 +02:00
)
else:
name = album["name"]
2022-12-20 02:22:32 +02:00
2022-12-21 00:59:47 +02:00
if cover is not None:
2023-08-14 14:44:07 +03:00
image = await col_photos.find_one(
{"_id": ObjectId(cover), "album": album["name"]}
)
2022-12-21 00:59:47 +02:00
cover = image["_id"].__str__() if image is not None else album["cover"]
else:
cover = album["cover"]
2023-08-14 14:44:07 +03:00
await col_albums.update_one(
2023-03-12 15:59:13 +02:00
{"_id": ObjectId(id)}, {"$set": {"name": name, "title": title, "cover": cover}}
2022-12-20 14:28:50 +02:00
)
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
return UJSONResponse({"name": name, "title": title, "cover": cover})
album_put_responses = {
404: AlbumNotFoundError("id").openapi,
2023-03-12 15:59:13 +02:00
406: AlbumIncorrectError("name", "error").openapi,
}
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
@app.put(
"/albums/{id}",
description="Modify album's name and title by id",
response_class=UJSONResponse,
response_model=AlbumModified,
responses=album_put_responses,
)
async def album_put(
id: str,
name: str,
title: str,
cover: str,
current_user: User = Security(get_current_active_user, scopes=["albums.write"]),
):
2022-12-20 14:28:50 +02:00
try:
2023-08-14 14:44:07 +03:00
album = await col_albums.find_one({"_id": ObjectId(id)})
2022-12-20 14:28:50 +02:00
if album is None:
raise InvalidId(id)
2023-08-14 14:44:07 +03:00
except InvalidId as exc:
raise AlbumNotFoundError(id) from exc
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
if re.search(re.compile("^[a-z,0-9,_]*$"), name) is False:
raise AlbumIncorrectError("name", "can only contain a-z, 0-9 and _ characters.")
2022-12-20 02:22:32 +02:00
2022-12-20 14:28:50 +02:00
if 2 > len(name) > 20:
raise AlbumIncorrectError("name", "must be >2 and <20 characters.")
2022-12-20 02:22:32 +02:00
2022-12-20 14:28:50 +02:00
if 2 > len(title) > 40:
raise AlbumIncorrectError("title", "must be >2 and <40 characters.")
2022-12-20 02:22:32 +02:00
2023-08-14 14:44:07 +03:00
image = await col_photos.find_one({"_id": ObjectId(cover), "album": album["name"]})
2023-03-12 15:59:13 +02:00
cover = image["_id"].__str__() if image is not None else None # type: ignore
2022-12-20 14:28:50 +02:00
rename(
2023-06-23 11:51:42 +03:00
Path(f"data/users/{current_user.user}/albums/{album['name']}"),
Path(f"data/users/{current_user.user}/albums/{name}"),
2022-12-20 14:28:50 +02:00
)
2022-12-20 02:22:32 +02:00
2023-08-14 14:44:07 +03:00
await col_photos.update_many(
2023-03-12 15:59:13 +02:00
{"user": current_user.user, "album": album["name"]}, {"$set": {"album": name}}
)
2023-08-14 14:44:07 +03:00
await col_albums.update_one(
2023-03-12 15:59:13 +02:00
{"_id": ObjectId(id)}, {"$set": {"name": name, "title": title, "cover": cover}}
2022-12-20 14:28:50 +02:00
)
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
return UJSONResponse({"name": name, "title": title, "cover": cover})
2022-12-20 02:22:32 +02:00
2023-03-12 15:59:13 +02:00
album_delete_responses = {404: AlbumNotFoundError("id").openapi}
@app.delete(
"/album/{id}",
description="Delete album by id",
status_code=HTTP_204_NO_CONTENT,
responses=album_delete_responses,
)
async def album_delete(
id: str,
current_user: User = Security(get_current_active_user, scopes=["albums.write"]),
):
2022-12-20 14:28:50 +02:00
try:
2023-08-14 14:44:07 +03:00
album = await col_albums.find_one_and_delete({"_id": ObjectId(id)})
2022-12-20 14:28:50 +02:00
if album is None:
raise InvalidId(id)
2023-08-14 14:44:07 +03:00
except InvalidId as exc:
raise AlbumNotFoundError(id) from exc
2023-03-12 15:59:13 +02:00
2023-08-14 14:44:07 +03:00
await col_photos.delete_many({"album": album["name"]})
2022-12-20 14:28:50 +02:00
2023-06-23 11:51:42 +03:00
rmtree(Path(f"data/users/{current_user.user}/albums/{album['name']}"))
2022-12-20 14:28:50 +02:00
2023-03-12 15:59:13 +02:00
return Response(status_code=HTTP_204_NO_CONTENT)