Refactorisation - Hook useApi centralisé (hooks/useApi.js) — supprime 200 lignes dupliquées - Helpers backend : get_or_404, _sync_domain_from_state, _sync_zone_from_state, _stable_id - Constantes de rôles RBAC dans auth.py (ROLE_GLOBAL_ADMIN, etc.) - handle_apply_domain découpé en _sync_domain_row / _sync_mailboxes / _sync_aliases (169→32 lignes) - oidc_callback découpé en _exchange_oidc_token / _provision_oidc_user (159→54 lignes) - Extraction composants : AccountModal, AliasModal, DnssecResultModal, DnssecManageModal - Suppression MailingLists.jsx (inutilisé) Corrections - Notification de connexion différée après validation TOTP - Création domaine : validation complète avant tout appel API (quota bal > quota global bloqué) - Création domaine : service Mail non tenté si création DNS échoue - Alias goto : validation email bloquante (frontend + backend) - Alias : vérification conflit avec compte mail existant (409) - delete_domain : notifie l'agent mail pour suppression dans netadmin_mail - Clés DNSSEC archivées dans key_dir/old/ avant activation et à la suppression de zone - _get_ds_records : exclut le dossier old/ (évitait la lecture de doublons) - Modale TOTP : remplace window.confirm par une vraie modale - "Mon compte" accessible sans rôle - Suppression champ "Serveur API" de la sidebar
108 lines
3.1 KiB
Python
108 lines
3.1 KiB
Python
"""
|
|
db.py — MySQL/MariaDB connection for NetAdmin (PyMySQL).
|
|
All tables live in the shared 'netadmin' database.
|
|
Rows returned as dicts. Interface compatible with sqlite3 usage patterns.
|
|
"""
|
|
|
|
import logging
|
|
from contextlib import contextmanager
|
|
from typing import Any
|
|
|
|
import pymysql
|
|
import pymysql.cursors
|
|
|
|
from config import cfg
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class _ConnWrapper:
|
|
"""
|
|
Thin wrapper around a PyMySQL connection that adds .execute() / .fetchone()
|
|
/ .fetchall() methods directly on the connection object — matching the sqlite3
|
|
interface used throughout the codebase.
|
|
"""
|
|
|
|
def __init__(self, conn):
|
|
self._conn = conn
|
|
self._cur = conn.cursor()
|
|
|
|
# ── Cursor proxy ─────────────────────────────────────────────────────
|
|
def execute(self, sql: str, params=None):
|
|
self._cur.execute(sql, params or ())
|
|
return self._cur
|
|
|
|
def fetchone(self):
|
|
return self._cur.fetchone()
|
|
|
|
def fetchall(self):
|
|
return self._cur.fetchall()
|
|
|
|
# ── Transaction proxy ─────────────────────────────────────────────────
|
|
def commit(self):
|
|
self._conn.commit()
|
|
|
|
def rollback(self):
|
|
self._conn.rollback()
|
|
|
|
def close(self):
|
|
try:
|
|
self._cur.close()
|
|
except Exception:
|
|
pass
|
|
self._conn.close()
|
|
|
|
|
|
def _connect() -> _ConnWrapper:
|
|
db = cfg.database
|
|
raw = pymysql.connect(
|
|
host = db.host,
|
|
port = db.port,
|
|
user = db.user,
|
|
password = db.password,
|
|
database = db.name,
|
|
charset = "utf8mb4",
|
|
cursorclass = pymysql.cursors.DictCursor,
|
|
autocommit = False,
|
|
)
|
|
return _ConnWrapper(raw)
|
|
|
|
|
|
@contextmanager
|
|
def get_db():
|
|
"""Yield a connection wrapper. Commits on success, rolls back on exception."""
|
|
conn = _connect()
|
|
try:
|
|
yield conn
|
|
conn.commit()
|
|
except Exception:
|
|
conn.rollback()
|
|
raise
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# ── Datetime helper ────────────────────────────────────────────────────────
|
|
|
|
def _dt(v) -> str | None:
|
|
"""Convert datetime/date → ISO string. Pass str/None through unchanged.
|
|
|
|
MariaDB returns DATETIME columns as datetime.datetime objects via PyMySQL.
|
|
Pydantic models expect str for these fields.
|
|
"""
|
|
import datetime as _datetime
|
|
if v is None:
|
|
return None
|
|
if isinstance(v, (_datetime.datetime, _datetime.date)):
|
|
return v.isoformat()
|
|
return str(v) if v else None
|
|
|
|
|
|
def get_or_404(conn, query: str, params: tuple, detail: str):
|
|
"""Execute query and raise HTTPException(404) if no row found."""
|
|
from fastapi import HTTPException
|
|
row = conn.execute(query, params).fetchone()
|
|
if not row:
|
|
raise HTTPException(404, detail)
|
|
return row
|