Skip to content

Commit

Permalink
internal/exp/...issue...: add Create issue API endpoint, use in app
Browse files Browse the repository at this point in the history
This enables deleting a POST handler for creating issues in issuesapp,
simplifying the code slightly.
  • Loading branch information
dmitshur committed Jun 15, 2020
1 parent bbab6a6 commit 1ac1458
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 64 deletions.
37 changes: 7 additions & 30 deletions internal/exp/app/issuesapp/frontend/main.go
Expand Up @@ -46,7 +46,7 @@ func main() {
js.Global.Set("MarkdownPreview", jsutil.Wrap(MarkdownPreview))
js.Global.Set("SwitchWriteTab", jsutil.Wrap(SwitchWriteTab))
js.Global.Set("PasteHandler", jsutil.Wrap(PasteHandler))
js.Global.Set("CreateNewIssue", CreateNewIssue)
js.Global.Set("CreateNewIssue", f.CreateNewIssue)
js.Global.Set("ToggleIssueState", ToggleIssueState)
js.Global.Set("PostComment", PostComment)
js.Global.Set("EditComment", jsutil.Wrap(f.EditComment))
Expand Down Expand Up @@ -116,20 +116,7 @@ func setupIssueToggleButton() {
}
}

func postJSON(url string, v interface{}) (*http.Response, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
return http.DefaultClient.Do(req)
}

func CreateNewIssue() {
func (f *frontend) CreateNewIssue() {
titleEditor := document.GetElementByID("title-editor").(*dom.HTMLInputElement)
commentEditor := document.QuerySelector(".comment-editor").(*dom.HTMLTextAreaElement)

Expand All @@ -147,25 +134,15 @@ func CreateNewIssue() {
}

go func() {
resp, err := postJSON("new", newIssue)
issue, err := f.is.Create(context.Background(), state.RepoSpec, newIssue)
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
// TODO: Display error in the UI, so it is more visible.
log.Println("creating issue failed:", err)
return
}

fmt.Printf("got reply: %v\n%q\n", resp.Status, string(body))

switch resp.StatusCode {
case http.StatusOK:
// Redirect.
dom.GetWindow().Location().Href = string(body)
}
// Redirect.
dom.GetWindow().Location().Href = fmt.Sprintf("%s/%d", state.BaseURI, issue.ID)
}()
}

