• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DigitalTolk / ex / 25294202308

03 May 2026 11:42PM UTC coverage: 92.456% (-0.02%) from 92.471%
25294202308

push

github

web-flow
Notification fixes (#61)

* Notification fixes

* fix

* security

* fixes

2144 of 2380 branches covered (90.08%)

Branch coverage included in aggregate %.

484 of 542 new or added lines in 18 files covered. (89.3%)

2 existing lines in 1 file now uncovered.

12110 of 13037 relevant lines covered (92.89%)

1139.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.0
/internal/handler/thread.go
1
package handler
2

3
import (
4
        "net/http"
5

6
        "github.com/DigitalTolk/ex/internal/middleware"
7
        "github.com/DigitalTolk/ex/internal/service"
8
)
9

10
// ThreadHandler exposes endpoints that span all parents (channels and
11
// conversations) for the authenticated user.
12
type ThreadHandler struct {
13
        messageSvc *service.MessageService
14
}
15

16
// NewThreadHandler creates a ThreadHandler.
17
func NewThreadHandler(messageSvc *service.MessageService) *ThreadHandler {
6✔
18
        return &ThreadHandler{messageSvc: messageSvc}
6✔
19
}
6✔
20

21
// List returns thread summaries the authenticated user has participated in,
22
// sorted newest-activity first.
23
func (h *ThreadHandler) List(w http.ResponseWriter, r *http.Request) {
4✔
24
        userID := middleware.UserIDFromContext(r.Context())
4✔
25
        if userID == "" {
5✔
26
                writeError(w, http.StatusUnauthorized, "unauthorized", "authentication required")
1✔
27
                return
1✔
28
        }
1✔
29
        summaries, err := h.messageSvc.ListUserThreads(r.Context(), userID)
3✔
30
        if err != nil {
4✔
31
                writeError(w, http.StatusInternalServerError, "list_error", err.Error())
1✔
32
                return
1✔
33
        }
1✔
34
        if summaries == nil {
2✔
35
                summaries = []*service.ThreadSummary{}
×
36
        }
×
37
        writeJSON(w, http.StatusOK, summaries)
2✔
38
}
39

40
func (h *ThreadHandler) Follow(w http.ResponseWriter, r *http.Request) {
4✔
41
        h.setFollow(w, r, true)
4✔
42
}
4✔
43

44
func (h *ThreadHandler) Unfollow(w http.ResponseWriter, r *http.Request) {
1✔
45
        h.setFollow(w, r, false)
1✔
46
}
1✔
47

48
func (h *ThreadHandler) setFollow(w http.ResponseWriter, r *http.Request, following bool) {
5✔
49
        userID := middleware.UserIDFromContext(r.Context())
5✔
50
        if userID == "" {
6✔
51
                writeError(w, http.StatusUnauthorized, "unauthorized", "authentication required")
1✔
52
                return
1✔
53
        }
1✔
54
        parentID := r.PathValue("parentID")
4✔
55
        threadRootID := r.PathValue("threadRootID")
4✔
56
        parentType, ok := normalizeThreadParentType(r.PathValue("parentType"))
4✔
57
        if parentID == "" || threadRootID == "" || !ok {
6✔
58
                writeError(w, http.StatusBadRequest, "bad_request", "invalid thread follow target")
2✔
59
                return
2✔
60
        }
2✔
61
        if err := h.messageSvc.SetThreadFollow(r.Context(), userID, parentID, parentType, threadRootID, following); err != nil {
2✔
NEW
62
                writeError(w, http.StatusBadRequest, "follow_error", err.Error())
×
NEW
63
                return
×
NEW
64
        }
×
65
        w.WriteHeader(http.StatusNoContent)
2✔
66
}
67

68
func normalizeThreadParentType(raw string) (string, bool) {
9✔
69
        switch raw {
9✔
70
        case "channel", "channels":
5✔
71
                return service.ParentChannel, true
5✔
72
        case "conversation", "conversations":
2✔
73
                return service.ParentConversation, true
2✔
74
        default:
2✔
75
                return "", false
2✔
76
        }
77
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc