Exemple #1
0
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)
}
Exemple #2
0
func HandleCreateNote(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	var note Note
	if err := json.NewDecoder(r.Body).Decode(&note); 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(&note); 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)
}
Exemple #3
0
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)
}
Exemple #4
0
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)
}
Exemple #5
0
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)
	}
}
Exemple #6
0
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)
}
Exemple #7
0
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)
	}
}
Exemple #8
0
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)
}
Exemple #9
0
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)
}
Exemple #10
0
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)
}
Exemple #11
0
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)
}
Exemple #12
0
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)
}
Exemple #13
0
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)
}
Exemple #14
0
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)
}
Exemple #15
0
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)
}
Exemple #16
0
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)
}