31 lines
1.1 KiB
Python
31 lines
1.1 KiB
Python
import hashlib
|
|
from datetime import date
|
|
|
|
from app.core.config import settings
|
|
|
|
ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
|
|
|
|
|
|
def _to_base32ish(value: int, length: int) -> str:
|
|
chars: list[str] = []
|
|
base = len(ALPHABET)
|
|
while value:
|
|
value, remainder = divmod(value, base)
|
|
chars.append(ALPHABET[remainder])
|
|
encoded = "".join(reversed(chars)) or ALPHABET[0]
|
|
return encoded[-length:].rjust(length, ALPHABET[0])
|
|
|
|
|
|
def _digest_int(payload: str) -> int:
|
|
digest = hashlib.sha256(payload.encode("utf-8")).digest()
|
|
return int.from_bytes(digest[:8], "big")
|
|
|
|
|
|
def build_certificate_no(sequence_id: int, project_code: str, issue_date: date | None = None) -> str:
|
|
year = (issue_date or date.today()).year
|
|
normalized_project = project_code.strip().upper()
|
|
seed = f"{settings.certificate_no_secret}:{year}:{normalized_project}:{sequence_id}"
|
|
short_code = _to_base32ish(_digest_int(seed), 7)
|
|
check_code = _to_base32ish(_digest_int(f"check:{seed}:{short_code}"), 2)
|
|
return f"PX{year}-{normalized_project}-{short_code}-{check_code}"
|