Files
kguerineau b12ebea0e8 refactor: refactorisation, validation et corrections mail/DNS
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
2026-04-24 15:36:29 +02:00

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