Skip to content

Commit 067ed15

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
app home: add a refresh button
1 parent a4fbca7 commit 067ed15

File tree

7 files changed

+299
-128
lines changed

7 files changed

+299
-128
lines changed

cmd/server/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ func run(ctx context.Context, cancel context.CancelFunc, cfg *config.ServerConfi
325325

326326
// Slack endpoints - routed to workspace-specific clients
327327
router.HandleFunc("/slack/events", eventRouter.HandleEvents).Methods("POST")
328-
router.HandleFunc("/slack/interactions", eventRouter.HandleInteractions).Methods("POST")
328+
router.HandleFunc("/slack/interactive-endpoint", eventRouter.HandleInteractions).Methods("POST")
329329
router.HandleFunc("/slack/slash", eventRouter.HandleSlashCommand).Methods("POST")
330330

331331
// Determine port.

internal/bot/interfaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type SlackClient interface {
1919
IsBotInChannel(ctx context.Context, channelID string) bool
2020
BotInfo(ctx context.Context) (*slack.AuthTestResponse, error)
2121
WorkspaceInfo(ctx context.Context) (*slack.TeamInfo, error)
22-
PublishHomeView(userID string, blocks []slack.Block) error
22+
PublishHomeView(ctx context.Context, userID string, blocks []slack.Block) error
2323
API() *slack.Client
2424
}
2525

internal/slack/events_router.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"log/slog"
77
"net/http"
8+
"net/url"
89

910
"github.com/slack-go/slack/slackevents"
1011
)
@@ -87,7 +88,11 @@ func (er *EventRouter) HandleEvents(w http.ResponseWriter, req *http.Request) {
8788
"team_id", teamID,
8889
"event_type", eventWrapper.Type,
8990
"remote_addr", req.RemoteAddr,
90-
"user_agent", req.Header.Get("User-Agent"))
91+
"user_agent", req.Header.Get("User-Agent"),
92+
"signature_present", signature != "",
93+
"timestamp", timestamp,
94+
"body_size", len(body),
95+
"response_status", http.StatusUnauthorized)
9196
w.WriteHeader(http.StatusUnauthorized)
9297
return
9398
}
@@ -112,12 +117,31 @@ func (er *EventRouter) HandleInteractions(w http.ResponseWriter, req *http.Reque
112117
return
113118
}
114119

120+
// Log raw body for debugging (truncate if too large)
121+
bodyPreview := string(body)
122+
if len(bodyPreview) > 500 {
123+
bodyPreview = bodyPreview[:500] + "... (truncated)"
124+
}
125+
slog.Debug("received interaction request",
126+
"body_size", len(body),
127+
"raw_body", bodyPreview,
128+
"remote_addr", req.RemoteAddr)
129+
115130
// Parse payload to extract team_id FIRST (before signature verification)
116131
// Interactions come as form-encoded with a "payload" field
117-
payload := req.FormValue("payload")
132+
// We must parse from body bytes since body was already read
133+
values, err := url.ParseQuery(string(body))
134+
if err != nil {
135+
slog.Error("failed to parse form data", "error", err)
136+
w.WriteHeader(http.StatusBadRequest)
137+
return
138+
}
139+
140+
payload := values.Get("payload")
118141
if payload == "" {
119-
// Try reading from body
120-
payload = string(body)
142+
slog.Error("interaction missing payload field")
143+
w.WriteHeader(http.StatusBadRequest)
144+
return
121145
}
122146

123147
var interaction struct {
@@ -155,12 +179,18 @@ func (er *EventRouter) HandleInteractions(w http.ResponseWriter, req *http.Reque
155179
slog.Warn("interaction signature verification failed - possible attack",
156180
"team_id", teamID,
157181
"remote_addr", req.RemoteAddr,
158-
"user_agent", req.Header.Get("User-Agent"))
182+
"user_agent", req.Header.Get("User-Agent"),
183+
"signature_present", signature != "",
184+
"timestamp", timestamp,
185+
"body_size", len(body),
186+
"response_status", http.StatusUnauthorized)
159187
w.WriteHeader(http.StatusUnauthorized)
160188
return
161189
}
162190

163-
slog.Debug("routing interaction to workspace", "team_id", teamID)
191+
slog.Debug("routing interaction to workspace",
192+
"team_id", teamID,
193+
"body_size", len(body))
164194

165195
// Forward to the workspace-specific client's interaction handler
166196
req.Body = io.NopCloser(&readerWrapper{data: body})

