mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
fix(history): stop reporting the device IP on every change (BEA-43) (#81)
* fix(history): stop reporting the device IP on every change (BEA-43) The history API embedded the whole DeviceInfo, so every project member read a teammate's server-observed IP (plus user/last_seen) next to every change on a page whose job is "who changed this file". Project a three-field historyDevice instead — id/name/os — mirroring heatByDevice. The device registry is unchanged: Observe/requestIP and both MetaStore backends keep recording the IP. This is a response projection, not a change in what gets collected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: history shows device name and OS, not the IP (BEA-43) README, SKILL.md and the docs site all promised the History view would show the connecting IP. It no longer does — the registry still records it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
af9ef4d181
commit
b408e004b3
@@ -11,8 +11,9 @@ import (
|
||||
// DeviceInfo is what the server knows about one syncing device: self-reported
|
||||
// name/OS (headers sent by the client), plus what the server itself observed
|
||||
// (public IP of the last push, last activity, the signed-in account). History
|
||||
// joins ops against this registry, so IPs are real — as the server saw them —
|
||||
// and ops stay small.
|
||||
// joins ops against this registry so ops stay small — but it reports only
|
||||
// id/name/os (historyDevice, history.go): the IP is recorded here, not
|
||||
// repeated to every project member on every change.
|
||||
type DeviceInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
@@ -158,7 +158,6 @@ export interface DeviceInfo {
|
||||
id?: string;
|
||||
name?: string;
|
||||
os?: string;
|
||||
ip?: string;
|
||||
}
|
||||
export interface HistoryEntry {
|
||||
time: string;
|
||||
|
||||
@@ -85,7 +85,7 @@ export function HistoryRow({
|
||||
const [diffOpen, setDiffOpen] = useState(false);
|
||||
const kind = e.kind === "put" ? "edit" : e.kind; // older servers report raw "put" ops
|
||||
const who = whoChanged(e);
|
||||
const dev = [e.device.name || e.device.id, e.device.os, e.device.ip].filter(Boolean).join(" · ");
|
||||
const dev = [e.device.name || e.device.id, e.device.os].filter(Boolean).join(" · ");
|
||||
const clickable = kind !== "delete";
|
||||
// A delete has no content, and a first version has nothing behind it.
|
||||
const diffable = !!diff && kind !== "delete" && !!e.blob;
|
||||
|
||||
+26
-13
@@ -19,18 +19,29 @@ import (
|
||||
// the revert/rollback phase, where restoring is just writing an old blob
|
||||
// back as a new op.
|
||||
|
||||
// historyDevice is the slice of the device registry history is allowed to
|
||||
// report: who/what made the change, not where they connected from. The
|
||||
// registry keeps observing and persisting the IP (devices.go) — it just
|
||||
// doesn't ride along here, where every project member reads it. Mirrors
|
||||
// heatByDevice (reads.go).
|
||||
type historyDevice struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
OS string `json:"os,omitempty"`
|
||||
}
|
||||
|
||||
// HistoryEntry is one change as the history API reports it.
|
||||
type HistoryEntry struct {
|
||||
Time string `json:"time"`
|
||||
Kind string `json:"kind"` // add | edit | delete
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Blob string `json:"blob,omitempty"` // sha256; fetch via the blob endpoint
|
||||
User string `json:"user,omitempty"`
|
||||
UserName string `json:"user_name,omitempty"`
|
||||
Author string `json:"author,omitempty"` // offline/git fallback identity
|
||||
Device DeviceInfo `json:"device"`
|
||||
Note string `json:"note,omitempty"`
|
||||
Time string `json:"time"`
|
||||
Kind string `json:"kind"` // add | edit | delete
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Blob string `json:"blob,omitempty"` // sha256; fetch via the blob endpoint
|
||||
User string `json:"user,omitempty"`
|
||||
UserName string `json:"user_name,omitempty"`
|
||||
Author string `json:"author,omitempty"` // offline/git fallback identity
|
||||
Device historyDevice `json:"device"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
// handleHistory serves ?path=<file> (one file's versions) or
|
||||
@@ -92,9 +103,11 @@ func (s *Server) handleHistory(v *volume, w http.ResponseWriter, r *http.Request
|
||||
case path == "" && prefix != "" && !strings.HasPrefix(op.Path, strings.TrimSuffix(prefix, "/")+"/"):
|
||||
continue
|
||||
}
|
||||
dev, _ := s.Devices.Get(op.Device)
|
||||
if dev.ID == "" {
|
||||
dev = DeviceInfo{ID: op.Device, Name: op.DeviceName}
|
||||
// Unregistered device (or volume mode, where Devices is nil): fall back
|
||||
// to the op's own id + self-reported name.
|
||||
dev := historyDevice{ID: op.Device, Name: op.DeviceName}
|
||||
if info, ok := s.Devices.Get(op.Device); ok && info.ID != "" {
|
||||
dev = historyDevice{ID: info.ID, Name: info.Name, OS: info.OS}
|
||||
}
|
||||
matched = append(matched, timed{HistoryEntry{
|
||||
Time: op.Time.UTC().Format("2006-01-02T15:04:05Z"), Kind: kinds[i],
|
||||
|
||||
@@ -100,10 +100,23 @@ func TestHistoryAPI(t *testing.T) {
|
||||
if newest.User != "alice@x.io" || newest.UserName != "Alice" {
|
||||
t.Fatalf("user = %+v", newest)
|
||||
}
|
||||
// device joined from the registry: name, OS, server-observed IP
|
||||
if newest.Device.Name != "alice-laptop" || newest.Device.OS != "darwin/arm64" || newest.Device.IP != "203.0.113.7" {
|
||||
// device joined from the registry: name and OS only — the server-observed
|
||||
// IP stays in the registry and out of every member's history feed (BEA-43).
|
||||
// The id always rides along, so a nameless device still renders as something.
|
||||
if newest.Device.ID != "dev1" || newest.Device.Name != "alice-laptop" || newest.Device.OS != "darwin/arm64" {
|
||||
t.Fatalf("device = %+v", newest.Device)
|
||||
}
|
||||
// asserted on the raw body, not the struct: a typed unmarshal would pass
|
||||
// even if the server still emitted the key.
|
||||
if strings.Contains(rec.Body.String(), "203.0.113.7") {
|
||||
t.Fatalf("history response leaks the device IP: %s", rec.Body)
|
||||
}
|
||||
if strings.Contains(rec.Body.String(), "last_seen") {
|
||||
t.Fatalf("history response carries registry internals: %s", rec.Body)
|
||||
}
|
||||
if d, ok := srv.Devices.Get("dev1"); !ok || d.IP != "203.0.113.7" {
|
||||
t.Fatalf("registry must keep observing the IP: %+v %v", d, ok)
|
||||
}
|
||||
if newest.Blob == "" || oldest.Blob == "" {
|
||||
t.Fatal("entries must link to their exact content")
|
||||
}
|
||||
@@ -124,9 +137,13 @@ func TestHistoryAPI(t *testing.T) {
|
||||
t.Fatalf("entry before the delete = %+v, want other.md's add", out.Entries[1])
|
||||
}
|
||||
// a device the registry never saw falls back to the op's own info
|
||||
if out.Entries[0].Device.Name != "dev2" {
|
||||
if out.Entries[0].Device.ID != "dev2" || out.Entries[0].Device.Name != "dev2" {
|
||||
t.Fatalf("unknown device fallback = %+v", out.Entries[0].Device)
|
||||
}
|
||||
// the prefix feed is the same projection — no IP there either
|
||||
if strings.Contains(rec.Body.String(), "203.0.113.7") {
|
||||
t.Fatalf("prefix feed leaks the device IP: %s", rec.Body)
|
||||
}
|
||||
|
||||
// whole-project feed + n limit
|
||||
rec = do(t, h, "GET", base+"history?n=2", nil)
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>BearDrive</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='%23f5a623'><rect x='4' y='4' width='5.6' height='24'/><rect x='11.2' y='4' width='14.4' height='11.2'/><rect x='11.2' y='16.8' width='16.8' height='11.2'/></svg>">
|
||||
<script type="module" crossorigin src="/assets/index-CPLO3Qr5.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-344uOWlP.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CkrUL9C1.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user