mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-01 17:38:15 +00:00
Show display name in audit log
This commit is contained in:
@@ -56,8 +56,11 @@ func (s *Store) MarkCommandReply(ctx context.Context, logID int64, response stri
|
||||
// CommandSession groups command-log entries by the console session they were
|
||||
// sent in.
|
||||
type CommandSession struct {
|
||||
ID int64
|
||||
SenderName string // who held the session
|
||||
ID int64
|
||||
// SenderName is who held the session: their current display name (or username),
|
||||
// falling back to the username snapshotted at session start if they've since
|
||||
// been deleted.
|
||||
SenderName string
|
||||
StartedAt time.Time
|
||||
EndedAt *time.Time
|
||||
Entries []*CommandLogEntry // newest first, matching the newest-first session order
|
||||
@@ -95,8 +98,9 @@ func (s *Store) ListCommandLogSessionsPage(ctx context.Context, repeaterID int64
|
||||
// behavior of the previous query).
|
||||
headers, err := s.pool.Query(ctx, `
|
||||
SELECT cs.id, cs.started_at, cs.ended_at,
|
||||
COALESCE(cs.sender_username, '(deleted)')
|
||||
COALESCE(NULLIF(u.display_name, ''), u.username, cs.sender_username, '(deleted)')
|
||||
FROM console_sessions cs
|
||||
LEFT JOIN users u ON u.id = cs.user_id
|
||||
WHERE cs.repeater_id = $1
|
||||
AND ($2 OR (cs.started_at, cs.id) < ($3, $4))
|
||||
AND EXISTS (SELECT 1 FROM command_log l WHERE l.session_id = cs.id)
|
||||
@@ -172,10 +176,11 @@ type OwnerCommandLogEntry struct {
|
||||
func (s *Store) ListRecentCommandsForOwner(ctx context.Context, ownerID int64, limit int) ([]OwnerCommandLogEntry, error) {
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT l.repeater_id, r.public_id, r.name,
|
||||
COALESCE(l.sender_username, '(deleted)'),
|
||||
COALESCE(NULLIF(u.display_name, ''), u.username, l.sender_username, '(deleted)'),
|
||||
l.command_text, l.sent_at, l.ack_received, l.response_text
|
||||
FROM command_log l
|
||||
JOIN repeaters r ON r.id = l.repeater_id
|
||||
LEFT JOIN users u ON u.id = l.user_id
|
||||
WHERE r.owner_id = $1
|
||||
ORDER BY l.sent_at DESC
|
||||
LIMIT $2`, ownerID, limit)
|
||||
|
||||
@@ -6,6 +6,58 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestCommandLogSenderName: the log shows the sender's live display name while
|
||||
// they exist, and falls back to the username snapshot once they're deleted.
|
||||
func TestCommandLogSenderName(t *testing.T) {
|
||||
t.Parallel()
|
||||
st, ctx := orgTestStore(t)
|
||||
|
||||
owner, err := st.CreateUser(ctx, "logowner2", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rep, err := st.CreateRepeater(ctx, &Repeater{
|
||||
OwnerID: owner.ID, Name: "R", PublicKeyHex: strings.Repeat("d", 64),
|
||||
RadioFreqHz: 1, RadioBwHz: 1, RadioSF: 11, RadioCR: 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A separate sender (not the owner, so deleting them doesn't cascade the repeater).
|
||||
sender, err := st.CreateUser(ctx, "bobby", "Bob Builder")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sid, err := st.StartConsoleSession(ctx, rep.ID, sender.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := st.LogCommand(ctx, rep.ID, sender.ID, sid, 0, "advert"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
senderName := func() string {
|
||||
page, _, err := st.ListCommandLogSessionsPage(ctx, rep.ID, nil)
|
||||
if err != nil || len(page) != 1 {
|
||||
t.Fatalf("page = %d sessions, %v; want 1", len(page), err)
|
||||
}
|
||||
return page[0].SenderName
|
||||
}
|
||||
|
||||
// While the sender exists: their display name.
|
||||
if got := senderName(); got != "Bob Builder" {
|
||||
t.Fatalf("sender name = %q, want the live display name %q", got, "Bob Builder")
|
||||
}
|
||||
|
||||
// After deletion (user_id → NULL): the username snapshot taken at session start.
|
||||
if _, err := st.pool.Exec(ctx, `DELETE FROM users WHERE id=$1`, sender.ID); err != nil {
|
||||
t.Fatalf("delete sender: %v", err)
|
||||
}
|
||||
if got := senderName(); got != "bobby" {
|
||||
t.Fatalf("deleted sender name = %q, want the username snapshot %q", got, "bobby")
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCommandLogSessionsPage walks the keyset-paginated session log and
|
||||
// checks every session with commands appears once, newest-first, across pages —
|
||||
// and that a session with no commands is omitted.
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
// StartConsoleSession records the start of a console session and returns its id.
|
||||
func (s *Store) StartConsoleSession(ctx context.Context, repeaterID, userID int64) (int64, error) {
|
||||
var id int64
|
||||
// Snapshot the username so the log stays a point-in-time record after renames.
|
||||
// Snapshot the username as a fallback so the log still identifies the sender if
|
||||
// they're later deleted; while they exist the log shows their live display name.
|
||||
err := s.pool.QueryRow(ctx,
|
||||
`INSERT INTO console_sessions (repeater_id, user_id, sender_username)
|
||||
VALUES ($1, $2, (SELECT username FROM users WHERE id = $2)) RETURNING id`,
|
||||
|
||||
Reference in New Issue
Block a user