Expand Down
38 changes: 6 additions & 32 deletions internal/exp/app/issuesapp/main.go
Expand Up @@ -58,13 +58,14 @@ import (
// issuesApp.ServeHTTP(w, req)
// })
//
// An HTTP API must be available (currently, only EditComment endpoint is used):
// An HTTP API must be available (currently, only Create and EditComment endpoints are used):
//
// // Register HTTP API endpoints.
// apiHandler := httphandler.Issues{Issues: service}
// http.Handle(httproute.List, errorHandler(apiHandler.List))
// http.Handle(httproute.Count, errorHandler(apiHandler.Count))
// http.Handle(httproute.ListTimeline, errorHandler(apiHandler.ListTimeline))
// http.Handle(httproute.Create, errorHandler(apiHandler.Create))
// http.Handle(httproute.EditComment, errorHandler(apiHandler.EditComment))
func New(service issues.Service, users users.Service, opt Options) http.Handler {
static, err := loadTemplates(common.State{}, opt.BodyPre)
Expand Down Expand Up @@ -166,7 +167,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) error {

// Handle "/new".
if req.URL.Path == "/new" {
return h.serveNewIssue(w, req)
return h.NewIssueHandler(w, req)
}

// Handle "/{issueID}" and "/{issueID}/...".
Expand Down Expand Up @@ -339,18 +340,10 @@ func (h *handler) IssueHandler(w http.ResponseWriter, req *http.Request, issueID
return nil
}

func (h *handler) serveNewIssue(w http.ResponseWriter, req *http.Request) error {
switch req.Method {
case http.MethodGet:
return h.CreateIssueHandler(w, req)
case http.MethodPost:
return h.PostCreateIssueHandler(w, req)
default:
return httperror.Method{Allowed: []string{http.MethodGet, http.MethodPost}}
func (h *handler) NewIssueHandler(w http.ResponseWriter, req *http.Request) error {
if req.Method != http.MethodGet {
return httperror.Method{Allowed: []string{http.MethodGet}}
}
}

func (h *handler) CreateIssueHandler(w http.ResponseWriter, req *http.Request) error {
state, err := h.state(req, 0)
if err != nil {
return err
Expand All @@ -366,25 +359,6 @@ func (h *handler) CreateIssueHandler(w http.ResponseWriter, req *http.Request) e
return nil
}

func (h *handler) PostCreateIssueHandler(w http.ResponseWriter, req *http.Request) error {
repoSpec := req.Context().Value(RepoSpecContextKey).(issues.RepoSpec)
baseURI := req.Context().Value(BaseURIContextKey).(string)

var issue issues.Issue
err := json.NewDecoder(req.Body).Decode(&issue)
if err != nil {
return httperror.BadRequest{Err: fmt.Errorf("json.Decode: %v", err)}
}

issue, err = h.is.Create(req.Context(), repoSpec, issue)
if err != nil {
return err
}

fmt.Fprintf(w, "%s/%d", baseURI, issue.ID)
return nil
}

func (h *handler) PostEditIssueHandler(w http.ResponseWriter, req *http.Request, issueID uint64) error {
if req.Method != http.MethodPost {
return httperror.Method{Allowed: []string{http.MethodPost}}
Expand Down
23 changes: 21 additions & 2 deletions internal/exp/service/issue/httpclient/httpclient.go
Expand Up @@ -116,8 +116,27 @@ func (i *Issues) ListTimeline(ctx context.Context, repo issues.RepoSpec, id uint
return tis, err
}

func (*Issues) Create(_ context.Context, repo issues.RepoSpec, issue issues.Issue) (issues.Issue, error) {
return issues.Issue{}, fmt.Errorf("Create: not implemented")
func (c *Issues) Create(ctx context.Context, repo issues.RepoSpec, issue issues.Issue) (issues.Issue, error) {
u := url.URL{
Path: httproute.Create,
RawQuery: url.Values{ // TODO: Automate this conversion process.
"RepoURI": {repo.URI},
"Title": {issue.Title},
"Body": {issue.Body},
}.Encode(),
}
resp, err := ctxhttp.Post(ctx, c.client, c.baseURL.ResolveReference(&u).String(), "", nil)
if err != nil {
return issues.Issue{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return issues.Issue{}, fmt.Errorf("did not get acceptable status code: %v body: %q", resp.Status, body)
}
var i issues.Issue
err = json.NewDecoder(resp.Body).Decode(&i)
return i, err
}

func (*Issues) CreateComment(_ context.Context, repo issues.RepoSpec, id uint64, comment issues.Comment) (issues.Comment, error) {
Expand Down
19 changes: 19 additions & 0 deletions internal/exp/service/issue/httphandler/httphandler.go
Expand Up @@ -81,6 +81,25 @@ func (h Issues) ListTimeline(w http.ResponseWriter, req *http.Request) error {
return gob.NewEncoder(w).Encode(tis)
}

func (h Issues) Create(w http.ResponseWriter, req *http.Request) error {
if req.Method != http.MethodPost {
return httperror.Method{Allowed: []string{http.MethodPost}}
}
q := req.URL.Query() // TODO: Automate this conversion process.
repo := issues.RepoSpec{URI: q.Get("RepoURI")}
issue, err := h.Issues.Create(req.Context(), repo, issues.Issue{
Title: q.Get("Title"),
Comment: issues.Comment{
Body: q.Get("Body"),
},
})
if err != nil {
// TODO: Return error via JSON.
return err
}
return httperror.JSONResponse{V: issue}
}

func (h Issues) EditComment(w http.ResponseWriter, req *http.Request) error {
if req.Method != "POST" {
return httperror.Method{Allowed: []string{"POST"}}
Expand Down
1 change: 1 addition & 0 deletions internal/exp/service/issue/httproute/httproute.go
Expand Up @@ -6,5 +6,6 @@ const (
List = "/api/issues/list"
Count = "/api/issues/count"
ListTimeline = "/api/issues/list-timeline"
Create = "/api/issues/create"
EditComment = "/api/issues/edit-comment"
)

0 comments on commit 1ac1458

Please sign in to comment.