Пример #1
0
// ResetUserPassword is a route which is called when accessing the page generated dispatched with
// account recovery emails.
func ResetUserPassword(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(vestigo.Param(r, "id"))
	if err != nil {
		log.Println("route ResetUserPassword, strconv.Atoi:", err)
		render.R.JSON(w, 400, map[string]interface{}{"error": "User ID could not be parsed from request URL."})
		return
	}

	var user User
	user.ID = int64(id)

	entry, err := user.Get()
	if err != nil {
		log.Println("route ResetUserPassword, user.Get:", err)
		if err.Error() == "not found" {
			render.R.JSON(w, 400, map[string]interface{}{"error": "User with that ID does not exist."})
			return
		}
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	// this ensures that accounts won't be compromised by posting recovery string as empty,
	// which would otherwise result in succesful password reset
	UUID := uuid.Parse(vestigo.Param(r, "recovery"))
	if UUID == nil {
		log.Println("route ResetUserPassword, uuid.Parse:", err)
		log.Println("there was a problem trying to verify password reset UUID for", entry.Email)
		render.R.JSON(w, 400, map[string]interface{}{"error": "Could not parse UUID from the request."})
		return
	}
	if entry.Recovery == vestigo.Param(r, "recovery") {
		newpassword := context.Get(r, "newpassword").(string)
		entry.Password = newpassword
		_, err = user.PasswordReset(entry)
		if err != nil {
			log.Println("route ResetUserPassword, user.PasswordReset:", err)
			render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
			return
		}
		switch Root(r) {
		case "api":
			render.R.JSON(w, 200, map[string]interface{}{"success": "Password was updated successfully."})
		case "user":
			http.Redirect(w, r, "/user/login", 302)
		}
	}
}
Пример #2
0
func (p *RelayMsgParser) SummaryHandler() http.HandlerFunc {
	// Initialize cache container with 1 second TTL, checks running twice a second.
	c := cache.New(1*time.Second, 500*time.Millisecond)
	return func(w http.ResponseWriter, r *http.Request) {
		localpart := vestigo.Param(r, "localpart")

		// Check cache first
		jsonUntyped, found := c.Get(localpart)
		if found {
			jsonBytes := jsonUntyped.([]byte)
			log.Printf("SummarizeEvents (cache): hit for [%s]", localpart)
			w.Write(jsonBytes)
			return
		}

		rows, err := p.Dbh.Query(fmt.Sprintf(`
			SELECT subject, count(distinct(smtp_from))
				FROM %s.relay_messages
			 WHERE smtp_to = $1 ||'@'|| $2
			 GROUP BY 1
		`, p.Schema), localpart, p.Domain)
		if err != nil {
			log.Printf("SummarizeEvents (SELECT): %s", err)
			http.Error(w, "Database error", http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		res := map[string][]SummaryResponse{}
		for rows.Next() {
			if rows.Err() == io.EOF {
				break
			}
			s := SummaryResponse{}
			if err = rows.Scan(&s.Subject, &s.Count); err != nil {
				log.Printf("SummarizeEvents (Scan): %s", err)
				http.Error(w, "Database error", http.StatusInternalServerError)
				return
			}
			res["results"] = append(res["results"], s)
		}
		if err = rows.Err(); err != nil {
			log.Printf("SummarizeEvents (Err): %s", err)
			http.Error(w, "Database error", http.StatusInternalServerError)
			return
		}

		jsonBytes, err := json.Marshal(res)
		if err != nil {
			log.Printf("SummarizeEvents (JSON): %s", err)
			http.Error(w, "Encoding error", http.StatusInternalServerError)
			return
		}

		// Add result to cache
		c.Set(localpart, jsonBytes, cache.DefaultExpiration)

		w.Write(jsonBytes)
	}
}
Пример #3
0
func ExampleManyRoutes() {
	// new router
	router := vestigo.NewRouter()
	// standard http.HandlerFunc
	handler := func(w http.ResponseWriter, r *http.Request) {
		fmt.Printf("version: %s, resource: %s\n", vestigo.Param(r, "version"), r.URL.Path)
	}
	// setup a GET /v:version/hi endpoint route in router
	router.Get("/v:version/hi", handler)
	// setup a GET /v:version/hi endpoint route in router
	router.Post("/v:version/hi", handler)
	// setup a GET /v:version/hi endpoint route in router
	router.Put("/v:version/hi", handler)
	// setup a GET /v:version/hi endpoint route in router
	router.Delete("/v:version/hi", handler)
	// setup a GET /v:version/hi endpoint route in router
	router.Patch("/v:version/hi", handler)

	// create a new request and response writer
	r, _ := http.NewRequest("PATCH", "/v2.3/hi", nil)
	w := httptest.NewRecorder()

	// execute the request
	router.ServeHTTP(w, r)
	// Output: version: 2.3, resource: /v2.3/hi
}
Пример #4
0
// ReadUser is a route which fetches user according to parameter "id" on API side and according to retrieved
// session cookie on frontend side.
// Returns user struct with all posts merged to object on API call. Frontend call will render user "home" page, "user/index.tmpl".
func ReadUser(w http.ResponseWriter, r *http.Request) {
	var user User
	switch Root(r) {
	case "api":
		id, err := strconv.ParseInt(vestigo.Param(r, "id"), 10, 64)
		if err != nil {
			log.Println("route ReadUser, strconv.Atoi:", err)
			render.R.JSON(w, 400, map[string]interface{}{"error": "The user ID could not be parsed from the request URL."})
			return
		}
		user.ID = id
		user, err := user.Get()
		if err != nil {
			log.Println("route ReadUser, user.Get:", err)
			if err.Error() == "not found" {
				render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"})
				return
			}
			render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
			return
		}
		for i, post := range user.Posts {
			if !post.Published {
				user.Posts = append(user.Posts[:i], user.Posts[i+1:]...)
			}
		}
		if len(user.Posts) == 0 {
			p := make([]Post, 0)
			user.Posts = p
		}
		render.R.JSON(w, 200, user)
	case "user":
		id, ok := SessionGetValue(r, "id")
		if !ok {
			log.Println("route ReadUser, SessionGetValue:", ok)
			SessionDelete(w, r, "id")
			render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.")
			return
		}
		user.ID = id
		user, err := user.Get()
		if err != nil {
			log.Println("route ReadUser, user.Get:", err)
			SessionDelete(w, r, "id")
			render.R.HTML(w, 500, "error", err)
			return
		}
		render.R.HTML(w, 200, "user/index", user)
	}
}
Пример #5
0
// Get or search.Get returns all posts which contain parameter search.Query in either
// post.Title or post.Content.
// Returns []Post and error object.
func (search Search) Get() (Search, error) {
	var post Post
	posts, err := post.GetAll()
	if err != nil {
		return search, err
	}
	for _, post := range posts {
		if post.Published {
			// posts are searched for a match in both content and title, so here
			// we declare two scanners for them
			content := bufio.NewScanner(strings.NewReader(post.Markdown))
			title := bufio.NewScanner(strings.NewReader(post.Title))
			// Blackfriday makes smartypants corrections some characters, which break the search
			content.Split(bufio.ScanWords)
			title.Split(bufio.ScanWords)
			// content is scanned trough Jaro-Winkler distance with
			// quite strict matching score of 0.9/1
			// matching score this high would most likely catch only different
			// capitalization and small typos
			//
			// since we are already in a for loop, we have to break the
			// iteration here by going to label End to avoid showing a
			// duplicate search result
			//
			// the condition after the OR operator limits searches to words basically
			// for example, searching for foobarbarbar would match foobar, but for now
			// we want to limit the searches to contain only the word foobar
			for content.Scan() {
				if jwd.Calculate(content.Text(), search.Query) >= 0.9 || strings.Contains(search.Query, content.Text()+" ") {
					search.Posts = append(search.Posts, post)
					goto End
				}
			}
			for title.Scan() {
				if jwd.Calculate(title.Text(), search.Query) >= 0.9 || strings.Contains(search.Query, title.Text()+" ") {
					search.Posts = append(search.Posts, post)
					goto End
				}
			}
		}
	End:
	}
	if len(search.Posts) == 0 {
		search.Posts = make([]Post, 0)
	}
	return search, nil
}

// SearchPost is a route which returns all posts and aggregates the ones which contain
// the POSTed search query in either Title or Content field.
func SearchPost(w http.ResponseWriter, r *http.Request) {

	search, err := GetSearch(r)
	if err != nil {
		log.Println("route SearchPost, context GetSearch:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	search, err = search.Get()
	if err != nil {
		log.Println("route SearchPost, search.Get:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	switch Root(r) {
	case "api":
		render.R.JSON(w, 200, search.Posts)
	case "posts":
		render.R.HTML(w, 200, "search", search.Posts)
	}
}

// CreatePost is a route which creates a new post according to the posted data.
// API renderponse contains the created post object and normal request redirects to "/user" page.
// Does not publish the post automatically. See PublishPost for more.
func CreatePost(w http.ResponseWriter, r *http.Request) {

	post, err := GetPost(r)
	if err != nil {
		log.Println("route CreatePost, context GetPost:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	var user User
	id, ok := SessionGetValue(r, "id")
	if !ok {
		log.Println("route CreatePost, SessionGetValue:", ok)
		SessionDelete(w, r, "id")
		render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.")
		return
	}
	user.ID = id
	user, err = user.Get()
	if err != nil {
		log.Println("route CreatePost, user.Get:", err)
		SessionDelete(w, r, "id")
		render.R.HTML(w, 500, "error", err)
		return
	}

	post, err = post.Insert(user)
	if err != nil {
		log.Println("route CreatePost, post.Insert:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}
	switch Root(r) {
	case "api":
		render.R.JSON(w, 200, post)
	case "posts":
		http.Redirect(w, r, "/user", 302)
	}
}

// ReadPosts is a route which returns all posts without merged owner data (although the object does include author field)
// Not available on frontend, so therefore it only returns a JSON renderponse, hence the post iteration in Go.
func ReadPosts(w http.ResponseWriter, r *http.Request) {
	var post Post
	published := make([]Post, 0)
	posts, err := post.GetAll()
	if err != nil {
		log.Println("route ReadPosts, post.GetAll:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}
	for _, post := range posts {
		if post.Published {
			published = append(published, post)
		}
	}
	render.R.JSON(w, 200, published)
}

// ReadPost is a route which returns post with given post.Slug.
// Returns post data on JSON call and displays a formatted page on frontend.
func ReadPost(w http.ResponseWriter, r *http.Request) {
	log.Println("url query:", r.URL.Query())
	var post Post
	if vestigo.Param(r, "slug") == "new" {
		render.R.JSON(w, 400, map[string]interface{}{"error": "There can't be a post called 'new'."})
		return
	}
	post.Slug = vestigo.Param(r, "slug")
	post, err := post.Get()
	if err != nil {
		log.Println("route ReadPost, post.Get:", err)
		if err.Error() == "not found" {
			render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"})
			return
		}
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}
	go post.Increment()
	switch Root(r) {
	case "api":
		render.R.JSON(w, 200, post)
	case "post":
		render.R.HTML(w, 200, "post/display", post)
	}
}

// EditPost is a route which returns a post object to be displayed and edited on frontend.
// Not available for JSON API.
// Analogous to ReadPost. Could be replaced at some point.
func EditPost(w http.ResponseWriter, r *http.Request) {
	var post Post
	post.Slug = vestigo.Param(r, "slug")
	post, err := post.Get()
	if err != nil {
		log.Println("route EditPost, post.Get:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}
	render.R.HTML(w, 200, "post/edit", post)
}

// UpdatePost is a route which updates a post defined by martini parameter "title" with posted data.
// Requirender session cookie. JSON request returns the updated post object, frontend call will redirect to "/user".
func UpdatePost(w http.ResponseWriter, r *http.Request) {

	var post Post
	post.Slug = vestigo.Param(r, "slug")
	post, err := post.Get()
	if err != nil {
		log.Println("route UpdatePost, post.Get:", err)
		if err.Error() == "not found" {
			render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"})
			return
		}
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	id, ok := SessionGetValue(r, "id")
	if !ok {
		log.Println("route UpdatePost, SessionGetValue:", ok)
		SessionDelete(w, r, "id")
		render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.")
		return
	}
	if post.Author != id {
		log.Println("route UpdatePost, post.Author and id mismatch")
		render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"})
		return
	}

	entry, err := GetPost(r)
	if err != nil {
		log.Println("route UpdatePost, context GetPost:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	post, err = post.Update(entry)
	if err != nil {
		log.Println("route UpdatePost, post.Update:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	switch Root(r) {
	case "api":
		render.R.JSON(w, 200, post)
	case "post":
		http.Redirect(w, r, "/user", 302)
	}
}

// PublishPost is a route which publishes a post and therefore making it appear on frontpage and search.
// JSON request returns `HTTP 200 {"success": "Post published"}` on success. Frontend call will redirect to
// published page.
// Requirender active session cookie.
func PublishPost(w http.ResponseWriter, r *http.Request) {
	var post Post
	post.Slug = vestigo.Param(r, "slug")
	post, err := post.Get()
	if err != nil {
		log.Println("route PublishPost, post.Get:", err)
		if err.Error() == "not found" {
			render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"})
			return
		}
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	id, ok := SessionGetValue(r, "id")
	if !ok {
		log.Println("route PublishPost, SessionGetValue:", ok)
		SessionDelete(w, r, "id")
		render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.")
		return
	}
	if post.Author != id {
		log.Println("route PublishPost, post.Author and id mismatch")
		render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"})
		return
	}

	var entry Post
	entry = post
	entry.Published = true
	post, err = post.Update(entry)
	if err != nil {
		log.Println("route PublishPost, post.Update:", err)
		if err.Error() == "unauthorized" {
			render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"})
			return
		}
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	switch Root(r) {
	case "api":
		render.R.JSON(w, 200, map[string]interface{}{"success": "Post published"})
	case "post":
		http.Redirect(w, r, "/post/"+post.Slug, 302)
	}
}

// UnpublishPost is a route which unpublishes a post and therefore making it disappear from frontpage and search.
// JSON request returns `HTTP 200 {"success": "Post unpublished"}` on success. Frontend call will redirect to
// user control panel.
// Requirender active session cookie.
// The route is anecdotal to route PublishPost().
func UnpublishPost(w http.ResponseWriter, r *http.Request) {
	var post Post
	post.Slug = vestigo.Param(r, "slug")
	post, err := post.Get()
	if err != nil {
		log.Println("route UnpublishPost, post.Get:", err)
		if err.Error() == "not found" {
			render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"})
			return
		}
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	id, ok := SessionGetValue(r, "id")
	if !ok {
		log.Println("route UnpublishPost, SessionGetValue:", ok)
		SessionDelete(w, r, "id")
		render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.")
		return
	}

	if post.Author != id {
		log.Println("route UnpublishPost, author mismatch")
		render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"})
		return
	}

	err = post.Unpublish()
	if err != nil {
		log.Println("route UnpublishPost, post.Unpublish:", err)
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	switch Root(r) {
	case "api":
		render.R.JSON(w, 200, map[string]interface{}{"success": "Post unpublished"})
	case "post":
		http.Redirect(w, r, "/user", 302)
	}
}

// DeletePost is a route which deletes a post according to martini parameter "title".
// JSON request returns `HTTP 200 {"success": "Post deleted"}` on success. Frontend call will redirect to
// "/user" page on successful request.
// Requirender active session cookie.
func DeletePost(w http.ResponseWriter, r *http.Request) {
	var post Post
	post.Slug = vestigo.Param(r, "slug")
	post, err := post.Get()
	if err != nil {
		log.Println("route DeletePost, post.Get:", err)
		if err.Error() == "not found" {
			render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"})
			return
		}
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}

	id, ok := SessionGetValue(r, "id")
	if !ok {
		log.Println("route DeletePost, SessionGetValue:", ok)
		SessionDelete(w, r, "id")
		render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.")
		return
	}
	if post.Author != id {
		log.Println("route DeletePost, author mismatch")
		render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"})
		return
	}

	err = post.Delete()
	if err != nil {
		log.Println("route DeletePost, post.Delete:", err)
		if err.Error() == "unauthorized" {
			render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"})
			return
		}
		render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"})
		return
	}
	switch Root(r) {
	case "api":
		render.R.JSON(w, 200, map[string]interface{}{"success": "Post deleted"})
	case "post":
		http.Redirect(w, r, "/user", 302)
	}
}