Coverage for server / connection / storage_bucket.py: 89%
28 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
1import os
2import boto3
3from botocore.client import Config
4from dotenv import load_dotenv
6load_dotenv()
8# MinIO Configuration (IONOS Server)
9MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT")
10MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY")
11MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY")
12MINIO_BUCKET = os.getenv("MINIO_BUCKET")
13MINIO_PUBLIC_URL = os.getenv("MINIO_PUBLIC_URL", MINIO_ENDPOINT)
15s3 = boto3.client(
16 "s3",
17 endpoint_url=MINIO_ENDPOINT,
18 aws_access_key_id=MINIO_ACCESS_KEY,
19 aws_secret_access_key=MINIO_SECRET_KEY,
20 config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
21 region_name="us-east-1",
22)
24# Private bucket for PII (e.g. user profile photos). Objects here are NOT public-read;
25# they are served only via short-lived presigned URLs minted at read time.
26MINIO_PRIVATE_BUCKET = os.getenv("MINIO_PRIVATE_BUCKET", "eruditiontx-private")
29def presign_get(key: str, bucket: str | None = None, expires: int = 600) -> str | None:
30 """Mint a short-lived presigned GET URL for a private object.
32 ``key`` is the object key (e.g. ``user-images/<id>/<file>.png``). Returns ``None``
33 for an empty key so callers can pass through missing values unchanged.
34 """
35 if not key:
36 return None
37 return s3.generate_presigned_url(
38 "get_object",
39 Params={"Bucket": bucket or MINIO_PRIVATE_BUCKET, "Key": key},
40 ExpiresIn=expires,
41 )
44_USER_IMAGE_MARKER = "user-images/"
47def profile_picture_url(value: str | None, expires: int = 600) -> str | None:
48 """Return a display URL for a stored ``profile_picture`` value.
50 Profile photos live in the private bucket (PII). If ``value`` references a
51 ``user-images/`` object — whether stored as a bare key (``user-images/<id>/<file>``)
52 or a legacy full public URL — return a short-lived **presigned** GET URL so it
53 renders in ``<img>`` without exposing the object publicly. Any other value
54 (``None``, empty, or a non-user-images URL) is passed through unchanged.
55 """
56 if not value or _USER_IMAGE_MARKER not in value:
57 return value
58 key = value[value.index(_USER_IMAGE_MARKER):]
59 return presign_get(key, expires=expires)
62_CLASS_IMAGE_MARKER = "class-images/"
65def class_photo_url(value: str | None, expires: int = 600) -> str | None:
66 """Return a display URL for a stored ``class_photo`` value.
68 Class cover photos live in the private bucket. If ``value`` references a
69 ``class-images/`` object, return a short-lived **presigned** GET URL so it
70 renders in an ``<img>`` without exposing the object publicly. Any other
71 value (``None``, empty, or a non-class-images value) is passed through
72 unchanged.
73 """
74 if not value or _CLASS_IMAGE_MARKER not in value:
75 return value
76 key = value[value.index(_CLASS_IMAGE_MARKER):]
77 return presign_get(key, expires=expires)