feat: initial Jimi Gallery prototype

- Public site (Home/Artists/Exhibitions/News/About/Contact) with EN/KO/JA i18n
- Admin panel with login, CRUD, image upload, multilingual editing
- Exhibition slider/lightbox view
- FastAPI + MongoDB backend, JWT auth
- Docker Compose deployment, behind nginx at jimi.yakenator.io
This commit is contained in:
2026-04-25 12:47:36 +09:00
commit 098b55e3b0
32 changed files with 5334 additions and 0 deletions

37
backend/auth.py Normal file
View File

@ -0,0 +1,37 @@
"""Admin auth: bcrypt-less password check + JWT issuance and verification."""
import os
from datetime import datetime, timedelta, timezone
import jwt
from fastapi import Header, HTTPException
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "admin")
JWT_SECRET = os.environ.get("JWT_SECRET", "dev-secret-change-me")
JWT_ALGO = "HS256"
TOKEN_TTL_HOURS = int(os.environ.get("JWT_TTL_HOURS", "24"))
def issue_token() -> str:
payload = {
"sub": "admin",
"iat": datetime.now(timezone.utc),
"exp": datetime.now(timezone.utc) + timedelta(hours=TOKEN_TTL_HOURS),
}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGO)
def verify_token(token: str) -> bool:
try:
jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGO])
return True
except Exception:
return False
async def require_auth(authorization: str = Header(default=None)):
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="Missing bearer token")
token = authorization.split(" ", 1)[1].strip()
if not verify_token(token):
raise HTTPException(status_code=401, detail="Invalid or expired token")
return True