Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added bin/golangci-lint
Binary file not shown.
Binary file added bin/gotestsum
Binary file not shown.
82 changes: 79 additions & 3 deletions server/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/pkg/errors"

"github.com/mattermost/mattermost-plugin-autolink/server/autolink"
"github.com/mattermost/mattermost-plugin-autolink/server/autolinkclient"
)

type Store interface {
Expand All @@ -34,7 +35,10 @@ func NewHandler(store Store, authorization Authorization) *Handler {
root := mux.NewRouter()
api := root.PathPrefix("/api/v1").Subrouter()
api.Use(h.adminOrPluginRequired)
api.HandleFunc("/link", h.setLink).Methods("POST")
link := api.PathPrefix("/link").Subrouter()
link.HandleFunc("", h.setLink).Methods(http.MethodPost)
link.HandleFunc("", h.deleteLink).Methods(http.MethodDelete)
link.HandleFunc("", h.getLinks).Methods(http.MethodGet)

api.Handle("{anything:.*}", http.NotFoundHandler())

Expand Down Expand Up @@ -98,7 +102,7 @@ func (h *Handler) setLink(w http.ResponseWriter, r *http.Request) {
found := false
changed := false
for i := range links {
if links[i].Name == newLink.Name || links[i].Pattern == newLink.Pattern {
if links[i].Name == newLink.Name {
if !links[i].Equals(newLink) {
links[i] = newLink
changed = true
Expand All @@ -114,12 +118,84 @@ func (h *Handler) setLink(w http.ResponseWriter, r *http.Request) {
status := http.StatusNotModified
if changed {
if err := h.store.SaveLinks(links); err != nil {
h.handleError(w, errors.Wrap(err, "unable to save link"))
h.handleError(w, errors.Wrap(err, "unable to save the link"))
return
}
status = http.StatusOK
}

ReturnStatusOK(status, w)
}

func (h *Handler) deleteLink(w http.ResponseWriter, r *http.Request) {
autolinkName := r.URL.Query().Get(autolinkclient.AutolinkNameQueryParam)
if autolinkName == "" {
h.handleError(w, errors.New("autolink name should not be empty"))
return
}

links := h.store.GetLinks()
found := false
for i := 0; i < len(links); i++ {
if links[i].Name == autolinkName {
links = append(links[:i], links[i+1:]...)
found = true
Comment thread
Kshitij-Katiyar marked this conversation as resolved.
Comment thread
Kshitij-Katiyar marked this conversation as resolved.
break
}
}

status := http.StatusNotFound
if found {
if err := h.store.SaveLinks(links); err != nil {
h.handleError(w, errors.Wrap(err, "unable to save the link"))
return
}
status = http.StatusOK
}

ReturnStatusOK(status, w)
}

func (h *Handler) getLinks(w http.ResponseWriter, r *http.Request) {
links := h.store.GetLinks()

autolinkName := r.URL.Query().Get(autolinkclient.AutolinkNameQueryParam)
if autolinkName == "" {
h.handleSendingJSONContent(w, links)
return
}

var autolink *autolink.Autolink
for _, link := range links {
currentLink := link
if currentLink.Name == autolinkName {
autolink = &currentLink
break
}
}
if autolink == nil {
h.handleError(w, errors.Errorf("no autolink found with name %s", autolinkName))
return
}

h.handleSendingJSONContent(w, autolink)
}

func (h *Handler) handleSendingJSONContent(w http.ResponseWriter, v interface{}) {
w.Header().Set("Content-Type", "application/json")
b, err := json.Marshal(v)
if err != nil {
h.handleError(w, errors.Wrap(err, "failed to marshal JSON response"))
return
}

if _, err = w.Write(b); err != nil {
h.handleError(w, errors.Wrap(err, "failed to write JSON response"))
return
}
}

func ReturnStatusOK(status int, w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, _ = w.Write([]byte(`{"status": "OK"}`))
Expand Down
112 changes: 112 additions & 0 deletions server/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -168,3 +170,113 @@ func TestSetLink(t *testing.T) {
})
}
}

func TestGetLink(t *testing.T) {
prevLinks := []autolink.Autolink{{
Name: "test",
Pattern: ".*1",
Template: "test",
}}

for _, tc := range []struct {
name string
autoLinkName string
expectStatus int
expectReturn string
}{
{
name: "get the autolink",
autoLinkName: "test",
expectStatus: http.StatusOK,
expectReturn: `{"Name":"test","Disabled":false,"Pattern":".*1","Template":"test","Scope":null,"WordMatch":false,"DisableNonWordPrefix":false,"DisableNonWordSuffix":false,"ProcessBotPosts":false}`,
},
{
name: "not found",
autoLinkName: "test-1",
expectStatus: http.StatusInternalServerError,
expectReturn: `{"error":"An internal error has occurred. Check app server logs for details.","details":"no autolink found with name test-1"}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
var saved []autolink.Autolink
var saveCalled bool

h := NewHandler(
&linkStore{
prev: prevLinks,
saveCalled: &saveCalled,
saved: &saved,
},
authorizeAll{},
)

w := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/link?autolinkName=%s", tc.autoLinkName), nil)
require.NoError(t, err)

r.Header.Set("Mattermost-Plugin-ID", "testfrom")
r.Header.Set("Mattermost-User-ID", "testuser")

h.ServeHTTP(w, r)

respBody, err := io.ReadAll(w.Body)
require.NoError(t, err)

require.Equal(t, tc.expectStatus, w.Code)
require.Equal(t, tc.expectReturn, string(respBody))
})
}
}

func TestDeleteLink(t *testing.T) {
autoLinkName := "test"
for _, tc := range []struct {
name string
prevLinks []autolink.Autolink
expectStatus int
}{
{
name: "delete the autolink",
prevLinks: []autolink.Autolink{{
Name: "test",
Pattern: ".*1",
Template: "test",
}},
expectStatus: http.StatusOK,
},
{
name: "not found",
prevLinks: []autolink.Autolink{{
Name: "test1",
Pattern: ".*1",
Template: "test",
}},
expectStatus: http.StatusNotFound,
},
} {
t.Run(tc.name, func(t *testing.T) {
var saved []autolink.Autolink
var saveCalled bool

h := NewHandler(
&linkStore{
prev: tc.prevLinks,
saveCalled: &saveCalled,
saved: &saved,
},
authorizeAll{},
)

w := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("/api/v1/link?autolinkName=%s", autoLinkName), nil)
require.NoError(t, err)

r.Header.Set("Mattermost-Plugin-ID", "testfrom")
r.Header.Set("Mattermost-User-ID", "testuser")

h.ServeHTTP(w, r)

require.Equal(t, tc.expectStatus, w.Code)
})
}
}
72 changes: 68 additions & 4 deletions server/autolinkclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import (
"fmt"
"io"
"net/http"
"net/url"

"github.com/mattermost/mattermost-plugin-autolink/server/autolink"
)

const autolinkPluginID = "mattermost-autolink"
const (
autolinkPluginID = "mattermost-autolink"
AutolinkNameQueryParam = "autolinkName"
)

type PluginAPI interface {
PluginHTTP(*http.Request) *http.Response
Expand Down Expand Up @@ -45,22 +49,82 @@ func (c *Client) Add(links ...autolink.Autolink) error {
return err
}

req, err := http.NewRequest("POST", "/"+autolinkPluginID+"/api/v1/link", bytes.NewReader(linkBytes))
resp, err := c.call("/"+autolinkPluginID+"/api/v1/link", http.MethodPost, linkBytes, nil)
Comment thread
Kshitij-Katiyar marked this conversation as resolved.
if err != nil {
return err
}
defer resp.Body.Close()

resp, err := c.Do(req)
if resp.StatusCode != http.StatusOK {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("unable to add the link %s. Error: %v, %v", link.Name, resp.StatusCode, string(respBody))
}
}

return nil
}

func (c *Client) Delete(links ...string) error {
for _, link := range links {
queryParams := url.Values{
AutolinkNameQueryParam: {link},
}

resp, err := c.call("/"+autolinkPluginID+"/api/v1/link", http.MethodDelete, nil, queryParams)
Comment thread
Kshitij-Katiyar marked this conversation as resolved.
if err != nil {
return err
}
defer resp.Body.Close()
Comment thread
Kshitij-Katiyar marked this conversation as resolved.

if resp.StatusCode != http.StatusOK {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("unable to install autolink. Error: %v, %v", resp.StatusCode, string(respBody))
return fmt.Errorf("unable to delete the link %s. Error: %v, %v", link, resp.StatusCode, string(respBody))
}
}

return nil
}

func (c *Client) Get(autolinkName string) (*autolink.Autolink, error) {
queryParams := url.Values{
AutolinkNameQueryParam: {autolinkName},
}

resp, err := c.call("/"+autolinkPluginID+"/api/v1/link", http.MethodGet, nil, queryParams)
if err != nil {
return nil, err
}
defer resp.Body.Close()

respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unable to get the link %s. Error: %v, %v", autolinkName, resp.StatusCode, string(respBody))
}

var response *autolink.Autolink
if err = json.Unmarshal(respBody, &response); err != nil {
return nil, err
}

return response, nil
}

func (c *Client) call(url, method string, body []byte, queryParams url.Values) (*http.Response, error) {
req, err := http.NewRequest(method, url, bytes.NewReader(body))
if err != nil {
return nil, err
}

req.URL.RawQuery = queryParams.Encode()

resp, err := c.Do(req)
if err != nil {
return nil, err
}

return resp, nil
}
Loading