Compare commits

...
Author SHA1 Message Date
bruno 47ac8bf6c0 Merge pull request '[fix] Version 2.80.1 synchronisee + corrections ruff (60 erreurs CI)' (#26) from fix/version-2.80.1-ruff into main
CI / security (push) Failing after 1s
CI / lint (push) Failing after 2m10s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-27 23:00:13 -04:00
bruno 01572736c0 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/webhooks.py
CI / security (pull_request) Successful in 1m57s
CI / lint (pull_request) Failing after 2m7s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
2026-10-27 22:58:14 -04:00
bruno b6e5f01748 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/watcher.py 2026-10-27 22:58:11 -04:00
bruno 90da0ac990 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/tools/audit.py 2026-10-27 22:58:08 -04:00
bruno bc26d1506b [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/share.py 2026-10-27 22:58:05 -04:00
bruno 769e3ca559 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/services/vaults.py 2026-10-27 22:58:02 -04:00
bruno b5d789f02a [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/services/recent.py 2026-10-27 22:57:59 -04:00
bruno c8dce25e0d [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/services/backups.py 2026-10-27 22:57:56 -04:00
bruno b322e2d24a [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/scheduler.py 2026-10-27 22:57:53 -04:00
bruno 414ec69c0a [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/routers/realtime.py 2026-10-27 22:57:50 -04:00
bruno f5ca656c04 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/routers/backups.py 2026-10-27 22:57:47 -04:00
bruno e158232aba [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/push.py 2026-10-27 22:57:44 -04:00
bruno 632871ac24 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/notify.py 2026-10-27 22:57:40 -04:00
bruno 7af7b2c8c2 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/indexer.py 2026-10-27 22:57:36 -04:00
bruno 97132770e4 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/guide_export.py 2026-10-27 22:57:32 -04:00
bruno 683ad3ef94 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/export.py 2026-10-27 22:57:29 -04:00
bruno 604383f51c [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/coffre_store.py 2026-10-27 22:57:25 -04:00
bruno bdeb2f00b3 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/coffre_crypto.py 2026-10-27 22:57:21 -04:00
bruno 8688875fae [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/bookslm_routes.py 2026-10-27 22:57:17 -04:00
bruno a42271c7de [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/auth/user_store.py 2026-10-27 22:57:13 -04:00
bruno cf4685eaf1 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/auth/router.py 2026-10-27 22:57:10 -04:00
bruno 5023fb6774 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/audit.py 2026-10-27 22:57:06 -04:00
bruno 04eb70cbe4 [fix] Corrections ruff CI (UP017/UP041/UP012/I001/F401/FURB162) : backend/admin.py 2026-10-27 22:57:01 -04:00
bruno 4dc010f54d [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : CHANGELOG.md 2026-10-27 22:56:57 -04:00
bruno f7e36bc44e [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : docs/ROADMAP.md 2026-10-27 22:56:53 -04:00
bruno a9ac1c06a7 [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : README.fr.md 2026-10-27 22:56:48 -04:00
bruno 9bf6e7492f [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : README.md 2026-10-27 22:56:43 -04:00
bruno 14070cef43 [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : desktop/Cargo.lock 2026-10-27 22:56:37 -04:00
bruno 6b1abbb50e [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : desktop/Cargo.toml 2026-10-27 22:56:33 -04:00
bruno 46c641ebc5 [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : desktop/tauri.conf.json 2026-10-27 22:56:30 -04:00
bruno 2d036f5126 [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : package.json 2026-10-27 22:56:27 -04:00
bruno 2c69e357b8 [fix] Bump VERSION 2.79.0 -> 2.80.1 (synchronisation bump_version.py) : VERSION 2026-10-27 22:56:25 -04:00
bruno ba81f22d40 Merge pull request '[Coffre Secrets] Phase 1 complète : chiffrement, API et UI (1a + 1b + 1c)' (#25) from feat/coffre-secrets-phase1 into main
CI / lint (push) Failing after 1m47s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Successful in 1m58s
2026-10-27 22:29:09 -04:00
31 changed files with 104 additions and 90 deletions
+14 -1
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section > **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.79.0**. > [Unreleased](#unreleased). La dernière version livrée est **2.80.1**.
--- ---
@@ -14,6 +14,19 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
--- ---
## [2.80.1] — 2026-10-27
### Ajouté
- **Coffre Secrets — Phase 1** (contenu de la release 2.80.0, dont l'entrée changelog manquait) : chiffrement par enveloppe AES-256-GCM, API `/api/coffres`, nœud virtuel Secrets sous le Home de l'utilisateur.
### Corrigé
- **Étiquette de version 2.80.0** — le tag v2.80.0 pointait sur un contenu dont le fichier `VERSION` valait encore 2.79.0 (un merge ne déclenche pas le bump automatique) ; `VERSION` et tous les fichiers dérivés passent à 2.80.1, pour que la version affichée corresponde au tag de la prochaine release.
- **CI lint (ruff)** — 60 erreurs corrigées dans `backend/` : alias `datetime.UTC` (Python 3.11+), `TimeoutError` natif, tri d'imports et import inutilisé dans `coffre_store.py`, `.encode()` sans argument redondant, `fromisoformat` gérant « Z » ; aucune règle ruff masquée, aucun changement de comportement.
---
## [2.79.0] — 2026-10-26 ## [2.79.0] — 2026-10-26
### Ajouté ### Ajouté
+3 -3
View File
@@ -4,7 +4,7 @@
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
[![Version](https://img.shields.io/badge/Version-2.79.0-blue.svg)]() [![Version](https://img.shields.io/badge/Version-2.80.1-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -977,8 +977,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
## 📝 Changelog ## 📝 Changelog
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.79.0). Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.80.1).
--- ---
*Projet : ObsiGate | Version : 2.79.0 | Dernière mise à jour : Septembre 2026* *Projet : ObsiGate | Version : 2.80.1 | Dernière mise à jour : Septembre 2026*
+3 -3
View File
@@ -2,7 +2,7 @@
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
[![Version](https://img.shields.io/badge/Version-2.79.0-blue.svg)]() [![Version](https://img.shields.io/badge/Version-2.80.1-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -1152,8 +1152,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
## 📝 Changelog ## 📝 Changelog
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.79.0). See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.80.1).
--- ---
*Project: ObsiGate | Version: 2.79.0 | Last updated: September 2026* *Project: ObsiGate | Version: 2.80.1 | Last updated: September 2026*
+1 -1
View File
@@ -1 +1 @@
2.79.0 2.80.1
+2 -2
View File
@@ -10,7 +10,7 @@ import logging
import os import os
import shutil import shutil
import time import time
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -244,7 +244,7 @@ def _build_stats_payload() -> dict:
"disk_used_gb": disk_used_gb, "disk_used_gb": disk_used_gb,
"disk_total_gb": disk_total_gb, "disk_total_gb": disk_total_gb,
"uptime_seconds": int(_server_uptime_seconds()), "uptime_seconds": int(_server_uptime_seconds()),
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
} }
+7 -7
View File
@@ -10,7 +10,7 @@ Format: JSON lines (one JSON object per line) for easy parsing.
import json import json
import logging import logging
import os import os
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
logger = logging.getLogger("obsigate.audit") logger = logging.getLogger("obsigate.audit")
@@ -54,7 +54,7 @@ def log_file_save(
): ):
"""Log a file save (PUT) operation.""" """Log a file save (PUT) operation."""
_write_entry({ _write_entry({
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"action": "file_save", "action": "file_save",
"username": username, "username": username,
"ip": ip or "unknown", "ip": ip or "unknown",
@@ -72,7 +72,7 @@ def log_file_delete(
): ):
"""Log a file delete operation.""" """Log a file delete operation."""
_write_entry({ _write_entry({
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"action": "file_delete", "action": "file_delete",
"username": username, "username": username,
"ip": ip or "unknown", "ip": ip or "unknown",
@@ -88,7 +88,7 @@ def log_config_change(
): ):
"""Log a configuration change.""" """Log a configuration change."""
_write_entry({ _write_entry({
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"action": "config_change", "action": "config_change",
"username": username, "username": username,
"ip": ip or "unknown", "ip": ip or "unknown",
@@ -99,7 +99,7 @@ def log_config_change(
def log_vault_add(username: str, vault_name: str, vault_path: str, ip: str | None = None): def log_vault_add(username: str, vault_name: str, vault_path: str, ip: str | None = None):
"""Log a vault addition.""" """Log a vault addition."""
_write_entry({ _write_entry({
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"action": "vault_add", "action": "vault_add",
"username": username, "username": username,
"ip": ip or "unknown", "ip": ip or "unknown",
@@ -111,7 +111,7 @@ def log_vault_add(username: str, vault_name: str, vault_path: str, ip: str | Non
def log_vault_remove(username: str, vault_name: str, ip: str | None = None): def log_vault_remove(username: str, vault_name: str, ip: str | None = None):
"""Log a vault removal.""" """Log a vault removal."""
_write_entry({ _write_entry({
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"action": "vault_remove", "action": "vault_remove",
"username": username, "username": username,
"ip": ip or "unknown", "ip": ip or "unknown",
@@ -136,7 +136,7 @@ def log_coffre_event(
(coffre:<slug>/<nom>), des identifiants et des codes de résultat. (coffre:<slug>/<nom>), des identifiants et des codes de résultat.
""" """
entry = { entry = {
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"action": action, "action": action,
"username": username, "username": username,
"ip": ip or "unknown", "ip": ip or "unknown",
+3 -2
View File
@@ -7,6 +7,7 @@ import binascii
import logging import logging
import os import os
import re import re
from datetime import UTC
from fastapi import APIRouter, Body, Depends, HTTPException, Request, Response from fastapi import APIRouter, Body, Depends, HTTPException, Request, Response
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
@@ -696,7 +697,7 @@ async def mfa_webauthn_register(
Returns recovery codes when MFA is newly enabled (they were never issued). Returns recovery codes when MFA is newly enabled (they were never issued).
""" """
from datetime import datetime, timezone from datetime import datetime
from .user_store import get_user, update_user from .user_store import get_user, update_user
from .webauthn_mfa import complete_registration, resolve_relying_party from .webauthn_mfa import complete_registration, resolve_relying_party
@@ -716,7 +717,7 @@ async def mfa_webauthn_register(
logger.warning(f"WebAuthn registration failed for {current_user['username']}: {e}") logger.warning(f"WebAuthn registration failed for {current_user['username']}: {e}")
raise HTTPException(400, "Validation du credential WebAuthn échouée") raise HTTPException(400, "Validation du credential WebAuthn échouée")
record["registered_at"] = datetime.now(timezone.utc).isoformat() record["registered_at"] = datetime.now(UTC).isoformat()
creds = list(user.get("webauthn_credentials", [])) creds = list(user.get("webauthn_credentials", []))
creds = [c for c in creds if c.get("credential_id") != record["credential_id"]] creds = [c for c in creds if c.get("credential_id") != record["credential_id"]]
creds.append(record) creds.append(record)
+7 -7
View File
@@ -8,7 +8,7 @@ import logging
import shutil import shutil
import threading import threading
import uuid import uuid
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
from .password import hash_password from .password import hash_password
@@ -97,8 +97,8 @@ def create_user(
"active": True, "active": True,
"language": "fr", # default UI language "language": "fr", # default UI language
"avatar": None, # profile picture data-URL (#113) "avatar": None, # profile picture data-URL (#113)
"created_at": datetime.now(timezone.utc).isoformat(), "created_at": datetime.now(UTC).isoformat(),
"password_changed_at": datetime.now(timezone.utc).timestamp(), "password_changed_at": datetime.now(UTC).timestamp(),
"last_login": None, "last_login": None,
"failed_attempts": 0, "failed_attempts": 0,
"locked_until": None, "locked_until": None,
@@ -126,7 +126,7 @@ def update_user(username: str, updates: dict) -> dict:
if "password" in safe_updates: if "password" in safe_updates:
safe_updates["password_hash"] = hash_password(safe_updates.pop("password")) safe_updates["password_hash"] = hash_password(safe_updates.pop("password"))
# BUG-028: invalidate every token issued before this change. # BUG-028: invalidate every token issued before this change.
safe_updates["password_changed_at"] = datetime.now(timezone.utc).timestamp() safe_updates["password_changed_at"] = datetime.now(UTC).timestamp()
data["users"][username].update(safe_updates) data["users"][username].update(safe_updates)
_write(data) _write(data)
@@ -148,7 +148,7 @@ def delete_user(username: str):
def record_login_success(username: str): def record_login_success(username: str):
"""Record a successful login: update last_login, reset failed attempts.""" """Record a successful login: update last_login, reset failed attempts."""
update_user(username, { update_user(username, {
"last_login": datetime.now(timezone.utc).isoformat(), "last_login": datetime.now(UTC).isoformat(),
"failed_attempts": 0, "failed_attempts": 0,
"locked_until": None, "locked_until": None,
}) })
@@ -171,7 +171,7 @@ def record_login_failure(username: str) -> int:
# Lock after 5 failed attempts (15 minutes) # Lock after 5 failed attempts (15 minutes)
if attempts >= 5: if attempts >= 5:
locked_until = ( locked_until = (
datetime.now(timezone.utc) + timedelta(minutes=15) datetime.now(UTC) + timedelta(minutes=15)
).isoformat() ).isoformat()
updates["locked_until"] = locked_until updates["locked_until"] = locked_until
logger.warning(f"Account '{username}' locked after {attempts} failed attempts") logger.warning(f"Account '{username}' locked after {attempts} failed attempts")
@@ -189,7 +189,7 @@ def is_locked(username: str) -> bool:
if not user or not user.get("locked_until"): if not user or not user.get("locked_until"):
return False return False
locked_until = datetime.fromisoformat(user["locked_until"]) locked_until = datetime.fromisoformat(user["locked_until"])
if datetime.now(timezone.utc) > locked_until: if datetime.now(UTC) > locked_until:
# Lock expired — unlock # Lock expired — unlock
update_user(username, {"locked_until": None, "failed_attempts": 0}) update_user(username, {"locked_until": None, "failed_attempts": 0})
return False return False
+1 -1
View File
@@ -591,7 +591,7 @@ async def api_bookslm_agent(
while True: while True:
try: try:
kind, item = await asyncio.wait_for(queue.get(), timeout=0.25) kind, item = await asyncio.wait_for(queue.get(), timeout=0.25)
except asyncio.TimeoutError: except TimeoutError:
if run_task.done(): if run_task.done():
break break
continue continue
+2 -2
View File
@@ -177,7 +177,7 @@ def unwrap_dek(wrapped: dict, kek: bytes, coffre_id: str) -> bytes:
def encrypt_secret_value(dek: bytes, secret_id: str, version: int, valeur: str) -> dict: def encrypt_secret_value(dek: bytes, secret_id: str, version: int, valeur: str) -> dict:
"""Chiffre une valeur de secret (AAD = secret_id:version).""" """Chiffre une valeur de secret (AAD = secret_id:version)."""
aad = f"{secret_id}:{version}".encode("utf-8") aad = f"{secret_id}:{version}".encode()
return encrypt_with_key(dek, valeur.encode("utf-8"), aad) return encrypt_with_key(dek, valeur.encode("utf-8"), aad)
@@ -185,7 +185,7 @@ def decrypt_secret_value(
dek: bytes, secret_id: str, version: int, nonce_b64: str, ciphertext_b64: str dek: bytes, secret_id: str, version: int, nonce_b64: str, ciphertext_b64: str
) -> str: ) -> str:
"""Déchiffre une valeur de secret.""" """Déchiffre une valeur de secret."""
aad = f"{secret_id}:{version}".encode("utf-8") aad = f"{secret_id}:{version}".encode()
return decrypt_with_key(dek, nonce_b64, ciphertext_b64, aad).decode("utf-8") return decrypt_with_key(dek, nonce_b64, ciphertext_b64, aad).decode("utf-8")
+7 -8
View File
@@ -40,19 +40,18 @@ import re
import secrets as _secrets import secrets as _secrets
import threading import threading
import uuid import uuid
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
from backend.coffre_crypto import ( from backend.coffre_crypto import (
KEKAbsenteError, decrypt_secret_value,
encrypt_secret_value,
generate_dek, generate_dek,
load_kek, load_kek,
parse_kek, parse_kek,
reencrypt_secret_value, reencrypt_secret_value,
unwrap_dek, unwrap_dek,
wrap_dek, wrap_dek,
encrypt_secret_value,
decrypt_secret_value,
) )
logger = logging.getLogger("obsigate.coffre_store") logger = logging.getLogger("obsigate.coffre_store")
@@ -123,7 +122,7 @@ def _valeurs_path(base: Path) -> Path:
def _now_iso() -> str: def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat() return datetime.now(UTC).isoformat()
def _parse_date(value: str | None) -> datetime | None: def _parse_date(value: str | None) -> datetime | None:
@@ -140,7 +139,7 @@ def _parse_date(value: str | None) -> datetime | None:
except ValueError as exc: except ValueError as exc:
raise ValidationError(f"Date invalide : {value!r}") from exc raise ValidationError(f"Date invalide : {value!r}") from exc
if parsed.tzinfo is None: if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc) parsed = parsed.replace(tzinfo=UTC)
return parsed return parsed
@@ -149,7 +148,7 @@ def est_expire(expiration: str | None, maintenant: datetime | None = None) -> bo
parsed = _parse_date(expiration) parsed = _parse_date(expiration)
if parsed is None: if parsed is None:
return False return False
ref = maintenant or datetime.now(timezone.utc) ref = maintenant or datetime.now(UTC)
return parsed <= ref return parsed <= ref
@@ -766,7 +765,7 @@ def create_octroi(
): ):
raise IntrouvableError("Secret introuvable dans ce coffre") raise IntrouvableError("Secret introuvable dans ce coffre")
if expiration is None: if expiration is None:
expiration = (datetime.now(timezone.utc) + timedelta(days=DUREE_OCTROI_DEFAUT_JOURS)).isoformat() expiration = (datetime.now(UTC) + timedelta(days=DUREE_OCTROI_DEFAUT_JOURS)).isoformat()
else: else:
_parse_date(expiration) _parse_date(expiration)
with _lock: with _lock:
+2 -2
View File
@@ -359,7 +359,7 @@ def export_epub(vault_path: Path, file_path: Path) -> bytes:
rendered = _render_body(body, path.parent, vault_path, path) rendered = _render_body(body, path.parent, vault_path, path)
uid = f"obsigate-{_slugify(title)}-{int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())}" uid = f"obsigate-{_slugify(title)}-{int(datetime.datetime.now(tz=datetime.UTC).timestamp())}"
xhtml = f"""<?xml version="1.0" encoding="utf-8"?> xhtml = f"""<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
@@ -383,7 +383,7 @@ def export_epub(vault_path: Path, file_path: Path) -> bytes:
<dc:title>{html_mod.escape(title)}</dc:title> <dc:title>{html_mod.escape(title)}</dc:title>
<dc:creator>{html_mod.escape(author)}</dc:creator> <dc:creator>{html_mod.escape(author)}</dc:creator>
<dc:language>fr</dc:language> <dc:language>fr</dc:language>
<meta property="dcterms:modified">{datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}</meta> <meta property="dcterms:modified">{datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%dT%H:%M:%SZ')}</meta>
</metadata> </metadata>
<manifest> <manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/> <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
+1 -1
View File
@@ -355,7 +355,7 @@ def _export_footer(lang: str) -> str:
template = loc.get("guide105.export_footer", "") template = loc.get("guide105.export_footer", "")
if "%s" not in template and "{" not in template: if "%s" not in template and "{" not in template:
template = "ObsiGate {version}" template = "ObsiGate {version}"
today = datetime.datetime.now(tz=datetime.timezone.utc).date().isoformat() today = datetime.datetime.now(tz=datetime.UTC).date().isoformat()
return _collapse(template).format(version=_app_version(), date=today) return _collapse(template).format(version=_app_version(), date=today)
+4 -4
View File
@@ -5,7 +5,7 @@ import os
import re import re
import threading import threading
from collections.abc import Callable from collections.abc import Callable
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -581,7 +581,7 @@ def _scan_vault(
try: try:
relative = fpath.relative_to(vault_root) relative = fpath.relative_to(vault_root)
stat = fpath.stat() stat = fpath.stat()
modified = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat() modified = datetime.fromtimestamp(stat.st_mtime, tz=UTC).isoformat()
# #86 differential scan: reuse the previous entry when neither # #86 differential scan: reuse the previous entry when neither
# size nor mtime changed — skips the disk read + parse below. # size nor mtime changed — skips the disk read + parse below.
@@ -866,7 +866,7 @@ async def build_index(progress_callback=None) -> None:
# Record timestamp of full index rebuild # Record timestamp of full index rebuild
global _last_full_index_ts global _last_full_index_ts
_last_full_index_ts = datetime.now(timezone.utc).isoformat() _last_full_index_ts = datetime.now(UTC).isoformat()
# Build attachment index # Build attachment index
from backend.attachment_indexer import build_attachment_index from backend.attachment_indexer import build_attachment_index
@@ -1033,7 +1033,7 @@ def _index_single_file_sync(vault_name: str, vault_path: str, file_path: str, va
return None return None
stat = fpath.stat() stat = fpath.stat()
modified = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat() modified = datetime.fromtimestamp(stat.st_mtime, tz=UTC).isoformat()
# PDF handling — binary, must go through pdf_reader (same as _scan_vault) # PDF handling — binary, must go through pdf_reader (same as _scan_vault)
tags: list[str] = [] tags: list[str] = []
+4 -4
View File
@@ -25,7 +25,7 @@ import os
import smtplib import smtplib
import threading import threading
import uuid import uuid
from datetime import datetime, timezone from datetime import UTC, datetime
from email.message import EmailMessage from email.message import EmailMessage
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -182,7 +182,7 @@ def create_channel(name: str, channel_type: str, config: dict[str, Any]) -> dict
"type": channel_type, "type": channel_type,
"enabled": True, "enabled": True,
"config": normalized, "config": normalized,
"created_at": datetime.now(timezone.utc).isoformat(), "created_at": datetime.now(UTC).isoformat(),
"last_sent_at": None, "last_sent_at": None,
"last_error": None, "last_error": None,
} }
@@ -287,7 +287,7 @@ def _send_webhook(url: str, title: str, message: str, trigger: str) -> None:
"event": trigger, "event": trigger,
"title": title, "title": title,
"message": message, "message": message,
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"source": "obsigate-notify", "source": "obsigate-notify",
}, },
timeout=10.0, timeout=10.0,
@@ -345,6 +345,6 @@ def _mark_sent(channel_id: str, error: str | None) -> None:
channels = _read_channels() channels = _read_channels()
for channel in channels: for channel in channels:
if channel.get("id") == channel_id: if channel.get("id") == channel_id:
channel["last_sent_at"] = datetime.now(timezone.utc).isoformat() channel["last_sent_at"] = datetime.now(UTC).isoformat()
channel["last_error"] = error channel["last_error"] = error
_write_channels(channels) _write_channels(channels)
+2 -2
View File
@@ -6,7 +6,7 @@ import base64
import json import json
import logging import logging
import os import os
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -185,7 +185,7 @@ async def subscribe_push(
"keys": request.subscription.keys, "keys": request.subscription.keys,
"vault": request.vault, "vault": request.vault,
"username": username, "username": username,
"created_at": datetime.now(timezone.utc).isoformat() "created_at": datetime.now(UTC).isoformat()
} }
_push_subscriptions.append(subscription) _push_subscriptions.append(subscription)
+2 -2
View File
@@ -19,7 +19,7 @@ Adaptations strictement équivalentes :
import logging import logging
import os import os
import time import time
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -207,7 +207,7 @@ async def api_backups_list(
"file": rel_file, "file": rel_file,
"backup_file": fpath.name, "backup_file": fpath.name,
"timestamp": ts, "timestamp": ts,
"datetime": datetime.fromtimestamp(ts, tz=timezone.utc).isoformat(), "datetime": datetime.fromtimestamp(ts, tz=UTC).isoformat(),
"size": fsize, "size": fsize,
"full_path": str(fpath), "full_path": str(fpath),
}) })
+1 -1
View File
@@ -49,7 +49,7 @@ async def api_events(current_user=Depends(require_auth)):
try: try:
msg = await asyncio.wait_for(queue.get(), timeout=30.0) msg = await asyncio.wait_for(queue.get(), timeout=30.0)
yield f"event: {msg['event']}\ndata: {msg['data']}\n\n" yield f"event: {msg['event']}\ndata: {msg['data']}\n\n"
except asyncio.TimeoutError: except TimeoutError:
# Keepalive comment # Keepalive comment
yield ": keepalive\n\n" yield ": keepalive\n\n"
except asyncio.CancelledError: except asyncio.CancelledError:
+12 -12
View File
@@ -26,7 +26,7 @@ import logging
import os import os
import threading import threading
import uuid import uuid
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -118,7 +118,7 @@ def _validate_schedule(schedule: dict[str, Any]) -> dict[str, Any]:
try: try:
parsed = datetime.fromisoformat(at) parsed = datetime.fromisoformat(at)
if parsed.tzinfo is None: if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc) parsed = parsed.replace(tzinfo=UTC)
except ValueError: except ValueError:
raise ValueError("at doit être une date ISO-8601 (ex. 2026-10-05T08:30:00)") from None raise ValueError("at doit être une date ISO-8601 (ex. 2026-10-05T08:30:00)") from None
return {"kind": kind, "at": parsed.isoformat()} return {"kind": kind, "at": parsed.isoformat()}
@@ -138,7 +138,7 @@ def create_task(
"""Create a scheduled task. Raises ValueError on invalid action/schedule.""" """Create a scheduled task. Raises ValueError on invalid action/schedule."""
validated_action = _validate_action(action) validated_action = _validate_action(action)
validated_schedule = _validate_schedule(schedule) validated_schedule = _validate_schedule(schedule)
now = datetime.now(timezone.utc) now = datetime.now(UTC)
with _lock: with _lock:
tasks = _read_tasks() tasks = _read_tasks()
task = { task = {
@@ -200,9 +200,9 @@ def delete_task(task_id: str) -> bool:
def compute_next_run(task: dict[str, Any], now: datetime | None = None) -> datetime: def compute_next_run(task: dict[str, Any], now: datetime | None = None) -> datetime:
"""Compute the next due datetime for *task*.""" """Compute the next due datetime for *task*."""
now = now or datetime.now(timezone.utc) now = now or datetime.now(UTC)
if now.tzinfo is None: if now.tzinfo is None:
now = now.replace(tzinfo=timezone.utc) now = now.replace(tzinfo=UTC)
schedule = task.get("schedule", {}) schedule = task.get("schedule", {})
kind = schedule.get("kind") kind = schedule.get("kind")
last_run_at = task.get("last_run_at") last_run_at = task.get("last_run_at")
@@ -211,7 +211,7 @@ def compute_next_run(task: dict[str, Any], now: datetime | None = None) -> datet
try: try:
last = datetime.fromisoformat(str(last_run_at)) last = datetime.fromisoformat(str(last_run_at))
if last.tzinfo is None: if last.tzinfo is None:
last = last.replace(tzinfo=timezone.utc) last = last.replace(tzinfo=UTC)
except ValueError: except ValueError:
last = None last = None
if kind == "interval_hours": if kind == "interval_hours":
@@ -233,11 +233,11 @@ def compute_next_run(task: dict[str, Any], now: datetime | None = None) -> datet
try: try:
at = datetime.fromisoformat(str(schedule.get("at"))) at = datetime.fromisoformat(str(schedule.get("at")))
if at.tzinfo is None: if at.tzinfo is None:
at = at.replace(tzinfo=timezone.utc) at = at.replace(tzinfo=UTC)
except ValueError: except ValueError:
return now return now
if task.get("last_run_at"): if task.get("last_run_at"):
return datetime.max.replace(tzinfo=timezone.utc) # déjà exécutée return datetime.max.replace(tzinfo=UTC) # déjà exécutée
return at return at
return now + timedelta(hours=24) return now + timedelta(hours=24)
@@ -278,7 +278,7 @@ def run_task(task_id: str, *, manual: bool = False) -> dict[str, Any]:
return {"task_id": task_id, "skipped": True, "reason": "disabled"} return {"task_id": task_id, "skipped": True, "reason": "disabled"}
try: try:
result = _execute_action(task) result = _execute_action(task)
task["last_run_at"] = datetime.now(timezone.utc).isoformat() task["last_run_at"] = datetime.now(UTC).isoformat()
task["last_status"] = "ok" task["last_status"] = "ok"
task["last_error"] = None task["last_error"] = None
task["run_count"] = int(task.get("run_count", 0)) + 1 task["run_count"] = int(task.get("run_count", 0)) + 1
@@ -292,7 +292,7 @@ def run_task(task_id: str, *, manual: bool = False) -> dict[str, Any]:
broadcast("schedule_success", f"Tâche « {task['name']} » OK", "Exécution manuelle réussie.") broadcast("schedule_success", f"Tâche « {task['name']} » OK", "Exécution manuelle réussie.")
return {"task_id": task_id, "ok": True, "result": result} return {"task_id": task_id, "ok": True, "result": result}
except Exception as e: except Exception as e:
task["last_run_at"] = datetime.now(timezone.utc).isoformat() task["last_run_at"] = datetime.now(UTC).isoformat()
task["last_status"] = "error" task["last_status"] = "error"
task["last_error"] = str(e) task["last_error"] = str(e)
task["run_count"] = int(task.get("run_count", 0)) + 1 task["run_count"] = int(task.get("run_count", 0)) + 1
@@ -315,7 +315,7 @@ def run_task(task_id: str, *, manual: bool = False) -> dict[str, Any]:
def tick(now: datetime | None = None) -> list[dict[str, Any]]: def tick(now: datetime | None = None) -> list[dict[str, Any]]:
"""Run every due task. Returns per-task outcomes (empty when idle).""" """Run every due task. Returns per-task outcomes (empty when idle)."""
now = now or datetime.now(timezone.utc) now = now or datetime.now(UTC)
outcomes: list[dict[str, Any]] = [] outcomes: list[dict[str, Any]] = []
for task in _read_tasks(): for task in _read_tasks():
if not task.get("enabled", True): if not task.get("enabled", True):
@@ -323,7 +323,7 @@ def tick(now: datetime | None = None) -> list[dict[str, Any]]:
try: try:
next_run = datetime.fromisoformat(str(task.get("next_run_at") or "")) next_run = datetime.fromisoformat(str(task.get("next_run_at") or ""))
if next_run.tzinfo is None: if next_run.tzinfo is None:
next_run = next_run.replace(tzinfo=timezone.utc) next_run = next_run.replace(tzinfo=UTC)
except ValueError: except ValueError:
next_run = compute_next_run(task, now) next_run = compute_next_run(task, now)
if next_run <= now: if next_run <= now:
+3 -3
View File
@@ -14,7 +14,7 @@ import logging
import os import os
import shutil import shutil
import time import time
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -145,7 +145,7 @@ def list_backup_files(vault_name: str, relative_path: str) -> list[dict[str, Any
except (ValueError, TypeError): except (ValueError, TypeError):
continue continue
try: try:
dt = datetime.fromtimestamp(ts, tz=timezone.utc).isoformat() dt = datetime.fromtimestamp(ts, tz=UTC).isoformat()
except (OSError, OverflowError, ValueError) as ts_err: except (OSError, OverflowError, ValueError) as ts_err:
logger.warning(f"Skipping backup with invalid timestamp {ts}: {ts_err}") logger.warning(f"Skipping backup with invalid timestamp {ts}: {ts_err}")
continue continue
@@ -219,7 +219,7 @@ def diff_backup(
status=500, status=500,
details={"vault": vault_name, "path": path, "version": ts}, details={"vault": vault_name, "path": path, "version": ts},
) from e ) from e
dt = datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") dt = datetime.fromtimestamp(ts, tz=UTC).strftime("%Y-%m-%d %H:%M:%S UTC")
return content, f"{path}@{dt}" return content, f"{path}@{dt}"
try: try:
+1 -1
View File
@@ -117,7 +117,7 @@ def list_recent(
for v_name, f in recent: for v_name, f in recent:
iso_modified = f.get("modified", "") iso_modified = f.get("modified", "")
try: try:
mtime_dt = datetime.fromisoformat(iso_modified.replace("Z", "+00:00")) mtime_dt = datetime.fromisoformat(iso_modified)
mtime_val = mtime_dt.timestamp() mtime_val = mtime_dt.timestamp()
except Exception: except Exception:
mtime_val = time.time() mtime_val = time.time()
+5 -4
View File
@@ -7,6 +7,7 @@ Single source of truth consumed by both the REST routes (``/api/vaults``,
from __future__ import annotations from __future__ import annotations
from datetime import UTC
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -49,7 +50,7 @@ def get_vault_root(vault_name: str) -> Path:
def browse_directory(vault_name: str, path: str = "") -> dict[str, Any]: def browse_directory(vault_name: str, path: str = "") -> dict[str, Any]:
"""Return the direct children of a vault directory (directories first).""" """Return the direct children of a vault directory (directories first)."""
from datetime import datetime, timezone from datetime import datetime
from backend.indexer import SUPPORTED_EXTENSIONS from backend.indexer import SUPPORTED_EXTENSIONS
from backend.vault_settings import get_vault_setting from backend.vault_settings import get_vault_setting
@@ -98,7 +99,7 @@ def browse_directory(vault_name: str, path: str = "") -> dict[str, Any]:
"type": "file", "type": "file",
"size": stat.st_size, "size": stat.st_size,
"modified": stat.st_mtime, "modified": stat.st_mtime,
"modified_iso": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat(), "modified_iso": datetime.fromtimestamp(stat.st_mtime, tz=UTC).isoformat(),
"extension": entry.suffix.lower(), "extension": entry.suffix.lower(),
}) })
except PermissionError: except PermissionError:
@@ -156,7 +157,7 @@ def list_all_files(
ServiceError: ``not_found`` (404) if the directory does not exist, ServiceError: ``not_found`` (404) if the directory does not exist,
``permission_denied`` (403) if it cannot be read. ``permission_denied`` (403) if it cannot be read.
""" """
from datetime import datetime, timezone from datetime import datetime
from backend.indexer import IGNORED_DIRS from backend.indexer import IGNORED_DIRS
@@ -215,7 +216,7 @@ def list_all_files(
"vault": vault_name, "vault": vault_name,
"size": stat.st_size, "size": stat.st_size,
"modified": stat.st_mtime, "modified": stat.st_mtime,
"modified_iso": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat(), "modified_iso": datetime.fromtimestamp(stat.st_mtime, tz=UTC).isoformat(),
"extension": ext.lstrip(".") if ext else "", "extension": ext.lstrip(".") if ext else "",
"tags": _indexed_tags(vault_name, rel_path), "tags": _indexed_tags(vault_name, rel_path),
} }
+5 -5
View File
@@ -11,7 +11,7 @@ import json
import logging import logging
import secrets import secrets
import threading import threading
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
logger = logging.getLogger("obsigate.share") logger = logging.getLogger("obsigate.share")
@@ -58,7 +58,7 @@ def create_share(
expires_at = None expires_at = None
if expires_in_hours: if expires_in_hours:
expires_at = (datetime.now(timezone.utc) + timedelta(hours=expires_in_hours)).isoformat() expires_at = (datetime.now(UTC) + timedelta(hours=expires_in_hours)).isoformat()
share = { share = {
"id": token, "id": token,
@@ -66,7 +66,7 @@ def create_share(
"vault": vault, "vault": vault,
"path": path, "path": path,
"created_by": created_by, "created_by": created_by,
"created_at": datetime.now(timezone.utc).isoformat(), "created_at": datetime.now(UTC).isoformat(),
"expires_at": expires_at, "expires_at": expires_at,
"access_count": 0, "access_count": 0,
"last_accessed": None, "last_accessed": None,
@@ -86,7 +86,7 @@ def get_share_by_token(token: str) -> dict | None:
return None return None
if share.get("expires_at"): if share.get("expires_at"):
expires = datetime.fromisoformat(share["expires_at"]) expires = datetime.fromisoformat(share["expires_at"])
if datetime.now(timezone.utc) > expires: if datetime.now(UTC) > expires:
return None return None
return share return share
@@ -98,7 +98,7 @@ def record_access(token: str):
share = data["shares"].get(token) share = data["shares"].get(token)
if share: if share:
share["access_count"] = share.get("access_count", 0) + 1 share["access_count"] = share.get("access_count", 0) + 1
share["last_accessed"] = datetime.now(timezone.utc).isoformat() share["last_accessed"] = datetime.now(UTC).isoformat()
_write(data) _write(data)
+2 -2
View File
@@ -7,7 +7,7 @@ Reuses the application audit log (``data/audit.log``, JSON lines) and adds an
from __future__ import annotations from __future__ import annotations
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import Any from typing import Any
from backend.audit import _write_entry from backend.audit import _write_entry
@@ -41,7 +41,7 @@ def log_tool_call(
) -> None: ) -> None:
"""Append an ``ai_tool_call`` entry to the audit log.""" """Append an ``ai_tool_call`` entry to the audit log."""
_write_entry({ _write_entry({
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"action": "ai_tool_call", "action": "ai_tool_call",
"username": username, "username": username,
"ip": ip or "unknown", "ip": ip or "unknown",
+1 -1
View File
@@ -244,7 +244,7 @@ class VaultWatcher:
key = event.get('dest') or event['src'] key = event.get('dest') or event['src']
pending[key] = event pending[key] = event
except asyncio.TimeoutError: except TimeoutError:
if pending: if pending:
events_to_process = list(pending.values()) events_to_process = list(pending.values())
pending.clear() pending.clear()
+4 -4
View File
@@ -31,7 +31,7 @@ import os
import socket import socket
import threading import threading
import uuid import uuid
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from urllib.parse import urlparse from urllib.parse import urlparse
@@ -275,7 +275,7 @@ def create_webhook(
"url": url, "url": url,
"events": [e for e in events if e in VALID_EVENTS], "events": [e for e in events if e in VALID_EVENTS],
"enabled": True, "enabled": True,
"created_at": datetime.now(timezone.utc).isoformat(), "created_at": datetime.now(UTC).isoformat(),
"last_fired_at": None, "last_fired_at": None,
# #198 : filtres optionnels (None = pas de filtre, reçoit tout). # #198 : filtres optionnels (None = pas de filtre, reçoit tout).
"vault": _normalize_vault_filter(vault), "vault": _normalize_vault_filter(vault),
@@ -344,7 +344,7 @@ async def dispatch_webhooks(event_type: str, data: dict):
payload = { payload = {
"event": event_type, "event": event_type,
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"data": data, "data": data,
} }
body = json.dumps(payload, default=str) body = json.dumps(payload, default=str)
@@ -369,7 +369,7 @@ async def dispatch_webhooks(event_type: str, data: dict):
logger.debug(f"Webhook '{wh['name']}' OK ({resp.status})") logger.debug(f"Webhook '{wh['name']}' OK ({resp.status})")
else: else:
logger.warning(f"Webhook '{wh['name']}' failed ({resp.status})") logger.warning(f"Webhook '{wh['name']}' failed ({resp.status})")
update_webhook(wh["id"], {"last_fired_at": datetime.now(timezone.utc).isoformat()}) update_webhook(wh["id"], {"last_fired_at": datetime.now(UTC).isoformat()})
except Exception as e: except Exception as e:
logger.warning(f"Webhook '{wh['name']}' error: {e}") logger.warning(f"Webhook '{wh['name']}' error: {e}")
+1 -1
View File
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]] [[package]]
name = "obsigate-desktop" name = "obsigate-desktop"
version = "2.79.0" version = "2.80.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"env_logger", "env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "obsigate-desktop" name = "obsigate-desktop"
version = "2.79.0" version = "2.80.1"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"] authors = ["Bruno Charest"]
edition = "2021" edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate", "productName": "ObsiGate",
"version": "2.79.0", "version": "2.80.1",
"identifier": "com.obsigate.desktop", "identifier": "com.obsigate.desktop",
"build": { "build": {
"frontendDist": "../frontend", "frontendDist": "../frontend",
+1 -1
View File
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap # ObsiGate — Roadmap
> **Version :** 2.79.0 | **Dernière mise à jour :** 2026-10-26 > **Version :** 2.80.1 | **Dernière mise à jour :** 2026-10-27
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
> vers les fonctionnalités livrées. > vers les fonctionnalités livrées.
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "obsigate", "name": "obsigate",
"version": "2.79.0", "version": "2.80.1",
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.", "description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
"main": "patch.js", "main": "patch.js",
"directories": { "directories": {