diff --git a/cef_3M/__init__.py b/cef_3M/__init__.py index 41e6100..e482565 100644 --- a/cef_3M/__init__.py +++ b/cef_3M/__init__.py @@ -1,21 +1,13 @@ from fastapi import FastAPI, UploadFile, Request, Depends from fastapi.middleware.cors import CORSMiddleware -from minio import Minio import mimetypes -import re from .auth import JWTBearer from .sql import SessionMaker, Uploads from . import util import config from datetime import datetime, timedelta - - -minioClient = Minio( - config.MINIO_ADDR, - access_key=config.MINIO_ACCESS_KEY, - secret_key=config.MINIO_SECRET_KEY, -) +from . import endpoints app = FastAPI() app.add_middleware( @@ -26,6 +18,8 @@ app.add_middleware( allow_headers=["*"], ) +app.include_router(endpoints.router) + @app.post("/upload", dependencies=[Depends(JWTBearer())]) async def upload(file: UploadFile, request: Request): if file.size > config.MAX_FILE_SIZE: @@ -40,11 +34,12 @@ async def upload(file: UploadFile, request: Request): existing.expiry = datetime.now() + timedelta(days=7) else: mime = mimetypes.guess_type(safeFilename) - minioClient.put_object("uploads", sha, file.file, file.size, content_type=mime[0]) + util.minioClient.put_object("uploads", sha, file.file, file.size, content_type=mime[0]) up = Uploads(hash=sha) session.add(up) session.commit() return {"url": f"https://{config.MINIO_ADDR}/uploads/{sha}/{safeFilename}"} + __all__ = ["sql", "auth", "util"] \ No newline at end of file diff --git a/cef_3M/endpoints/__init__.py b/cef_3M/endpoints/__init__.py new file mode 100644 index 0000000..6a61a98 --- /dev/null +++ b/cef_3M/endpoints/__init__.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter +import os +import importlib +router = APIRouter() + +for module in os.listdir(os.path.dirname(__file__)): + if module == '__init__.py' or module[-3:] != '.py': + continue + importlib.import_module("."+module[:-3], package="cef_3M.endpoints") diff --git a/cef_3M/endpoints/pfp.py b/cef_3M/endpoints/pfp.py new file mode 100644 index 0000000..4eb86dc --- /dev/null +++ b/cef_3M/endpoints/pfp.py @@ -0,0 +1,66 @@ +import mimetypes +import time + +from . import router +from fastapi import UploadFile, Request, Depends + +from ..util import minioClient +from ..auth import JWTBearer +import config + +from pywuffs import ImageDecoderType +from pywuffs.aux import ( + ImageDecoder, + ImageDecoderConfig, +) +pfpConfig = ImageDecoderConfig() +pfpConfig.max_incl_dimension = 400 +pfpConfig.enabled_decoders = [ + ImageDecoderType.GIF, + ImageDecoderType.PNG, + ImageDecoderType.JPEG, +] + +iconConfig = ImageDecoderConfig() +iconConfig.max_incl_dimension = 24 +iconConfig.enabled_decoders = [ + ImageDecoderType.PNG, +] + +@router.post("/pfp/upload", dependencies=[Depends(JWTBearer())]) +async def pfpUpload(file: UploadFile, request: Request): + if file.size > config.MAX_PFP_SIZE: + return {"error": "file too big"} + whoami = request.state.jwt + username = whoami["account"].lower() + + # It's not the path I exactly wanted, but this will have to do - WUFFS ensures that the file is valid then we just save it to the server + # I hope there's no issue in doing that... + decoder = ImageDecoder(pfpConfig) + data = await file.read() + decoded = decoder.decode(data) + if decoded.error_message: + return {"error": "invalid file"} + file.file.seek(0) + + mime = mimetypes.guess_type(file.filename) + minioClient.put_object("pfp", username, file.file, file.size, content_type=mime[0]) + return {"url": f"https://{config.MINIO_ADDR}/pfp/{username}?{time.time():.0f}"} + +@router.post("/pfp/uploadIcon", dependencies=[Depends(JWTBearer())]) +async def pfpUpload(file: UploadFile, request: Request): + if file.size > config.MAX_PFP_SIZE: + return {"error": "file too big"} + whoami = request.state.jwt + username = whoami["account"].lower() + + decoder = ImageDecoder(iconConfig) + data = await file.read() + decoded = decoder.decode(data) + if decoded.error_message: + return {"error": "invalid file"} + file.file.seek(0) + + mime = mimetypes.guess_type(file.filename) + minioClient.put_object("pfp", username+"/icon", file.file, file.size, content_type=mime[0]) + return {"url": f"https://{config.MINIO_ADDR}/pfp/{username}/icon?{time.time():.0f}"} \ No newline at end of file diff --git a/cef_3M/util.py b/cef_3M/util.py index f03ec2e..d55bc69 100644 --- a/cef_3M/util.py +++ b/cef_3M/util.py @@ -1,5 +1,8 @@ import hashlib import re +import config +from minio import Minio + from fastapi import UploadFile @@ -13,3 +16,9 @@ async def SHA256(f: UploadFile) -> str: sha.update(data) await f.seek(0) return sha.hexdigest() + +minioClient = Minio( + config.MINIO_ADDR, + access_key=config.MINIO_ACCESS_KEY, + secret_key=config.MINIO_SECRET_KEY, +) diff --git a/config.example.py b/config.example.py index 412ce1c..5826303 100644 --- a/config.example.py +++ b/config.example.py @@ -6,5 +6,9 @@ MINIO_ACCESS_KEY = "access-key-goes-here" MINIO_SECRET_KEY = "secret-key-goes-here" MAX_FILE_SIZE = 1024*1024*20 +MAX_PFP_SIZE = 1024*1024*1.5 +# It's a 24x24 image, you can fit that in 32k +MAX_ICON_SIZE = 1024*32 + # Need to figure out how to make this cooperate more ALLOWED_DOMAINS = ["*"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 123d3e6..0490d75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,14 +15,18 @@ httpcore==1.0.5 httptools==0.6.1 httpx==0.27.0 idna==3.4 +inflect==7.2.1 Jinja2==3.1.4 Mako==1.3.5 markdown-it-py==3.0.0 MarkupSafe==2.1.5 mdurl==0.1.2 minio==7.1.17 +more-itertools==10.2.0 mysqlclient==2.2.4 +numpy==1.26.4 orjson==3.10.3 +pillow==10.3.0 pycparser==2.21 pydantic==2.4.2 pydantic_core==2.10.1 @@ -31,12 +35,15 @@ PyJWT==2.8.0 PyMySQL==1.1.0 python-dotenv==1.0.1 python-multipart==0.0.9 +pywuffs==1.2.1 PyYAML==6.0.1 rich==13.7.1 shellingham==1.5.4 sniffio==1.3.0 +sqlacodegen @ git+https://github.com/agronholm/sqlacodegen@2a6053224d28da27c9b89aa611b9dd4c27637fe6 SQLAlchemy==2.0.30 starlette==0.37.2 +typeguard==4.2.1 typer==0.12.3 typing_extensions==4.8.0 ujson==5.10.0