-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from gouline/slack-refactor
Refactor Slack auth into separate package
- Loading branch information
Showing
21 changed files
with
706 additions
and
369 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,196 +1,113 @@ | ||
package server | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"regexp" | ||
"strings" | ||
"time" | ||
|
||
"github.com/gouline/blaster/internal/pkg/format" | ||
"github.com/gouline/blaster/internal/pkg/scache" | ||
"github.com/gouline/blaster/internal/pkg/slack" | ||
"github.com/labstack/echo/v4" | ||
"github.com/slack-go/slack" | ||
) | ||
|
||
var suggestCache = scache.New(5*time.Minute, 10*time.Minute) | ||
|
||
// APISuggest handles /api/suggest. | ||
func (s *Server) handleAPISuggest(c echo.Context) error { | ||
token := s.authorizedToken(c) | ||
if token == "" { | ||
slackCtx := s.slack.Context(c) | ||
if !slackCtx.Authorized { | ||
return c.String(http.StatusUnauthorized, "no token") | ||
} | ||
|
||
cacheResponse := <-buildSuggestCache(token) | ||
if cacheResponse.Error != nil { | ||
return c.String(http.StatusInternalServerError, cacheResponse.Error.Error()) | ||
destinations, err := slackCtx.Destinations() | ||
if err != nil { | ||
return c.String(http.StatusInternalServerError, err.Error()) | ||
} | ||
|
||
allSuggestions := cacheResponse.Value.([]suggestion) | ||
|
||
// Filter out all symbols from term | ||
symbolReg := format.NewAllSymbolsRegexp() | ||
term := strings.ToLower(" " + c.QueryParam("term")) | ||
term = symbolReg.ReplaceAllString(term, "") | ||
|
||
// Filter users by term | ||
suggestions := []suggestion{} | ||
for _, suggestion := range allSuggestions { | ||
if strings.Contains(suggestion.Search, term) { | ||
suggestions = append(suggestions, suggestion) | ||
if len(suggestions) == 10 { | ||
break | ||
} | ||
} | ||
} | ||
suggestions := suggestDestinations(c.QueryParam("term"), destinations) | ||
|
||
return c.JSON(http.StatusOK, suggestions) | ||
} | ||
|
||
// handleAPISend handles /api/send. | ||
func (s *Server) handleAPISend(c echo.Context) error { | ||
token := s.authorizedToken(c) | ||
if token == "" { | ||
return c.String(http.StatusUnauthorized, "no token") | ||
slackCtx := s.slack.Context(c) | ||
if !slackCtx.Authorized { | ||
return c.NoContent(http.StatusUnauthorized) | ||
} | ||
|
||
client := slack.New(token) | ||
|
||
// Bind JSON request | ||
var request sendRequest | ||
err := c.Bind(&request) | ||
if err != nil { | ||
if err := c.Bind(&request); err != nil { | ||
return c.String(http.StatusBadRequest, err.Error()) | ||
} | ||
|
||
// Open/get channel by user ID | ||
channel, _, _, err := client.OpenConversation(&slack.OpenConversationParameters{ | ||
Users: []string{ | ||
request.User, | ||
}, | ||
}) | ||
if err != nil { | ||
return c.String(http.StatusInternalServerError, err.Error()) | ||
} | ||
|
||
// Post message to opened channel | ||
_, _, err = client.PostMessage( | ||
channel.ID, | ||
slack.MsgOptionText(request.Message, false), | ||
slack.MsgOptionAsUser(request.AsUser), | ||
) | ||
if err != nil { | ||
if err := slackCtx.SendMessage(request.User, request.Message, request.AsUser); err != nil { | ||
return c.String(http.StatusInternalServerError, err.Error()) | ||
} | ||
|
||
return c.JSON(http.StatusOK, struct{}{}) | ||
} | ||
|
||
func buildSuggestCache(token string) <-chan scache.Response { | ||
return suggestCache.ResponseChan(format.HashToken(token), func(key string) (interface{}, error) { | ||
client := slack.New(token) | ||
|
||
symbolReg := format.NewAllSymbolsRegexp() | ||
|
||
var suggestions []suggestion | ||
|
||
userLookup := map[string]suggestion{} | ||
|
||
// Get all users | ||
users, err := client.GetUsers() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
suggestions = []suggestion{} | ||
|
||
for _, user := range users { | ||
if user.Deleted || user.IsBot { | ||
continue | ||
} | ||
|
||
realName := user.Profile.RealName | ||
displayName := user.Profile.DisplayName | ||
|
||
// Format label based on availability | ||
label := realName | ||
if displayName != "" { | ||
label += " (" + displayName + ")" | ||
// suggestDestinations filters destionations into suggestions by search term. | ||
func suggestDestinations(term string, destinations []*slack.Destination) []*suggestion { | ||
term = " " + sanitizeSearchTerm(term) | ||
|
||
suggestions := []*suggestion{} | ||
for _, dest := range destinations { | ||
searchable := " " + sanitizeSearchTerm(strings.ToLower(dest.Name)+" "+strings.ToLower(dest.DisplayName)) | ||
|
||
if strings.Contains(searchable, term) { | ||
children := []*suggestion{} | ||
for _, child := range dest.Children { | ||
children = append(children, &suggestion{ | ||
Type: sanitizeCSV(child.Type), | ||
Label: sanitizeCSV(suggestionLabel(child.Name, child.DisplayName)), | ||
Value: sanitizeCSV(child.ID), | ||
}) | ||
} | ||
|
||
// Filter out all symbols from search string | ||
search := fmt.Sprintf(" %s %s", strings.ToLower(realName), strings.ToLower(displayName)) | ||
search = symbolReg.ReplaceAllString(search, "") | ||
|
||
// Sanitize labels and values | ||
sanitize := func(s string) string { | ||
return strings.Replace(s, ",", "", -1) | ||
} | ||
suggestions = append(suggestions, &suggestion{ | ||
Type: sanitizeCSV(dest.Type), | ||
Label: sanitizeCSV(suggestionLabel(dest.Name, dest.DisplayName)), | ||
Value: sanitizeCSV(dest.ID), | ||
Children: children, | ||
}) | ||
|
||
s := suggestion{ | ||
Type: "user", | ||
Label: sanitize(label), | ||
Value: sanitize(user.ID), | ||
Search: search, | ||
if len(suggestions) == 10 { | ||
break | ||
} | ||
|
||
suggestions = append(suggestions, s) | ||
|
||
userLookup[user.ID] = s | ||
} | ||
|
||
usergroups, err := client.GetUserGroups(slack.GetUserGroupsOptionIncludeUsers(true)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
for _, usergroup := range usergroups { | ||
if !usergroup.IsUserGroup { | ||
continue | ||
} | ||
|
||
children := []suggestion{} | ||
|
||
for _, userID := range usergroup.Users { | ||
user, found := userLookup[userID] | ||
if !found { | ||
continue | ||
} | ||
|
||
children = append(children, user) | ||
} | ||
|
||
name := usergroup.Name | ||
handle := usergroup.Handle | ||
label := name + " (" + handle + ")" | ||
|
||
// Filter out all symbols from search string | ||
search := fmt.Sprintf(" %s %s", strings.ToLower(name), strings.ToLower(handle)) | ||
search = symbolReg.ReplaceAllString(search, "") | ||
return suggestions | ||
} | ||
|
||
suggestions = append(suggestions, suggestion{ | ||
Type: "usergroup", | ||
Label: label, | ||
Value: "null", | ||
Search: search, | ||
Children: children, | ||
}) | ||
} | ||
// sanitizeSearchTerm removes lowercases search term and removes skippable characters. | ||
func sanitizeSearchTerm(s string) string { | ||
symbolReg := regexp.MustCompile("[!-/:-@[-`{-~]+") | ||
return symbolReg.ReplaceAllString(strings.ToLower(s), "") | ||
} | ||
|
||
return suggestions, nil | ||
}) | ||
// sanitizeCSV removes commas for comma-separated values. | ||
func sanitizeCSV(s string) string { | ||
return strings.Replace(s, ",", "", -1) | ||
} | ||
|
||
type suggestion struct { | ||
Type string `json:"type"` | ||
Label string `json:"label"` | ||
Value string `json:"value"` | ||
Children []suggestion `json:"children,omitempty"` | ||
Search string `json:"-"` | ||
// suggestionLabel formats label with a mandatory name and an optional display name. | ||
func suggestionLabel(name, displayName string) string { | ||
label := name | ||
if displayName != "" { | ||
label += " (" + displayName + ")" | ||
} | ||
return label | ||
} | ||
|
||
type sendRequest struct { | ||
User string `json:"user"` | ||
Message string `json:"message"` | ||
AsUser bool `json:"as_user"` | ||
} | ||
|
||
type suggestion struct { | ||
Type string `json:"type"` | ||
Label string `json:"label"` | ||
Value string `json:"value"` | ||
Children []*suggestion `json:"children,omitempty"` | ||
} |
Oops, something went wrong.