diff --git a/web/handlers/http.go b/web/handlers/http.go index ab93ce7..5f5577b 100644 --- a/web/handlers/http.go +++ b/web/handlers/http.go @@ -329,12 +329,13 @@ func New( // notices (the mini-CMS) var nh = noticeHandler{ + render: r, flashes: flashHelper, notices: dbs.Notices, pinned: dbs.PinnedNotices, } - m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nh.list)) + m.Get(router.CompleteNoticeList).HandlerFunc(nh.list) m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nh.show)) // public aliases diff --git a/web/handlers/notices.go b/web/handlers/notices.go index ac22925..fb53184 100644 --- a/web/handlers/notices.go +++ b/web/handlers/notices.go @@ -5,6 +5,8 @@ package handlers import ( + "encoding/json" + "go.mindeco.de/http/render" "html/template" "net/http" "strconv" @@ -16,6 +18,7 @@ import ( ) type noticeHandler struct { + render *render.Renderer flashes *errors.FlashHelper pinned roomdb.PinnedNoticesService @@ -27,22 +30,33 @@ type noticesListData struct { Flashes []errors.FlashMessage } -func (h noticeHandler) list(rw http.ResponseWriter, req *http.Request) (interface{}, error) { +func (h noticeHandler) list(rw http.ResponseWriter, req *http.Request) { + var responder listNoticesResponder + switch req.URL.Query().Get("encoding") { + case "json": + responder = newListNoticesJSONResponder(rw) + default: + responder = newListNoticesHTMLResponder(h.render, rw, req) + } lst, err := h.pinned.List(req.Context()) if err != nil { - return nil, err + responder.RenderError(err) + return + } + + flashes, err := h.flashes.GetAll(rw, req) + if err != nil { + responder.RenderError(err) + return } pageData := noticesListData{ AllNotices: lst.Sorted(), - } - pageData.Flashes, err = h.flashes.GetAll(rw, req) - if err != nil { - return nil, err + Flashes: flashes, } - return pageData, nil + responder.Render(pageData) } type noticeShowData struct { @@ -80,3 +94,86 @@ func (h noticeHandler) show(rw http.ResponseWriter, req *http.Request) (interfac return pageData, nil } + +type listNoticesResponder interface { + Render(noticesListData) + RenderError(error) +} + +type listNoticesJSONResponder struct { + rw http.ResponseWriter +} + +func newListNoticesJSONResponder(rw http.ResponseWriter) *listNoticesJSONResponder { + return &listNoticesJSONResponder{rw: rw} +} + +func (l listNoticesJSONResponder) Render(data noticesListData) { + l.rw.Header().Set("Content-Type", "application/json") + var pinnedNotices []listNoticesJSONResponsePinnedNotice + for _, pinnedNotice := range data.AllNotices { + v := listNoticesJSONResponsePinnedNotice{ + Name: string(pinnedNotice.Name), + Notices: nil, + } + for _, notice := range pinnedNotice.Notices { + v.Notices = append(v.Notices, listNoticesJSONResponseNotice{ + ID: notice.ID, + Title: notice.Title, + Content: notice.Content, + Language: notice.Language, + }) + } + pinnedNotices = append(pinnedNotices, v) + } + + var resp = listNoticesJSONResponse{ + PinnedNotices: pinnedNotices, + } + json.NewEncoder(l.rw).Encode(resp) +} + +func (l listNoticesJSONResponder) RenderError(err error) { + l.rw.Header().Set("Content-Type", "application/json") + l.rw.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(l.rw).Encode(struct { + Status string `json:"status"` + Error string `json:"error"` + }{"error", err.Error()}) +} + +type listNoticesHTMLResponder struct { + renderer *render.Renderer + rw http.ResponseWriter + req *http.Request +} + +func newListNoticesHTMLResponder(renderer *render.Renderer, rw http.ResponseWriter, req *http.Request) *listNoticesHTMLResponder { + return &listNoticesHTMLResponder{renderer: renderer, rw: rw, req: req} +} + +func (l listNoticesHTMLResponder) Render(data noticesListData) { + l.renderer.HTML("notice/list.tmpl", func(w http.ResponseWriter, req *http.Request) (interface{}, error) { + return data, nil + })(l.rw, l.req) +} + +func (l listNoticesHTMLResponder) RenderError(err error) { + l.renderer.Error(l.rw, l.req, http.StatusInternalServerError, err) +} + +type listNoticesJSONResponse struct { + PinnedNotices []listNoticesJSONResponsePinnedNotice `json:"pinned_notices"` +} + +type listNoticesJSONResponsePinnedNotice struct { + Name string `json:"name"` + Notices []listNoticesJSONResponseNotice `json:"notices"` +} + +type listNoticesJSONResponseNotice struct { + ID int64 `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Language string `json:"language"` +} diff --git a/web/handlers/notices_test.go b/web/handlers/notices_test.go index 0db8c04..b79713d 100644 --- a/web/handlers/notices_test.go +++ b/web/handlers/notices_test.go @@ -223,3 +223,74 @@ func TestNoticesCreateOnlyModsAndHigherInRestricted(t *testing.T) { webassert.HasFlashMessages(t, ts.Client, noticeListURL, "ErrorNotAuthorized") } + +func TestNoticesListReturnsJSONWhenCorrectParameterIsSet(t *testing.T) { + ts := setup(t) + a := assert.New(t) + + ts.PinnedDB.ListReturns(roomdb.PinnedNotices{ + "name1": { + { + ID: 1, + Title: "title1", + Content: "content1", + Language: "language1", + }, + { + ID: 2, + Title: "title2", + Content: "content2", + Language: "language2", + }, + }, + "name2": { + { + ID: 3, + Title: "title3", + Content: "content3", + Language: "language3", + }, + }, + }, nil) + + noticeURL := ts.URLTo(router.CompleteNoticeList) + values := noticeURL.Query() + values.Set("encoding", "json") + noticeURL.RawQuery = values.Encode() + + var response listNoticesJSONResponse + res := ts.Client.GetJSON(noticeURL, &response) + a.Equal(http.StatusOK, res.Code, "wrong HTTP status code") + a.Equal(listNoticesJSONResponse{ + PinnedNotices: []listNoticesJSONResponsePinnedNotice{ + { + Name: "name1", + Notices: []listNoticesJSONResponseNotice{ + { + ID: 1, + Title: "title1", + Content: "content1", + Language: "language1", + }, + { + ID: 2, + Title: "title2", + Content: "content2", + Language: "language2", + }, + }, + }, + { + Name: "name2", + Notices: []listNoticesJSONResponseNotice{ + { + ID: 3, + Title: "title3", + Content: "content3", + Language: "language3", + }, + }, + }, + }, + }, response) +}