internal/slack/home_handler.go

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package slack
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"log/slog"
78
"strings"
@@ -37,10 +38,38 @@ func NewHomeHandler(
3738

3839
// HandleAppHomeOpened updates the app home view when a user opens it.
3940
func (h *HomeHandler) HandleAppHomeOpened(ctx context.Context, teamID, slackUserID string) error {
40-
slog.Debug("handling app home opened",
41+
slog.Info("handling app home opened - fetching fresh data",
4142
"team_id", teamID,
4243
"slack_user_id", slackUserID)
4344

45+
// Try up to 2 times - first with cached client, second with fresh client after invalid_auth
46+
for attempt := 0; attempt < 2; attempt++ {
47+
if attempt > 0 {
48+
slog.Info("retrying home view after invalid_auth", "team_id", teamID, "attempt", attempt+1)
49+
}
50+
51+
err := h.tryHandleAppHomeOpened(ctx, teamID, slackUserID)
52+
if err == nil {
53+
return nil
54+
}
55+
56+
// If invalid_auth and first attempt, invalidate cache and retry
57+
if strings.Contains(err.Error(), "invalid_auth") && attempt == 0 {
58+
slog.Warn("invalid_auth detected - invalidating cache and retrying",
59+
"team_id", teamID)
60+
h.slackManager.InvalidateCache(teamID)
61+
continue
62+
}
63+
64+
// Other errors or second attempt - return immediately
65+
return err
66+
}
67+
68+
return errors.New("failed after retries")
69+
}
70+
71+
// tryHandleAppHomeOpened attempts to handle app home opened event.
72+
func (h *HomeHandler) tryHandleAppHomeOpened(ctx context.Context, teamID, slackUserID string) error {
4473
// Get Slack client for this workspace
4574
slackClient, err := h.slackManager.Client(ctx, teamID)
4675
if err != nil {
@@ -50,8 +79,12 @@ func (h *HomeHandler) HandleAppHomeOpened(ctx context.Context, teamID, slackUser
5079
// Get Slack user info to extract email
5180
slackUser, err := slackClient.API().GetUserInfo(slackUserID)
5281
if err != nil {
82+
// Don't mask invalid_auth errors - let them propagate for retry logic
83+
if strings.Contains(err.Error(), "invalid_auth") {
84+
return fmt.Errorf("failed to get Slack user info: %w", err)
85+
}
5386
slog.Warn("failed to get Slack user info", "user_id", slackUserID, "error", err)
54-
return h.publishPlaceholderHome(slackClient, slackUserID)
87+
return h.publishPlaceholderHome(ctx, slackClient, slackUserID)
5588
}
5689

5790
// Extract GitHub username from email (simple heuristic: part before @)
@@ -62,15 +95,15 @@ func (h *HomeHandler) HandleAppHomeOpened(ctx context.Context, teamID, slackUser
6295
slog.Warn("could not extract GitHub username from Slack email",
6396
"slack_user_id", slackUserID,
6497
"email", email)
65-
return h.publishPlaceholderHome(slackClient, slackUserID)
98+
return h.publishPlaceholderHome(ctx, slackClient, slackUserID)
6699
}
67100
githubUsername := email[:atIndex]
68101

69102
// Get all orgs for this workspace
70103
workspaceOrgs := h.workspaceOrgs(teamID)
71104
if len(workspaceOrgs) == 0 {
72105
slog.Warn("no workspace orgs found", "team_id", teamID)
73-
return h.publishPlaceholderHome(slackClient, slackUserID)
106+
return h.publishPlaceholderHome(ctx, slackClient, slackUserID)
74107
}
75108

76109
// Get GitHub client for first org (they all share the same app)
@@ -92,7 +125,7 @@ func (h *HomeHandler) HandleAppHomeOpened(ctx context.Context, teamID, slackUser
92125
slog.Error("failed to fetch dashboard",
93126
"github_user", githubUsername,
94127
"error", err)
95-
return h.publishPlaceholderHome(slackClient, slackUserID)
128+
return h.publishPlaceholderHome(ctx, slackClient, slackUserID)
96129
}
97130

98131
// Add workspace orgs to dashboard for UI display
@@ -102,15 +135,17 @@ func (h *HomeHandler) HandleAppHomeOpened(ctx context.Context, teamID, slackUser
102135
blocks := home.BuildBlocks(dashboard, workspaceOrgs[0])
103136

104137
// Publish to Slack
105-
if err := slackClient.PublishHomeView(slackUserID, blocks); err != nil {
138+
if err := slackClient.PublishHomeView(ctx, slackUserID, blocks); err != nil {
106139
return fmt.Errorf("failed to publish home view: %w", err)
107140
}
108141

109-
slog.Info("published home view",
142+
slog.Info("published home view with fresh data",
110143
"slack_user_id", slackUserID,
111144
"github_user", githubUsername,
112145
"incoming_prs", len(dashboard.IncomingPRs),
146+
"incoming_blocked", dashboard.Counts().IncomingBlocked,
113147
"outgoing_prs", len(dashboard.OutgoingPRs),
148+
"outgoing_blocked", dashboard.Counts().OutgoingBlocked,
114149
"workspace_orgs", len(workspaceOrgs))
115150

116151
return nil
@@ -137,7 +172,7 @@ func (h *HomeHandler) workspaceOrgs(teamID string) []string {
137172
}
138173

139174
// publishPlaceholderHome publishes a simple placeholder home view.
140-
func (*HomeHandler) publishPlaceholderHome(slackClient *Client, slackUserID string) error {
175+
func (*HomeHandler) publishPlaceholderHome(ctx context.Context, slackClient *Client, slackUserID string) error {
141176
slog.Debug("publishing placeholder home", "user_id", slackUserID)
142177

143178
blocks := home.BuildBlocks(&home.Dashboard{
@@ -146,5 +181,5 @@ func (*HomeHandler) publishPlaceholderHome(slackClient *Client, slackUserID stri
146181
WorkspaceOrgs: []string{"your-org"},
147182
}, "your-org")
148183

149-
return slackClient.PublishHomeView(slackUserID, blocks)
184+
return slackClient.PublishHomeView(ctx, slackUserID, blocks)
150185
}

internal/slack/manager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ func (m *Manager) Client(ctx context.Context, teamID string) (*Client, error) {
103103
// Create client
104104
client = New(token, m.signingSecret)
105105
client.SetTeamID(teamID)
106+
client.SetManager(m) // Set manager reference for cache invalidation
106107

107108
// Set home view handler if configured
108109
if m.homeViewHandler != nil {

0 commit comments

Comments
 (0)