152 lines
5.9 KiB
Python
152 lines
5.9 KiB
Python
import uuid
|
|
import aiohttp
|
|
from fastapi import FastAPI, HTTPException, Request, UploadFile
|
|
from fastapi.responses import FileResponse
|
|
from fastapi import Depends, FastAPI
|
|
from fastapi.security import OAuth2PasswordBearer
|
|
from pyrate_limiter import Duration, Limiter, Rate
|
|
from fastapi_limiter.depends import RateLimiter
|
|
from fastapi import Depends
|
|
import asyncmy
|
|
import asyncio
|
|
import os
|
|
from dotenv import load_dotenv
|
|
from contextlib import asynccontextmanager
|
|
import logging
|
|
|
|
load_dotenv()
|
|
|
|
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
|
|
log_formatter = logging.Formatter(
|
|
fmt="%(asctime)s [%(levelname)s] %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
)
|
|
|
|
file_handler = logging.FileHandler("data/api.log", encoding="utf-8")
|
|
file_handler.setFormatter(log_formatter)
|
|
|
|
console_handler = logging.StreamHandler()
|
|
console_handler.setFormatter(log_formatter)
|
|
|
|
logging.basicConfig(
|
|
level=getattr(logging, log_level, logging.INFO),
|
|
handlers=[file_handler, console_handler],
|
|
)
|
|
|
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
SECRET = os.getenv("SECRET")
|
|
|
|
@asynccontextmanager
|
|
async def connect_db(app: FastAPI):
|
|
app.state.pool = await asyncmy.create_pool(
|
|
host=os.getenv("DB_HOST", "localhost"),
|
|
port=int(os.getenv("DB_PORT", 3306)),
|
|
user=os.getenv("DB_USER"),
|
|
password=os.getenv("DB_PASSWORD"),
|
|
db=os.getenv("DB_NAME"),
|
|
minsize=5,
|
|
maxsize=20
|
|
)
|
|
await create_tables(app.state.pool)
|
|
task = asyncio.create_task(fetch_images())
|
|
try:
|
|
yield
|
|
finally:
|
|
task.cancel()
|
|
try:
|
|
await task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
app.state.pool.close()
|
|
await app.state.pool.wait_closed()
|
|
|
|
app = FastAPI(lifespan=connect_db)
|
|
limiter = Limiter(Rate(50, Duration.MINUTE))
|
|
|
|
async def create_tables(pool):
|
|
async with pool.acquire() as conn:
|
|
async with conn.cursor() as cursor:
|
|
await cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS images (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
url VARCHAR(255) NOT NULL,
|
|
filename VARCHAR(255) NOT NULL,
|
|
source VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""")
|
|
await conn.commit()
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
return {"message": "yes the api works, maybe i will create a small landing page later here"}
|
|
|
|
@app.get("/random", dependencies=[Depends(RateLimiter(limiter=limiter))])
|
|
async def get_random_bnuy(request: Request):
|
|
async with app.state.pool.acquire() as conn:
|
|
async with conn.cursor() as cursor:
|
|
await cursor.execute("SELECT filename, source, url FROM images ORDER BY RAND() LIMIT 1;")
|
|
result = await cursor.fetchone()
|
|
if result:
|
|
filepath = os.path.join("data/images", result[0])
|
|
if os.path.exists(filepath):
|
|
return {"url": f"{request.base_url}images/{result[0]}", "source": result[1], "original_url": result[2]}
|
|
else:
|
|
raise HTTPException(status_code=404, detail="Image file not found")
|
|
else:
|
|
raise HTTPException(status_code=404, detail="No available images found")
|
|
|
|
@app.get("/images/{filename}", dependencies=[Depends(RateLimiter(limiter=limiter))])
|
|
async def get_image(filename: str):
|
|
async with app.state.pool.acquire() as conn:
|
|
async with conn.cursor() as cursor:
|
|
await cursor.execute("SELECT filename FROM images WHERE filename = %s", (filename,))
|
|
result = await cursor.fetchone()
|
|
if result:
|
|
filepath = os.path.join("data/images", result[0])
|
|
if os.path.exists(filepath):
|
|
return FileResponse(filepath)
|
|
else:
|
|
raise HTTPException(status_code=404, detail="Image file not found")
|
|
else:
|
|
raise HTTPException(status_code=404, detail="Image not found")
|
|
|
|
async def fetch_images():
|
|
from collector import save_picture
|
|
while True:
|
|
try:
|
|
logging.info("Starting image collection...")
|
|
await save_picture(app.state.pool)
|
|
logging.info("Image collection completed. Sleeping for 1 hour...")
|
|
except Exception as e:
|
|
logging.error(f"Error during image collection: {e}")
|
|
await asyncio.sleep(86400) # Sleep for 24 hours
|
|
|
|
@app.post("/upload", dependencies=[Depends(RateLimiter(limiter=limiter))])
|
|
async def upload_image(file: UploadFile, request: Request, token: str = Depends(oauth2_scheme)):
|
|
# if SECRET isn't set, return Unauthrorized
|
|
if token != SECRET or token is None:
|
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
|
if not file:
|
|
raise HTTPException(status_code=400, detail="No file uploaded")
|
|
if not file.filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')) or file.content_type not in ["image/jpeg", "image/png", "image/gif"]:
|
|
raise HTTPException(status_code=400, detail="Unsupported file type")
|
|
try:
|
|
content = await file.read()
|
|
generate_filename = str(uuid.uuid4()) + os.path.splitext(file.filename)[1]
|
|
filename = os.path.join("data/images", generate_filename)
|
|
with open(filename, "wb") as f:
|
|
f.write(content)
|
|
|
|
logging.info(f"Saved uploaded image to {filename}")
|
|
|
|
async with app.state.pool.acquire() as conn:
|
|
async with conn.cursor() as cursor:
|
|
await cursor.execute(
|
|
"INSERT INTO images (url, filename, source) VALUES (%s, %s, %s)",
|
|
(f"{request.base_url}images/{generate_filename}", generate_filename, "user_upload")
|
|
)
|
|
await conn.commit()
|
|
except Exception as e:
|
|
logging.error(f"Error saving uploaded image: {e}")
|
|
raise HTTPException(status_code=500, detail="Failed to save image") |