コード例 #1
0
ファイル: social.go プロジェクト: gnoirzox/gohackernews
// FacebookPostTopStory facebook posts the top story
func FacebookPostTopStory(context schedule.Context) {
	context.Log("#info posting top story facebook")

	// Get the top story
	q := stories.Popular().Limit(1).Order("rank desc, points desc, id desc")

	// Don't fetch old stories
	q.Where("created_at > current_timestamp - interval '6 hours'")

	// Fetch the story
	results, err := stories.FindAll(q)
	if err != nil {
		context.Logf("#error getting top story for fb %s", err)
		return
	}

	if len(results) > 0 {
		story := results[0]
		context.Logf("#info facebook posting %s", story.Name)
		err := facebook.Post(story.Name, story.Url)
		if err != nil {
			context.Logf("#error facebook post top story %s", err)
			return
		}
		// Do not record fb posts - this could lead to duplicates...
		// we should perhaps have a join table for social media posts, rather than dates on stories?
	} else {
		context.Logf("#warn no top story found for fb")
	}
}
コード例 #2
0
ファイル: mail.go プロジェクト: gnoirzox/gohackernews
// DailyEmail sends a daily email to subscribed users with top stories - change this to WeeklyEmail
// before putting it into production
// We should probably only do this for kenny at present
func DailyEmail(context schedule.Context) {
	context.Log("Sending daily email")

	// First fetch our stories over 5 points
	q := stories.Popular()

	// Must be within 7 days
	q.Where("created_at > current_timestamp - interval '7 day'")

	// Order by rank
	q.Order("rank desc, points desc, id desc")

	// Don't fetch stories that have already been mailed
	q.Where("newsletter_at IS NULL")

	// Fetch the stories
	topStories, err := stories.FindAll(q)
	if err != nil {
		context.Logf("#error getting top story tweet %s", err)
		return
	}

	if len(topStories) == 0 {
		context.Logf("#warn no stories found for newsletter")
		return
	}

	// Now fetch our recipient (initially just Kenny as this is in testing)
	recipient, err := users.Find(1)
	if err != nil {
		context.Logf("#error getting email reciipents %s", err)
		return
	}

	var jobStories []*stories.Story

	// Email recipients the stories in question - we should perhaps save in db so that we can
	// have an issue number and always reproduce the digests?
	mailContext := map[string]interface{}{
		"stories": topStories,
		"jobs":    jobStories,
	}
	err = mail.SendOne(recipient.Email, "Go News Digest", "users/views/mail/digest.html.got", mailContext)
	if err != nil {
		context.Logf("#error sending email %s", err)
		return
	}

	// Record that these stories have been mailed in db
	params := map[string]string{"newsletter_at": query.TimeString(time.Now().UTC())}
	err = q.Order("").UpdateAll(params)
	if err != nil {
		context.Logf("#error updating top story newsletter_at %s", err)
		return
	}

}
コード例 #3
0
ファイル: social.go プロジェクト: gnoirzox/gohackernews
// TweetTopStory tweets the top story
func TweetTopStory(context schedule.Context) {
	context.Log("Sending top story tweet")

	// Get the top story which has not been tweeted yet, newer than 1 day (we don't look at older stories)
	q := stories.Popular().Limit(1).Order("rank desc, points desc, id desc")

	// Don't fetch old stories - at some point soon this can come down to 1 day
	// as all older stories will have been tweeted
	// For now omit this as we have a backlog of old unposted stories
	// q.Where("created_at > current_timestamp - interval '60 days'")

	// Don't fetch stories that have already been tweeted
	q.Where("tweeted_at IS NULL")

	// Fetch the stories
	results, err := stories.FindAll(q)
	if err != nil {
		context.Logf("#error getting top story tweet %s", err)
		return
	}

	if len(results) > 0 {
		story := results[0]

		// Link to the primary url for this type of story
		url := story.PrimaryURL()

		if strings.HasPrefix(url, "/") {
			url = "https://golangnews.com" + url
		}

		tweet := fmt.Sprintf("%s #golang %s", story.Name, url)

		context.Logf("#info sending tweet:%s", tweet)

		_, err := twitter.Tweet(tweet)
		if err != nil {
			context.Logf("#error tweeting top story %s", err)
			return
		}

		// Record that this story has been tweeted in db
		params := map[string]string{"tweeted_at": query.TimeString(time.Now().UTC())}
		err = story.Update(params)
		if err != nil {
			context.Logf("#error updating top story tweet %s", err)
			return
		}
	} else {
		context.Logf("#warn no top story found for tweet")
	}

}
コード例 #4
0
ファイル: index.go プロジェクト: gnoirzox/gohackernews
// HandleIndex displays a list of stories at /stories
func HandleIndex(context router.Context) error {

	// Build a query
	q := stories.Query().Limit(listLimit)

	// Order by date by default
	q.Where("points > -6").Order("created_at desc")

	// Filter if necessary - this assumes name and summary cols
	filter := context.Param("q")
	if len(filter) > 0 {

		// Replace special characters with escaped sequence
		filter = strings.Replace(filter, "_", "\\_", -1)
		filter = strings.Replace(filter, "%", "\\%", -1)

		wildcard := "%" + filter + "%"

		// Perform a wildcard search for name or url
		q.Where("stories.name ILIKE ? OR stories.url ILIKE ?", wildcard, wildcard)

		// If filtering, order by rank, not by date
		q.Order("rank desc, points desc, id desc")
	}

	// Set the offset in pages if we have one
	page := int(context.ParamInt("page"))
	if page > 0 {
		q.Offset(listLimit * page)
	}

	// Fetch the stories
	results, err := stories.FindAll(q)
	if err != nil {
		return router.InternalError(err)
	}

	// Render the template
	view := view.New(context)

	setStoriesMetadata(view, context.Request())
	view.AddKey("page", page)
	view.AddKey("stories", results)

	if context.Param("format") == ".xml" {
		view.Layout("")
		view.Template("stories/views/index.xml.got")
	}

	return view.Render()

}
コード例 #5
0
ファイル: upvoted.go プロジェクト: gnoirzox/gohackernews
// HandleUpvoted displays a list of stories the user has upvoted in the past
func HandleUpvoted(context router.Context) error {

	// Build a query
	q := stories.Query().Limit(listLimit)

	// Select only above 0 points,  Order by rank, then points, then name
	q.Where("points > 0").Order("rank desc, points desc, id desc")

	// Select only stories which the user has upvoted
	user := authorise.CurrentUser(context)
	if !user.Anon() {
		// Can we use a join instead?
		v := query.New("votes", "story_id").Select("select story_id as id from votes").Where("user_id=? AND story_id IS NOT NULL AND points > 0", user.Id)

		storyIDs := v.ResultIDs()
		if len(storyIDs) > 0 {
			q.WhereIn("id", storyIDs)
		}
	}

	// Set the offset in pages if we have one
	page := int(context.ParamInt("page"))
	if page > 0 {
		q.Offset(listLimit * page)
	}

	// Fetch the stories
	results, err := stories.FindAll(q)
	if err != nil {
		return router.InternalError(err)
	}

	// Render the template
	view := view.New(context)
	setStoriesMetadata(view, context.Request())
	view.AddKey("page", page)
	view.AddKey("stories", results)
	view.Template("stories/views/index.html.got")

	if context.Param("format") == ".xml" {
		view.Layout("")
		view.Template("stories/views/index.xml.got")
	}

	return view.Render()

}
コード例 #6
0
ファイル: code.go プロジェクト: gnoirzox/gohackernews
// HandleCode displays a list of stories linking to repos (github etc) using gravity to order them
// responds to GET /stories/code
func HandleCode(context router.Context) error {

	// Build a query
	q := stories.Query().Where("points > -6").Order("rank desc, points desc, id desc").Limit(listLimit)

	// Restrict to stories with have a url starting with github.com or bitbucket.org
	// other code repos can be added later
	q.Where("url ILIKE 'https://github.com%'").OrWhere("url ILIKE 'https://bitbucket.org'")

	// Set the offset in pages if we have one
	page := int(context.ParamInt("page"))
	if page > 0 {
		q.Offset(listLimit * page)
	}

	// Fetch the stories
	results, err := stories.FindAll(q)
	if err != nil {
		return router.InternalError(err)
	}

	// Render the template
	view := view.New(context)
	setStoriesMetadata(view, context.Request())
	view.AddKey("page", page)
	view.AddKey("stories", results)

	view.Template("stories/views/index.html.got")

	if context.Param("format") == ".xml" {
		view.Layout("")
		view.Template("stories/views/index.xml.got")
	}

	return view.Render()

}
コード例 #7
0
ファイル: show.go プロジェクト: gnoirzox/gohackernews
// HandleShow serve a get request at /users/1
func HandleShow(context router.Context) error {

	// No auth - this is public

	// Find the user
	user, err := users.Find(context.ParamInt("id"))
	if err != nil {
		context.Logf("#error parsing user id: %s", err)
		return router.NotFoundError(err)
	}

	// Get the user comments
	q := comments.Where("user_id=?", user.Id).Limit(10).Order("created_at desc")
	userComments, err := comments.FindAll(q)
	if err != nil {
		return router.InternalError(err)
	}

	// Get the user stories
	q = stories.Where("user_id=?", user.Id).Limit(50).Order("created_at desc")
	userStories, err := stories.FindAll(q)
	if err != nil {
		return router.InternalError(err)
	}

	// Render the Template
	view := view.New(context)
	view.AddKey("user", user)
	view.AddKey("comments", userComments)
	view.AddKey("stories", userStories)
	view.AddKey("meta_title", user.Name)
	view.AddKey("meta_desc", user.Name)

	return view.Render()

}
コード例 #8
0
ファイル: home.go プロジェクト: gnoirzox/gohackernews
// HandleHome displays a list of stories using gravity to order them
// used for the home page for gravity rank see votes.go
// responds to GET /
func HandleHome(context router.Context) error {

	// Build a query
	q := stories.Query().Limit(listLimit)

	// Select only above 0 points,  Order by rank, then points, then name
	q.Where("points > 0").Order("rank desc, points desc, id desc")

	// Set the offset in pages if we have one
	page := int(context.ParamInt("page"))
	if page > 0 {
		q.Offset(listLimit * page)
	}

	// Fetch the stories
	results, err := stories.FindAll(q)
	if err != nil {
		return router.InternalError(err)
	}

	// Render the template
	view := view.New(context)
	setStoriesMetadata(view, context.Request())
	view.AddKey("page", page)
	view.AddKey("stories", results)
	view.AddKey("meta_title", "London TechCity News")
	view.Template("stories/views/index.html.got")

	if context.Param("format") == ".xml" {
		view.Layout("")
		view.Template("stories/views/index.xml.got")
	}

	return view.Render()

}
コード例 #9
0
ファイル: create.go プロジェクト: gnoirzox/gohackernews
// HandleCreate handles the POST of the create form for stories
func HandleCreate(context router.Context) error {

	// Check csrf token
	err := authorise.AuthenticityToken(context)
	if err != nil {
		return router.NotAuthorizedError(err)
	}

	// Check permissions - if not logged in and above 1 points, redirect to error
	if !authorise.CurrentUser(context).CanSubmit() {
		return router.NotAuthorizedError(nil, "Sorry", "You need to be registered and have more than 1 points to submit stories.")
	}

	// Get params
	params, err := context.Params()
	if err != nil {
		return router.InternalError(err)
	}

	// Get user details
	user := authorise.CurrentUser(context)
	ip := getUserIP(context)

	// Process urls
	url := params.Get("url")

	// Strip trailing slashes on url before comparisons
	if strings.HasSuffix(url, "/") {
		url = strings.Trim(url, "/")
	}

	// Strip ?utm_source etc - remove all after ?utm_source
	if strings.Contains(url, "?utm_") {
		url = strings.Split(url, "?utm_")[0]
	}

	// Strip url fragments (For example trailing # on medium urls)
	if strings.Contains(url, "#") {
		url = strings.Split(url, "#")[0]
	}

	// Rewrite mobile youtube links
	if strings.HasPrefix(url, "https://m.youtube.com") {
		url = strings.Replace(url, "https://m.youtube.com", "https://www.youtube.com", 1)
	}

	params.Set("url", url)

	// Check that no story with this url already exists
	q := stories.Where("url=?", url)
	duplicates, err := stories.FindAll(q)
	if err != nil {
		return router.InternalError(err)
	}

	if len(duplicates) > 0 {
		story := duplicates[0]

		// Check we have no votes already from this user, if we do fail
		if storyHasUserVote(story, user) {
			return router.NotAuthorizedError(err, "Vote Failed", "Sorry you are not allowed to vote twice, nice try!")

		}

		// Add a point to dupe and return
		addStoryVote(story, user, ip, 1)
		return router.Redirect(context, story.URLShow())
	}
	// Clean params according to role
	accepted := stories.AllowedParams()
	if authorise.CurrentUser(context).Admin() {
		accepted = stories.AllowedParamsAdmin()
	}
	cleanedParams := params.Clean(accepted)

	// Set a few params
	cleanedParams["points"] = "1"
	cleanedParams["user_id"] = fmt.Sprintf("%d", user.Id)
	cleanedParams["user_name"] = user.Name

	id, err := stories.Create(cleanedParams)
	if err != nil {
		return err // Create returns a router.Error
	}

	// Log creation
	context.Logf("#info Created story id,%d", id)

	// Redirect to the new story
	story, err := stories.Find(id)
	if err != nil {
		return router.InternalError(err)
	}

	// We need to add a vote to the story here too by adding a join to the new id
	err = recordStoryVote(story, user, ip, +1)
	if err != nil {
		return err
	}

	// Re-rank stories
	err = updateStoriesRank()
	if err != nil {
		return err
	}

	return router.Redirect(context, story.URLIndex())
}