func handleCreateTopic(ctx context.Context, w http.ResponseWriter, r *http.Request) { account, ok := auth.AuthRequired(pg.DB(ctx), w, r) if !ok { return } input := struct { Title string Tags []string Content string }{ Title: r.FormValue("title"), Tags: strings.Fields(r.FormValue("tags")), Content: r.FormValue("content"), } var errs []string if r.Method == "POST" { if input.Title == "" { errs = append(errs, `"title" is required`) } if input.Content == "" { errs = append(errs, `"content" is required`) } } if r.Method == "GET" || len(errs) != 0 { render(w, "topic_create.tmpl", input) return } db := pg.DB(ctx) tx, err := db.Beginx() if err != nil { log.Error("cannot start transaction", "error", err.Error()) respond500(w, r) return } defer tx.Rollback() topic := Topic{ AuthorID: int64(account.AccountID), Title: input.Title, Tags: input.Tags, } t, _, err := CreateTopicWithComment(tx, topic, input.Content) if err != nil { log.Error("cannot create topic with comment", "error", err.Error()) respond500(w, r) return } if err := tx.Commit(); err != nil { log.Error("cannot commit transaction", "error", err.Error()) respond500(w, r) return } http.Redirect(w, r, fmt.Sprintf("/t/%d", t.TopicID), http.StatusSeeOther) }
func HandleCreateNote(ctx context.Context, w http.ResponseWriter, r *http.Request) { var note Note if err := json.NewDecoder(r.Body).Decode(¬e); err != nil { web.JSONErr(w, err.Error(), http.StatusBadRequest) return } acc, ok := auth.AuthRequired(pg.DB(ctx), w, r) if !ok { return } if errs := validateNote(¬e); len(errs) > 0 { web.JSONErrs(w, errs, http.StatusBadRequest) return } note.OwnerID = acc.AccountID n, err := CreateNote(pg.DB(ctx), note) if err != nil { log.Printf("cannot create note: %s", err) web.StdJSONErr(w, http.StatusInternalServerError) return } web.JSONResp(w, n, http.StatusCreated) }
func HandleDisplayNote(ctx context.Context, w http.ResponseWriter, r *http.Request) { args := web.Args(ctx) note, err := NoteByID(pg.DB(ctx), stoint(args.ByIndex(0))) /* if err != nil { if err == pg.ErrNotFound { web.StdJSONErr(w, http.StatusNotFound) } else { log.Printf("cannot get %q note: %s", args.ByIndex(0), err) web.StdJSONErr(w, http.StatusInternalServerError) } return } if !note.IsPublic { acc, ok := auth.Authenticated(pg.DB(ctx), r) if !ok || acc.AccountID != note.OwnerID { web.StdJSONErr(w, http.StatusUnauthorized) return } } */ fmt.Println(err) tmpl.Render(w, "note_details.html", note) }
func handleListBookmarks(ctx context.Context, w http.ResponseWriter, r *http.Request) { offset, _ := strconv.ParseInt(r.URL.Query().Get("offset"), 10, 64) if offset < 0 { offset = 0 } bookmarks := make([]*Bookmark, 0, 100) err := pg.DB(ctx).Select(&bookmarks, ` SELECT b.* FROM bookmarks b ORDER BY created DESC LIMIT $1 OFFSET $2 `, 500, offset) if err != nil { log.Error("cannot select bookmarks", "error", err.Error()) web.StdJSONResp(w, http.StatusInternalServerError) return } resp := struct { Bookmarks []*Bookmark `json:"bookmarks"` }{ Bookmarks: bookmarks, } web.JSONResp(w, resp, http.StatusOK) }
func HandlePasteUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request) { var input struct { Content string } if err := json.NewDecoder(r.Body).Decode(&input); err != nil { web.StdJSONResp(w, http.StatusBadRequest) return } if input.Content == "" { web.JSONErr(w, `"Content" is required"`, http.StatusBadRequest) return } pid, _ := strconv.ParseInt(web.Args(ctx).ByIndex(0), 10, 64) db := pg.DB(ctx) paste, err := UpdatePaste(db, Paste{ ID: pid, Content: input.Content, }) switch err { case nil: web.JSONResp(w, paste, http.StatusOK) case pg.ErrNotFound: web.StdJSONResp(w, http.StatusNotFound) default: log.Printf("cannot update paste %d: %s", pid, err) web.StdJSONResp(w, http.StatusInternalServerError) } }
func HandlePasteList(ctx context.Context, w http.ResponseWriter, r *http.Request) { db := pg.DB(ctx) pastes, err := Pastes(db, 1000, 0) if err != nil { log.Printf("cannot list paste: %s", err) web.StdJSONResp(w, http.StatusInternalServerError) return } resp := struct { Pastes []*Paste }{ Pastes: pastes, } web.JSONResp(w, resp, http.StatusOK) }
func HandlePasteDetails(ctx context.Context, w http.ResponseWriter, r *http.Request) { pid, _ := strconv.ParseInt(web.Args(ctx).ByIndex(0), 10, 64) db := pg.DB(ctx) paste, err := PasteByID(db, pid) switch err { case nil: web.JSONResp(w, paste, http.StatusOK) case pg.ErrNotFound: web.StdJSONResp(w, http.StatusNotFound) default: log.Printf("cannot get paste %d: %s", pid, err) web.StdJSONResp(w, http.StatusInternalServerError) } }
func HandleUpdateNote(ctx context.Context, w http.ResponseWriter, r *http.Request) { var input Note if err := json.NewDecoder(r.Body).Decode(&input); err != nil { web.JSONErr(w, err.Error(), http.StatusBadRequest) return } if errs := validateNote(&input); len(errs) > 0 { web.JSONErrs(w, errs, http.StatusBadRequest) return } tx, err := pg.DB(ctx).Beginx() if err != nil { log.Printf("cannot start transaction: %s", err) web.StdJSONErr(w, http.StatusInternalServerError) return } defer tx.Rollback() acc, ok := auth.AuthRequired(tx, w, r) if !ok { return } noteID := stoint(web.Args(ctx).ByIndex(0)) if ok, err := IsNoteOwner(tx, noteID, acc.AccountID); err != nil { log.Printf("cannot check %d note owner: %s", noteID, err) web.StdJSONErr(w, http.StatusInternalServerError) return } else if !ok { web.JSONErr(w, "you are not owner of this note", http.StatusUnauthorized) return } note, err := UpdateNote(tx, input) if err != nil { log.Printf("cannot update %d note: %s", noteID, err) web.StdJSONErr(w, http.StatusInternalServerError) return } if err := tx.Commit(); err != nil { log.Printf("cannot commit transaction: %s", err) web.StdJSONErr(w, http.StatusInternalServerError) return } web.JSONResp(w, note, http.StatusOK) }
func handleAddBookmark(ctx context.Context, w http.ResponseWriter, r *http.Request) { var input struct { Url string `json:"url"` } if err := json.NewDecoder(r.Body).Decode(&input); err != nil { web.JSONErr(w, err.Error(), http.StatusBadRequest) return } resp, err := ctxhttp.Get(ctx, &crawler, input.Url) if err != nil { log.Error("cannot crawl", "url", input.Url, "error", err.Error()) web.StdJSONResp(w, http.StatusInternalServerError) return } defer resp.Body.Close() body := make([]byte, 1024*20) if n, err := resp.Body.Read(body); err != nil && err != io.EOF { log.Error("cannot read crawler response", "url", input.Url, "error", err.Error()) web.StdJSONResp(w, http.StatusInternalServerError) return } else { body = body[:n] } title := pageTitle(body) var b Bookmark err = pg.DB(ctx).Get(&b, ` INSERT INTO bookmarks (title, url, created) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING RETURNING * `, title, input.Url, time.Now()) if err != nil { log.Error("cannot create bookmark", "error", err.Error()) web.StdJSONResp(w, http.StatusInternalServerError) return } web.JSONResp(w, b, http.StatusCreated) }
func handleListTopics(ctx context.Context, w http.ResponseWriter, r *http.Request) { opts := TopicsOpts{ Limit: 200, Tags: r.URL.Query()["tag"], } db := pg.DB(ctx) topics, err := Topics(db, opts) if err != nil { log.Error("cannot list topics", "error", err.Error()) } context := struct { Topics []*TopicWithAuthor }{ Topics: topics, } render(w, "topic_list.tmpl", context) }
func handleTopicDetails(ctx context.Context, w http.ResponseWriter, r *http.Request) { db := pg.DB(ctx) tid, _ := strconv.ParseInt(web.Args(ctx).ByIndex(0), 10, 64) topic, err := TopicByID(db, tid) switch err { case nil: // all good case pg.ErrNotFound: respond404(w, r) return default: log.Error("cannot get topic by ID", "topic", web.Args(ctx).ByIndex(0), "error", err.Error()) respond500(w, r) return } page, _ := strconv.ParseInt(r.URL.Query().Get("page"), 10, 64) comments, err := Comments(db, CommentsOpts{ Offset: (page - 1) * 200, Limit: 200, TopicID: topic.TopicID, }) if err != nil { log.Error("cannot get comments for topic", "topic", fmt.Sprint(topic.TopicID), "error", err.Error()) respond500(w, r) return } context := struct { Topic *Topic Comments []*Comment }{ Topic: topic, Comments: comments, } render(w, "topic_details.tmpl", context) }
func HandleDeleteNote(ctx context.Context, w http.ResponseWriter, r *http.Request) { tx, err := pg.DB(ctx).Beginx() if err != nil { log.Printf("cannot start transaction: %s", err) web.StdJSONErr(w, http.StatusInternalServerError) return } defer tx.Rollback() acc, ok := auth.AuthRequired(tx, w, r) if !ok { return } noteID := stoint(web.Args(ctx).ByIndex(0)) if ok, err := IsNoteOwner(tx, noteID, acc.AccountID); err != nil { if err == pg.ErrNotFound { web.StdJSONErr(w, http.StatusNotFound) } else { log.Printf("cannot check %d note owner: %s", noteID, err) web.StdJSONErr(w, http.StatusInternalServerError) } return } else if !ok { web.JSONErr(w, "you are not owner of this note", http.StatusUnauthorized) return } if err := DeleteNote(tx, noteID); err != nil { log.Printf("cannot delete %d note: %s", noteID, err) web.StdJSONErr(w, http.StatusInternalServerError) return } if err := tx.Commit(); err != nil { log.Printf("cannot commit transaction: %s", err) web.StdJSONErr(w, http.StatusInternalServerError) return } w.WriteHeader(http.StatusGone) }
func HandlePasteCreate(ctx context.Context, w http.ResponseWriter, r *http.Request) { var input struct { Content string } if err := json.NewDecoder(r.Body).Decode(&input); err != nil { web.StdJSONResp(w, http.StatusBadRequest) return } if input.Content == "" { web.JSONErr(w, `"Content" is required"`, http.StatusBadRequest) return } db := pg.DB(ctx) paste, err := CreatePaste(db, Paste{Content: input.Content}) if err != nil { log.Printf("cannot create paste: %s", err) web.StdJSONResp(w, http.StatusInternalServerError) return } web.JSONResp(w, paste, http.StatusCreated) }
func handleCreateComment(ctx context.Context, w http.ResponseWriter, r *http.Request) { input := struct { Content string }{ Content: r.FormValue("content"), } var errs []string if len(input.Content) == 0 { errs = append(errs, `"content" is required`) } if len(errs) != 0 { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "%v", errs) return } db := pg.DB(ctx) tid, _ := strconv.ParseInt(web.Args(ctx).ByIndex(0), 10, 64) c, err := CreateComment(db, Comment{ TopicID: tid, Content: input.Content, AuthorID: 1, }) switch err { case nil: // ok default: log.Error("cannot create comment", "topic", fmt.Sprint(tid), "error", err.Error()) respond500(w, r) return } http.Redirect(w, r, fmt.Sprintf("/t/%d", c.TopicID), http.StatusSeeOther) }
func HandleListNotes(ctx context.Context, w http.ResponseWriter, r *http.Request) { db := pg.DB(ctx) acc, ok := auth.AuthRequired(db, w, r) if !ok { return } var offset int if raw := r.URL.Query().Get("offset"); raw != "" { if n, err := strconv.Atoi(raw); err != nil { web.JSONErr(w, "invalid 'offset' value", http.StatusBadRequest) return } else { offset = n } } notes, err := NotesByOwner(db, acc.AccountID, 300, offset) if err != nil { log.Printf("cannot fetch notes for %d: %s", acc.AccountID, err) web.StdJSONErr(w, http.StatusInternalServerError) return } if notes == nil { notes = make([]*Note, 0) // JSON api should return empty list } content := struct { Notes []*Note }{ Notes: notes, } web.JSONResp(w, content, http.StatusOK) }
func HandleLoginCallback(ctx context.Context, w http.ResponseWriter, r *http.Request) { var state string if c, err := r.Cookie(stateCookie); err != nil || c.Value == "" { log.Printf("invalid oauth state: expected %q, got %q", state, r.FormValue("state")) web.JSONRedirect(w, "/", http.StatusTemporaryRedirect) return } else { state = c.Value } if r.FormValue("state") != state { log.Printf("invalid oauth state: expected %q, got %q", state, r.FormValue("state")) web.JSONRedirect(w, "/", http.StatusTemporaryRedirect) return } var data authData switch err := cache.Get(ctx).Get("auth:"+state, &data); err { case nil: // all good case cache.ErrNotFound: web.JSONRedirect(w, "/", http.StatusTemporaryRedirect) return default: log.Printf("cannot get auth data from cache: %s", err) web.JSONRedirect(w, "/", http.StatusTemporaryRedirect) return } conf, ok := oauth(ctx, data.Provider) if !ok { log.Printf("missing oauth provider configuration: %#v", data) const code = http.StatusInternalServerError http.Error(w, http.StatusText(code), code) return } token, err := conf.Exchange(oauth2.NoContext, r.FormValue("code")) if err != nil { log.Printf("oauth exchange failed: %s", err) web.JSONRedirect(w, "/", http.StatusTemporaryRedirect) return } cli := google.NewClient(conf.Client(oauth2.NoContext, token)) user, _, err := cli.Users.Get("") if err != nil { log.Printf("cannot get user: %s", err) web.JSONRedirect(w, "/", http.StatusTemporaryRedirect) return } db := pg.DB(ctx) tx, err := db.Beginx() if err != nil { log.Printf("cannot start transaction: %s", err) http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) return } defer tx.Rollback() provider := strings.SplitN(data.Provider, ":", 2)[0] acc, err := AccountByLogin(tx, *user.Login, provider) if err != nil { if err != pg.ErrNotFound { log.Printf("cannot get account %s: %s", *user.Login, err) http.Error(w, "cannot authenticate", http.StatusInternalServerError) return } acc, err = CreateAccount(tx, *user.ID, *user.Login, provider) if err != nil { log.Printf("cannot create account for %v: %s", user, err) http.Error(w, "cannot create account", http.StatusInternalServerError) return } } if err := authenticate(tx, w, acc.AccountID, token.AccessToken, data.Scopes); err != nil { log.Printf("cannot authenticate %#v: %s", acc, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err := tx.Commit(); err != nil { log.Printf("cannot commit transaction: %s", err) http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) return } web.JSONRedirect(w, data.NextURL, http.StatusTemporaryRedirect) }