- store: SQLite whitelist + per-pubkey daily rate limiting - auth: NIP-98 kind:27235 event verification via pynostr - r2: boto3 presigned PUT URL generation for Cloudflare R2 - nostr: websocket relay subscription for kind:5392 registration events - main: FastAPI app with NIP-96 info endpoint and upload request endpoint Also moves package to src/ layout (required by uv_build) and pins Python to 3.12 for coincurve wheel availability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
48 lines
1.4 KiB
Python
48 lines
1.4 KiB
Python
import os
|
|
import uuid
|
|
|
|
import boto3
|
|
from botocore.config import Config
|
|
|
|
ALLOWED_CONTENT_TYPES: dict[str, str] = {
|
|
"image/jpeg": "jpg",
|
|
"image/png": "png",
|
|
"image/webp": "webp",
|
|
"image/gif": "gif",
|
|
}
|
|
|
|
MAX_FILE_SIZE = int(os.environ.get("JIRORIAN_MAX_FILE_SIZE", str(10 * 1024 * 1024)))
|
|
PRESIGNED_URL_TTL = int(os.environ.get("JIRORIAN_PRESIGNED_TTL", "300"))
|
|
|
|
_client = None
|
|
|
|
|
|
def _get_client():
|
|
global _client
|
|
if _client is None:
|
|
_client = boto3.client(
|
|
"s3",
|
|
endpoint_url=os.environ["R2_ENDPOINT"],
|
|
aws_access_key_id=os.environ["R2_ACCESS_KEY_ID"],
|
|
aws_secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"],
|
|
region_name="auto",
|
|
config=Config(signature_version="s3v4"),
|
|
)
|
|
return _client
|
|
|
|
|
|
def generate_presigned_upload(app: str, pubkey: str, content_type: str) -> tuple[str, str]:
|
|
"""Return (presigned PUT URL, public CDN URL) for a new upload."""
|
|
ext = ALLOWED_CONTENT_TYPES[content_type]
|
|
key = f"{app}/{pubkey}/{uuid.uuid4()}.{ext}"
|
|
bucket = os.environ["R2_BUCKET_NAME"]
|
|
cdn_base = os.environ["R2_CDN_URL"].rstrip("/")
|
|
|
|
upload_url = _get_client().generate_presigned_url(
|
|
"put_object",
|
|
Params={"Bucket": bucket, "Key": key, "ContentType": content_type},
|
|
ExpiresIn=PRESIGNED_URL_TTL,
|
|
HttpMethod="PUT",
|
|
)
|
|
|
|
return upload_url, f"{cdn_base}/{key}"
|