Compare commits

..

No commits in common. "4892916e16729a380a0a782546dfd915d51610a6" and "8ee3687c73d1dd119962152397d6ac5500091529" have entirely different histories.

15 changed files with 17 additions and 470 deletions

3
.gitignore vendored

@ -154,4 +154,5 @@ cython_debug/
# Custom
.vscode
config.json
config.json
pages

@ -1,21 +0,0 @@
from typing import Union
from pydantic import BaseModel
class Photo(BaseModel):
id: str
album: str
hash: str
filename: str
class Album(BaseModel):
id: str
name: str
title: str
class AlbumModified(BaseModel):
name: str
title: str
class SearchResults(BaseModel):
results: list
next_page: Union[str, None] = None

@ -2,7 +2,6 @@ import re
from os import makedirs, path, rename
from shutil import rmtree
from typing import Union
from classes.models import Album, AlbumModified, SearchResults
from modules.app import app
from modules.database import col_photos, col_albums
from modules.security import User, get_current_active_user
@ -13,7 +12,7 @@ from fastapi import HTTPException, Security
from fastapi.responses import UJSONResponse, Response
from starlette.status import HTTP_204_NO_CONTENT, HTTP_404_NOT_FOUND, HTTP_406_NOT_ACCEPTABLE, HTTP_409_CONFLICT
@app.post("/albums", response_class=UJSONResponse, response_model=Album, description="Create album with name and title")
@app.post("/albums", response_class=UJSONResponse, description="Create album with name and title")
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:
@ -40,7 +39,7 @@ async def album_create(name: str, title: str, current_user: User = Security(get_
}
)
@app.get("/albums", response_model=SearchResults, description="Find album by name")
@app.get("/albums", description="Find album by name")
async def album_find(q: str, current_user: User = Security(get_current_active_user, scopes=["albums.list"])):
output = {"results": []}
@ -51,7 +50,7 @@ async def album_find(q: str, current_user: User = Security(get_current_active_us
return UJSONResponse(output)
@app.patch("/albums/{id}", response_class=UJSONResponse, response_model=AlbumModified, description="Modify album's name or title by id")
@app.patch("/albums/{id}", response_class=UJSONResponse, description="Modify album's name or title by id")
async def album_patch(id: str, name: Union[str, None] = None, title: Union[str, None] = None, current_user: User = Security(get_current_active_user, scopes=["albums.write"])):
try:
@ -89,7 +88,7 @@ async def album_patch(id: str, name: Union[str, None] = None, title: Union[str,
}
)
@app.put("/albums/{id}", response_class=UJSONResponse, response_model=AlbumModified, description="Modify album's name and title by id")
@app.put("/albums/{id}", response_class=UJSONResponse, description="Modify album's name and title by id")
async def album_put(id: str, name: str, title: str, current_user: User = Security(get_current_active_user, scopes=["albums.write"])):
try:

@ -1,27 +0,0 @@
from os import path
from modules.app import app
from fastapi.responses import HTMLResponse, Response
@app.get("/pages/matter.css", include_in_schema=False)
async def page_matter():
with open(path.join("pages", "matter.css"), "r", encoding="utf-8") as f:
output = f.read()
return Response(content=output)
@app.get("/pages/{page}/{file}", include_in_schema=False)
async def page_assets(page:str, file: str):
with open(path.join("pages", page, file), "r", encoding="utf-8") as f:
output = f.read()
return Response(content=output)
@app.get("/", include_in_schema=False)
async def page_home():
with open(path.join("pages", "home", "index.html"), "r", encoding="utf-8") as f:
output = f.read()
return HTMLResponse(content=output)
@app.get("/register", include_in_schema=False)
async def page_register():
with open(path.join("pages", "register", "index.html"), "r", encoding="utf-8") as f:
output = f.read()
return HTMLResponse(content=output)

@ -2,11 +2,9 @@ import re
import pickle
from secrets import token_urlsafe
from magic import Magic
from datetime import datetime, timedelta
from os import makedirs, path, remove, system
from classes.models import Photo, SearchResults
from datetime import datetime
from os import makedirs, path, remove
from modules.hasher import get_phash, get_duplicates
from modules.scheduler import scheduler
from modules.security import User, get_current_active_user
from modules.app import app
from modules.database import col_photos, col_albums, col_tokens
@ -17,28 +15,8 @@ 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 modules.utils import logWrite
async def compress_image(image_path: str):
image_type = Magic(mime=True).from_file(image_path)
if image_type not in ["image/jpeg", "image/png"]:
logWrite(f"Not compressing {image_path} because its mime is '{image_type}'")
return
size_before = path.getsize(image_path) / 1024
if image_type == "image/jpeg":
system(f"jpegoptim {image_path} -o --max=60 --strip-all")
elif image_type == "image/png":
system(f"optipng -o3 {image_path}")
size_after = path.getsize(image_path) / 1024
logWrite(f"Compressed '{path.split(image_path)[-1]}' from {size_before} Kb to {size_after} Kb")
@app.post("/albums/{album}/photos", response_class=UJSONResponse, response_model=Photo, description="Upload a photo to album")
async def photo_upload(file: UploadFile, album: str, ignore_duplicates: bool = False, compress: bool = True, current_user: User = Security(get_current_active_user, scopes=["photos.write"])):
@app.post("/albums/{album}/photos", response_class=UJSONResponse, description="Upload a photo to album")
async def photo_upload(file: UploadFile, album: str, ignore_duplicates: bool = False, current_user: User = Security(get_current_active_user, scopes=["photos.write"])):
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.")
@ -72,9 +50,6 @@ async def photo_upload(file: UploadFile, album: str, ignore_duplicates: bool = F
uploaded = col_photos.insert_one( {"user": current_user.user, "album": album, "hash": file_hash, "filename": filename} )
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)])
return UJSONResponse(
{
"id": uploaded.inserted_id.__str__(),
@ -116,7 +91,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")
@app.get("/albums/{album}/photos", response_class=UJSONResponse, 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"])):
if col_albums.find_one( {"user": current_user.user, "name": album} ) is None:
@ -136,12 +111,13 @@ async def photo_find(q: str, album: str, page: int = 1, page_size: int = 100, cu
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
else:
output["next_page"] = None # type: ignore
with open("something.txt", "w", encoding="utf-8") as f:
f.write(pickle.loads(pickle.dumps(current_user)).user)
return UJSONResponse(output)
@app.get("/albums/{album}/photos/token", response_class=UJSONResponse, response_model=SearchResults, description="Find a photo by token")
@app.get("/albums/{album}/photos/token", response_class=UJSONResponse, description="Find a photo by token")
async def photo_find_token(token: str):
found_record = col_tokens.find_one( {"token": token} )

@ -1,3 +0,0 @@
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>END PLAY Photos API • Home</title>
<link href="/pages/matter.css" rel="stylesheet">
<link rel="stylesheet" href="/pages/home/style.css">
</head>
<body>
<form class="registration">
<h1>👋 Welcome!</h1>
<p>You need to register in order to use this API.</p>
<p>After registering use official docs to learn how to authentiticate and start managing photos.</p>
<center><a class="matter-button-contained" href="https://photos.end-play.xyz/register">Sign Up</a></center>
</form>
</body>
</html>

@ -1,6 +0,0 @@
// JavaScript is used for toggling loading state
var form = document.querySelector('form');
form.onsubmit = function (event) {
event.preventDefault();
form.classList.add('signed');
};

@ -1,148 +0,0 @@
/* Material Customization */
:root {
--pure-material-primary-rgb: 255, 191, 0;
--pure-material-onsurface-rgb: 0, 0, 0;
}
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: url("https://res.cloudinary.com/finnhvman/image/upload/v1541930411/pattern.png");
}
.registration {
position: relative;
border-radius: 8px;
padding: 16px 48px;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
overflow: hidden;
background-color: white;
}
h1 {
margin: 32px 0;
font-family: "Roboto", "Segoe UI", BlinkMacSystemFont, system-ui, -apple-system;
font-weight: normal;
text-align: center;
}
.registration > label {
display: block;
margin: 24px 0;
width: 320px;
}
p {
font-family: "Roboto", "Segoe UI", BlinkMacSystemFont, system-ui, -apple-system;
font-weight: normal;
text-align: center;
}
a.matter-button-contained {
text-decoration: none;
}
a {
color: rgb(var(--pure-material-primary-rgb));
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
button {
display: block !important;
margin: 32px auto;
}
.done,
.progress {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
visibility: hidden;
}
.done {
transition: visibility 0s 1s;
}
.signed > .done {
visibility: visible;
}
.done > a {
display: inline-block;
text-decoration: none;
}
.progress {
opacity: 0;
}
.signed > .progress {
animation: loading 4s;
}
@keyframes loading {
0% {
visibility: visible;
}
12.5% {
opacity: 0;
}
25% {
opacity: 1;
}
87.5% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.left-footer,
.right-footer {
position: fixed;
padding: 14px;
bottom: 14px;
color: #555;
background-color: #eee;
font-family: "Roboto", "Segoe UI", BlinkMacSystemFont, system-ui, -apple-system;
font-size: 14px;
line-height: 1.5;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
}
.left-footer {
left: 0;
border-radius: 0 4px 4px 0;
text-align: left;
}
.right-footer {
right: 0;
border-radius: 4px 0 0 4px;
text-align: right;
}
.left-footer > a,
.right-footer > a {
color: black;
}
.left-footer > a:hover,
.right-footer > a:hover {
text-decoration: underline;
}

File diff suppressed because one or more lines are too long

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>END PLAY Photos API • Sign Up</title>
<link href="/pages/matter.css" rel="stylesheet">
<link rel="stylesheet" href="/pages/home/style.css">
</head>
<body>
<!-- partial:index.partial.html -->
<form class="registration" method="post">
<h1>👋 Welcome!</h1>
<label class="matter-textfield-outlined">
<input placeholder=" " type="text" alt="You won't be able to change it later!" required>
<span>Username</span>
</label>
<label class="matter-textfield-outlined">
<input placeholder=" " type="email" required>
<span>Email</span>
</label>
<label class="matter-textfield-outlined">
<input placeholder=" " type="password" required>
<span>Password</span>
</label>
<label class="matter-checkbox">
<input type="checkbox" required>
<span>I agree to the <a href="https://codepen.io/collection/nZKBZe/" target="_blank" title="Actually not a Terms of Service">Terms of Service</a></span>
</label>
<button class="matter-button-contained" type="submit">Sign Up</button>
<div class="done">
<h1>👌 You're all set!</h1>
<a class="matter-button-text" href="javascript:window.location.reload(true)">Again</a>
</div>
<div class="progress">
<progress class="matter-progress-circular" />
</div>
</form>
<!-- partial -->
<script src="./script.js"></script>
</body>
</html>

@ -1,6 +0,0 @@
// JavaScript is used for toggling loading state
var form = document.querySelector('form');
form.onsubmit = function (event) {
event.preventDefault();
form.classList.add('signed');
};

@ -1,138 +0,0 @@
/* Material Customization */
:root {
--pure-material-primary-rgb: 255, 191, 0;
--pure-material-onsurface-rgb: 0, 0, 0;
}
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: url("https://res.cloudinary.com/finnhvman/image/upload/v1541930411/pattern.png");
}
.registration {
position: relative;
border-radius: 8px;
padding: 16px 48px;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
overflow: hidden;
background-color: white;
}
h1 {
margin: 32px 0;
font-family: "Roboto", "Segoe UI", BlinkMacSystemFont, system-ui, -apple-system;
font-weight: normal;
text-align: center;
}
.registration > label {
display: block;
margin: 24px 0;
width: 320px;
}
a {
color: rgb(var(--pure-material-primary-rgb));
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
button {
display: block !important;
margin: 32px auto;
}
.done,
.progress {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
visibility: hidden;
}
.done {
transition: visibility 0s 1s;
}
.signed > .done {
visibility: visible;
}
.done > a {
display: inline-block;
text-decoration: none;
}
.progress {
opacity: 0;
}
.signed > .progress {
animation: loading 4s;
}
@keyframes loading {
0% {
visibility: visible;
}
12.5% {
opacity: 0;
}
25% {
opacity: 1;
}
87.5% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.left-footer,
.right-footer {
position: fixed;
padding: 14px;
bottom: 14px;
color: #555;
background-color: #eee;
font-family: "Roboto", "Segoe UI", BlinkMacSystemFont, system-ui, -apple-system;
font-size: 14px;
line-height: 1.5;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
}
.left-footer {
left: 0;
border-radius: 0 4px 4px 0;
text-align: left;
}
.right-footer {
right: 0;
border-radius: 4px 0 0 4px;
text-align: right;
}
.left-footer > a,
.right-footer > a {
color: black;
}
.left-footer > a:hover,
.right-footer > a:hover {
text-decoration: underline;
}

@ -1,7 +1,6 @@
from os import makedirs, sep
from modules.app import app
from modules.utils import *
from modules.scheduler import scheduler
from modules.extensions_loader import dynamic_import_from_src
from fastapi.responses import FileResponse
@ -15,6 +14,4 @@ async def favicon():
#=================================================================================
dynamic_import_from_src("extensions", star_import = True)
#=================================================================================
scheduler.start()
#=================================================================================

@ -5,5 +5,4 @@ scipy~=1.9.3
python-magic~=0.4.27
opencv-python~=4.6.0.66
python-jose[cryptography]~=3.3.0
passlib~=1.7.4
apscheduler~=3.9.1.post1
passlib~=1.7.4