feat(audit): P3-08 — audit log UI with filters
This commit is contained in:
@@ -38,6 +38,38 @@ func (s *Store) GetUserByID(ctx context.Context, id string) (*User, error) {
|
||||
return scanUser(row)
|
||||
}
|
||||
|
||||
// ListUsers returns every user, sorted by username. Used by surfaces
|
||||
// that need to render a user-id → username map (audit log filter,
|
||||
// "ack'd by" projections).
|
||||
func (s *Store) ListUsers(ctx context.Context) ([]User, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT id, username, password_hash, role, created_at, last_login_at
|
||||
FROM users ORDER BY username`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store: list users: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
var out []User
|
||||
for rows.Next() {
|
||||
var u User
|
||||
var role string
|
||||
var lastLogin sql.NullString
|
||||
var created string
|
||||
if err := rows.Scan(&u.ID, &u.Username, &u.PasswordHash, &role, &created, &lastLogin); err != nil {
|
||||
return nil, fmt.Errorf("store: scan user row: %w", err)
|
||||
}
|
||||
u.Role = Role(role)
|
||||
t, _ := time.Parse(time.RFC3339Nano, created)
|
||||
u.CreatedAt = t
|
||||
if lastLogin.Valid {
|
||||
t, _ := time.Parse(time.RFC3339Nano, lastLogin.String)
|
||||
u.LastLoginAt = &t
|
||||
}
|
||||
out = append(out, u)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// CountUsers returns the total number of user rows. The first-run
|
||||
// bootstrap uses this to detect a fresh install.
|
||||
func (s *Store) CountUsers(ctx context.Context) (int, error) {
|
||||
|
||||
Reference in New Issue
Block a user