Envoie les emails via un relais SMTP configurable plutôt qu'en direct-to-MX
Ajoute le bloc "smtp" (host, port, identifiants, TLS) dans config.json, plus fiable et plus simple à opérer qu'une livraison directe au MX du destinataire. data/config.json n'est plus versionné (secrets), remplacé par data/config.example.json.
This commit is contained in:
@ -1,7 +1,6 @@
|
||||
"""Flat-file storage: JSON files per week for pointages, one file for congés."""
|
||||
import json
|
||||
import secrets
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path("/data")
|
||||
@ -9,17 +8,20 @@ CONFIG_FILE = DATA_DIR / "config.json"
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"heures_jour": 7.8, # 7h48 = 39h/semaine
|
||||
"allowed_email_domain": "", # ex: "sdv.fr" — seules ces adresses peuvent se connecter
|
||||
"mail_from": "", # ex: "no-reply@sdv.fr" — expéditeur des emails de connexion
|
||||
"smtp": {
|
||||
"host": "", # ex: "smtp.sdv.fr"
|
||||
"port": 587,
|
||||
"user": "",
|
||||
"password": "",
|
||||
"use_tls": True,
|
||||
},
|
||||
}
|
||||
|
||||
# ── User path helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
def _user_root(user_id: str) -> Path:
|
||||
"""Return data directory for a user.
|
||||
|
||||
Legacy fallback: if no users/ directory exists yet but /data/pointages/ does,
|
||||
the first login migrates the data automatically (see migrate_legacy_to_user).
|
||||
After migration, each user gets /data/users/{user_id}/.
|
||||
"""
|
||||
return DATA_DIR / "users" / user_id
|
||||
|
||||
|
||||
@ -35,29 +37,6 @@ def _ensure_user_dirs(user_id: str):
|
||||
_pointages_dir(user_id).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
# ── Legacy migration (called once on first login) ─────────────────────────────
|
||||
|
||||
def needs_migration() -> bool:
|
||||
"""True when legacy flat data exists and no users/ directory has been created yet."""
|
||||
return (DATA_DIR / "pointages").exists() and not (DATA_DIR / "users").exists()
|
||||
|
||||
|
||||
def migrate_legacy_to_user(user_id: str):
|
||||
"""Move /data/pointages/ and /data/conges.json into /data/users/{user_id}/."""
|
||||
user_root = _user_root(user_id)
|
||||
user_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
legacy_pt = DATA_DIR / "pointages"
|
||||
dest_pt = user_root / "pointages"
|
||||
if legacy_pt.exists() and not dest_pt.exists():
|
||||
shutil.copytree(legacy_pt, dest_pt)
|
||||
|
||||
legacy_cg = DATA_DIR / "conges.json"
|
||||
dest_cg = user_root / "conges.json"
|
||||
if legacy_cg.exists() and not dest_cg.exists():
|
||||
shutil.copy2(legacy_cg, dest_cg)
|
||||
|
||||
|
||||
# ── User list ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def list_users() -> list[str]:
|
||||
@ -126,6 +105,33 @@ def save_conges(conges: list[dict], user_id: str = ""):
|
||||
)
|
||||
|
||||
|
||||
# ── Auth (email + password) ────────────────────────────────────────────────────
|
||||
|
||||
def _auth_file(user_id: str) -> Path:
|
||||
return _user_root(user_id) / "auth.json"
|
||||
|
||||
|
||||
def load_auth(user_id: str) -> dict:
|
||||
f = _auth_file(user_id)
|
||||
if f.exists():
|
||||
return json.loads(f.read_text())
|
||||
return {"email": None, "password_hash": None, "token": None, "token_expires": None}
|
||||
|
||||
|
||||
def save_auth(user_id: str, **fields):
|
||||
auth = load_auth(user_id)
|
||||
auth.update(fields)
|
||||
_ensure_user_dirs(user_id)
|
||||
_auth_file(user_id).write_text(json.dumps(auth, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
def find_user_by_reset_token(token: str) -> str | None:
|
||||
for user_id in list_users():
|
||||
if load_auth(user_id).get("token") == token:
|
||||
return user_id
|
||||
return None
|
||||
|
||||
|
||||
# ── Notifications (ntfy topic + presence tracking) ────────────────────────────
|
||||
|
||||
def _notif_file(user_id: str) -> Path:
|
||||
|
||||
Reference in New Issue
Block a user