fix: GetSessions scan robuste (sql.NullString) + formatDate adaptatif

- Scan des colonnes datetime via sql.NullString au lieu de time.Time pour éviter
  les échecs silencieux dus aux formats mixtes SQLite (CURRENT_TIMESTAMP vs RFC3339)
- Log explicite si un scan échoue (au lieu du continue silencieux)
- formatDate frontend adapte le format SQLite "YYYY-MM-DD HH:MM:SS" en ISO pour new Date()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
enzo 2026-03-22 01:39:37 +01:00
parent 95757124de
commit 1cbd7e9d17
2 changed files with 20 additions and 13 deletions

View file

@ -359,23 +359,27 @@ func (h *AuthHandler) GetSessions(w http.ResponseWriter, r *http.Request) {
defer rows.Close() defer rows.Close()
type Session struct { type Session struct {
ID int64 `json:"id"` ID int64 `json:"id"`
UserAgent string `json:"user_agent"` UserAgent string `json:"user_agent"`
IP string `json:"ip"` IP string `json:"ip"`
CreatedAt time.Time `json:"created_at"` CreatedAt string `json:"created_at"`
LastUsedAt *time.Time `json:"last_used_at"` LastUsedAt *string `json:"last_used_at"`
ExpiresAt time.Time `json:"expires_at"` ExpiresAt string `json:"expires_at"`
} }
sessions := []Session{} sessions := []Session{}
for rows.Next() { for rows.Next() {
var s Session var s Session
var lastUsed sql.NullTime var createdAt, expiresAt sql.NullString
if err := rows.Scan(&s.ID, &s.UserAgent, &s.IP, &s.CreatedAt, &lastUsed, &s.ExpiresAt); err != nil { var lastUsedAt sql.NullString
if err := rows.Scan(&s.ID, &s.UserAgent, &s.IP, &createdAt, &lastUsedAt, &expiresAt); err != nil {
log.Printf("[GetSessions] scan error userID=%d: %v", claims.UserID, err)
continue continue
} }
if lastUsed.Valid { s.CreatedAt = createdAt.String
s.LastUsedAt = &lastUsed.Time s.ExpiresAt = expiresAt.String
if lastUsedAt.Valid && lastUsedAt.String != "" {
s.LastUsedAt = &lastUsedAt.String
} }
sessions = append(sessions, s) sessions = append(sessions, s)
} }

View file

@ -686,9 +686,12 @@ document.addEventListener('alpine:init', () => {
this.revoking = { ...this.revoking, [id]: false } this.revoking = { ...this.revoking, [id]: false }
}, },
formatDate(iso) { formatDate(raw) {
if (!iso) return '—' if (!raw) return '—'
const d = new Date(iso) // SQLite retourne "YYYY-MM-DD HH:MM:SS" ou RFC3339 selon le driver
// new Date() gère les deux si on normalise le séparateur
const d = new Date(raw.includes('T') ? raw : raw.replace(' ', 'T') + 'Z')
if (isNaN(d.getTime())) return raw
return d.toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) return d.toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })
}, },