Commit abdeafa214

abdeafa2143636d262e1c9e77efbb2ed0ab3e9f3

parent: 921868375e

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-29 04:41 UTC

web: mark-read dispatches notifications read

Ref #261

Layout: unified · split

CHANGELOG.org +2
@@ -6,6 +6,8 @@ anything beyond "replace the binary and restart" is needed.
66
77* Unreleased
88
9- Marking notifications read from the web dispatches =notifications
10 read=, so it counts against the write budget and is audited (#261).
911- =gitbayd admin backup --verify= also checks every release asset the
1012 database names against its recorded size and sha256, and every
1113 archived LFS object against its name (#259).
internal/httpd/account_test.go +43
@@ -498,3 +498,46 @@ func TestAccountTokenCreateAuditOmitsToken(t *testing.T) {
498498 t.Fatal("no cmd token create audit row")
499499 }
500500}
501
502// Marking notices read goes through notifications read, so it carries the
503// audit trail and write budget of the CLI command (#261).
504func TestNotificationsReadDispatches(t *testing.T) {
505 st, err := store.Open(":memory:")
506 if err != nil {
507 t.Fatal(err)
508 }
509 defer st.Close()
510 if err := st.MigrateUp(); err != nil {
511 t.Fatal(err)
512 }
513 uid, err := st.CreateUser("alice", false)
514 if err != nil {
515 t.Fatal(err)
516 }
517 u := store.User{ID: uid, Username: "alice"}
518 repoID, err := st.CreateRepo("user", uid, "app", "public")
519 if err != nil {
520 t.Fatal(err)
521 }
522 for i := 0; i < 2; i++ {
523 if err := st.AddNotice(uid, repoID, "issue", "bob", "s", "alice/app/issues/1"); err != nil {
524 t.Fatal(err)
525 }
526 }
527 s := New(config.Default(), st, nil)
528
529 notices, _ := st.Inbox(uid, true, 10, 0)
530 req := httptest.NewRequest("POST", "/notifications/read", strings.NewReader("id="+strconv.FormatInt(notices[0].ID, 10)))
531 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
532 s.notificationsRead(httptest.NewRecorder(), req, u)
533 if n := st.UnreadNotices(uid); n != 1 {
534 t.Fatalf("unread after one id = %d, want 1", n)
535 }
536 assertAudited(t, st, "cmd notifications read")
537
538 req = httptest.NewRequest("POST", "/notifications/read", nil)
539 s.notificationsRead(httptest.NewRecorder(), req, u)
540 if n := st.UnreadNotices(uid); n != 0 {
541 t.Fatalf("unread after all = %d, want 0", n)
542 }
543}
internal/httpd/notifyweb.go +6 −8
@@ -38,20 +38,18 @@ func (s *Server) notifications(w http.ResponseWriter, r *http.Request, u store.U
3838}
3939
4040// notificationsRead marks one notice read, or the whole inbox when no id
41// is given, then returns to the list.
41// is given, through notifications read, then returns to the list (#261).
4242func (s *Server) notificationsRead(w http.ResponseWriter, r *http.Request, u store.User) {
43 var ids []int64
43 argv := []string{"notifications", "read", "--all"}
4444 if v := r.FormValue("id"); v != "" {
45 n, err := strconv.ParseInt(v, 10, 64)
46 if err != nil {
45 if _, err := strconv.ParseInt(v, 10, 64); err != nil {
4746 http.Error(w, "bad id", http.StatusBadRequest)
4847 return
4948 }
50 ids = append(ids, n)
49 argv = []string{"notifications", "read", v}
5150 }
52 if _, err := s.st.MarkNoticesRead(u.ID, ids); err != nil {
53 http.Error(w, "internal error", http.StatusInternalServerError)
54 return
51 if _, msg, ok := s.runControl(u, argv); !ok {
52 s.setFlash(w, msg)
5553 }
5654 http.Redirect(w, r, "/notifications", http.StatusSeeOther)
5755}