Compare commits

4 Commits
Author SHA1 Message Date
Michelle b2b43cb8cb Merge branch 'main' of ssh://git.scrunkly.cat:2205/Michelle/bnuy-api
Build and publish bnuy api / build (push) Has been cancelled
2026-05-16 20:37:54 +02:00
Michelle bc2652cc0e try adding upload with token + rename "subreddit" row to "source" 2026-05-16 20:37:46 +02:00
Michelle 177d2e7e70 update README
Build and publish bnuy api / build (push) Successful in 13m9s
2026-05-13 11:38:06 +02:00
Michelle b3139d9c5b Edit workflow to work with GitHub Container Registry
Build and publish bnuy api / build (push) Successful in 13m16s
2026-05-13 10:58:33 +02:00
6 changed files with 74 additions and 9 deletions
+1
View File
@@ -1,5 +1,6 @@
# bnuy-api
POST_LIMIT=20
SECRET=generate-me
LOG_LEVEL=INFO
FORWARDED_ALLOW_IPS=172.16.0.0/12
+6 -1
View File
@@ -19,7 +19,12 @@ jobs:
# this step was made by AI, i hope it works
- name: Extract registry host
id: registry
run: echo "host=$(echo ${{ github.server_url }} | sed 's|https\?://||')" >> "$GITHUB_OUTPUT"
run: |
HOST=$(echo ${{ github.server_url }} | sed 's|https\?://||')
if [ "$HOST" = "github.com" ]; then
HOST="ghcr.io"
fi
echo "host=$HOST" >> "$GITHUB_OUTPUT"
- name: Docker meta
id: meta
+25 -1
View File
@@ -1,4 +1,28 @@
# bnuy-api
A API which collects pictures of bunnies and provides a API to get random bunny pictures.
doesn't work yet, still in progress
it's just a basic API right now, I want to add more sources for gifs (tenor?) and maybe more sources for pictures
# Usage
The API is available [here](https://bnuy-api.scrunkly.cat/random)
# Selfhosting
Just copy the compose.yml from the repo and copy the .env.example to .env and fill it.
```
# bnuy-api
POST_LIMIT=20
LOG_LEVEL=INFO
FORWARDED_ALLOW_IPS=172.16.0.0/12
# Database MariaDB
DB_HOST=mariadb
DB_PORT=3306
DB_ROOT_PASSWORD=rootpassword
DB_USER=bnuy
DB_PASSWORD=example
DB_NAME=bnuy
```
+1 -1
View File
@@ -73,7 +73,7 @@ async def save_picture(pool):
async with pool.acquire() as conn:
async with conn.cursor() as cursor:
await cursor.execute(
"INSERT INTO images (url, filename, subreddit) VALUES (%s, %s, %s)",
"INSERT INTO images (url, filename, source) VALUES (%s, %s, %s)",
(url, generate_filename, subreddit)
)
await conn.commit()
+38 -4
View File
@@ -1,7 +1,9 @@
import uuid
import aiohttp
from fastapi import FastAPI, HTTPException, Request
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
@@ -31,6 +33,9 @@ logging.basicConfig(
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(
@@ -66,7 +71,7 @@ async def create_tables(pool):
id INT AUTO_INCREMENT PRIMARY KEY,
url VARCHAR(255) NOT NULL,
filename VARCHAR(255) NOT NULL,
subreddit VARCHAR(255) NOT NULL,
source VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
@@ -80,12 +85,12 @@ async def root():
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, subreddit, url FROM images ORDER BY RAND() LIMIT 1;")
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": f"https://www.reddit.com/r/{result[1]}/", "original_url": result[2]}
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:
@@ -116,3 +121,32 @@ async def fetch_images():
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')):
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")
+1
View File
@@ -2,3 +2,4 @@ fastapi[standard]
fastapi_limiter
asyncmy
aiohttp
python-multipart