// CreateComment creates a new comment on a post func (handler CommentHandler) CreateComment(c *gin.Context) { // lookup post by uuid postUUID := c.Param("puuid") var post table.Post if err := handler.db.Where("uuid = ?", postUUID).First(&post).Error; err != nil { c.JSON(http.StatusNotFound, resp.APIResponse{IsError: true, Message: "Question does not exist"}) return } // deserialize comment var comment table.Comment c.Bind(&comment) if !comment.IsValidForCreate() { c.JSON(http.StatusBadRequest, resp.APIResponse{IsError: true, Message: "Invalid data"}) return } comment.PostID = post.ID comment.UserID = auth.GetUserIDFromCookie(c) // create new comment if err := handler.db.Create(&comment).Error; err != nil { c.JSON(http.StatusInternalServerError, resp.APIResponse{IsError: true, Message: "Unknown error"}) return } // get comment that we just inserted with user info handler.db.Preload("User").Find(&comment) c.JSON(http.StatusCreated, resp.APIResponse{IsError: false, Value: comment}) }
// GetAllQuestionsCSV serves a csv file report of all the questions in a group // Columns: Question, Score (upvotes - downvotes), Total votes (upvotes + downvotes), Upvotes, Downvotes, Created by func (handler ExportHandler) GetAllQuestionsCSV(c *gin.Context) { gname := c.Param("gname") if !auth.HasAccessToGroup(auth.GetUserIDFromCookie(c), gname, handler.db) { c.JSON(http.StatusForbidden, resp.APIResponse{IsError: true, Message: "You don't have permission to access this group"}) return } var group table.Group if err := handler.db.Where("name = ?", gname).First(&group).Error; err != nil { c.JSON(http.StatusNotFound, resp.APIResponse{IsError: true, Message: "Group does not exist"}) return } // get all posts for a group with comments and users for those comments var posts []table.Post handler.db.First(&group, table.Group{Name: gname}) handler.db.Model(&group).Order("id").Preload("User").Association("Posts").Find(&posts) output := "Question,Score (upvotes - downvotes),Total votes (upvotes + downvotes),Upvotes,Downvotes,Created by" + "\n" for _, post := range posts { data := []string{post.Name, fmt.Sprintf("%v", int(post.Upvotes)-int(post.Downvotes)), fmt.Sprintf("%v", post.Upvotes+post.Downvotes), fmt.Sprintf("%v", post.Upvotes), fmt.Sprintf("%v", post.Downvotes), fmt.Sprintf("%v", post.User.FullName())} for i := range data { data[i] = escapeForCSV(data[i]) } output += strings.Join(data[:], ",") + "\n" } c.Writer.Header().Set("Content-Disposition", "attachment; filename=questions.csv") c.Writer.Header().Set("Content-Type", c.Request.Header.Get("Content-Type")) c.String(http.StatusOK, output) }
// CreatePost creates a new question func (handler PostHandler) CreatePost(c *gin.Context) { // lookup group by name groupname := c.Param("gname") var group table.Group if err := handler.db.Where("name = ?", groupname).First(&group).Error; err != nil { c.JSON(http.StatusNotFound, resp.APIResponse{IsError: true, Message: "Group does not exist"}) return } // deserialize post var post table.Post c.Bind(&post) if !post.IsValidForCreate() { c.JSON(http.StatusBadRequest, resp.APIResponse{IsError: true, Message: "Invalid data"}) return } post.GroupID = group.ID post.UUID = uuid.NewV4().String() //TODO make sure this doesn't break everything post.CreatedBy = auth.GetUserIDFromCookie(c) // create new question if err := handler.db.Create(&post).Error; err != nil { c.JSON(http.StatusConflict, resp.APIResponse{IsError: true, Message: "Question has already been asked"}) return } //TODO - this is not ideal... // go gives the timestamp more precision than pg stores, so we now get the obj from pg handler.db.Where("uuid = ?", post.UUID).First(&post) // make sure comments are empty slice and not nil post.Comments = make([]table.Comment, 0) c.JSON(http.StatusCreated, resp.APIResponse{IsError: false, Value: post}) }
// ShowAdminPage either shows the login page or the admin panel, depending // on whether the user is logged in and has access to the group func (gc AdminController) ShowAdminPage(c *gin.Context) { gname := c.Param("gname") if auth.IsLoggedIn(c) && auth.HasAccessToGroup(auth.GetUserIDFromCookie(c), gname, gc.db) { // user has admin access http.ServeFile(c.Writer, c.Request, "views/admin_panel.html") } else { // show login page http.ServeFile(c.Writer, c.Request, "views/admin_login.html") } }
// GetUserVotes gets the user's votes for a group func (handler VoteHandler) GetUserVotes(c *gin.Context) { gname := c.Param("gname") var group table.Group if err := handler.db.Where("name = ?", gname).First(&group).Error; err != nil { c.JSON(http.StatusNotFound, resp.APIResponse{IsError: true, Message: "Group does not exist"}) return } userID := auth.GetUserIDFromCookie(c) var votes []table.Vote if err := handler.db.Joins("left join posts on posts.id = votes.post_id").Where("posts.group_id = ? and votes.user_id = ?", group.ID, userID).Find(&votes).Error; err != nil { // return empty slice instead of nil for no data votes = make([]table.Vote, 0) } c.JSON(http.StatusOK, resp.APIResponse{IsError: false, Value: votes}) }
// DeletePost soft deletes a question func (handler PostHandler) DeletePost(c *gin.Context) { // lookup post by uuid postUUID := c.Param("puuid") var post table.Post if err := handler.db.Where("uuid = ?", postUUID).Preload("Group").First(&post).Error; err != nil { c.JSON(http.StatusNotFound, resp.APIResponse{IsError: true, Message: "Question does not exist"}) return } // make sure user is an admin if !auth.HasAccessToGroup(auth.GetUserIDFromCookie(c), post.Group.Name, handler.db) { c.JSON(http.StatusForbidden, resp.APIResponse{IsError: true, Message: "You don't have permission to access this group"}) return } handler.db.Delete(&post) c.String(http.StatusOK, "Successfully deleted") }
// GetTopUsersCSV serves a csv file report of users with the most votes // Columns: Name, Total votes func (handler ExportHandler) GetTopUsersCSV(c *gin.Context) { gname := c.Param("gname") if !auth.HasAccessToGroup(auth.GetUserIDFromCookie(c), gname, handler.db) { c.JSON(http.StatusForbidden, resp.APIResponse{IsError: true, Message: "You don't have permission to access this group"}) return } var group table.Group if err := handler.db.Where("name = ?", gname).First(&group).Error; err != nil { c.JSON(http.StatusNotFound, resp.APIResponse{IsError: true, Message: "Group does not exist"}) return } handler.db.First(&group, table.Group{Name: gname}) rows, err := handler.db.Table("users").Select("users.firstname, users.lastname, count(*) as total_votes").Joins("join votes on votes.user_id = users.id join posts on posts.id = votes.post_id join groups on groups.id = posts.group_id").Where("groups.id = ?", group.ID).Group("users.id, users.firstname, users.lastname").Rows() if err != nil { log.Printf("Unable to get top user rows: %s", err) c.JSON(http.StatusInternalServerError, resp.APIResponse{IsError: true, Message: "Unable to export top users"}) return } output := "Name,Total votes" + "\n" for rows.Next() { var firstname string var lastname string var totalVotes int rows.Scan(&firstname, &lastname, &totalVotes) data := []string{firstname + " " + lastname, fmt.Sprintf("%v", totalVotes)} for i := range data { data[i] = escapeForCSV(data[i]) } output += strings.Join(data[:], ",") + "\n" } c.Writer.Header().Set("Content-Disposition", "attachment; filename=top_users.csv") c.Writer.Header().Set("Content-Type", c.Request.Header.Get("Content-Type")) c.String(http.StatusOK, output) }
// CreateOrUpdateVote create a new vote or update the user's existing one func (handler VoteHandler) CreateOrUpdateVote(c *gin.Context) { puuid := c.Param("puuid") var voteReq req.VoteCreateRequest if err := c.BindJSON(&voteReq); err != nil { log.Printf("Unable request: %s", err) c.JSON(http.StatusBadRequest, resp.APIResponse{IsError: true, Message: "Error parsing request"}) return } if !voteReq.IsValid() { c.JSON(http.StatusBadRequest, resp.APIResponse{IsError: true, Message: "Invalid data"}) return } // lookup post by uuid var post table.Post if err := handler.db.Where("uuid = ?", puuid).First(&post).Error; err != nil { c.JSON(http.StatusOK, resp.APIResponse{IsError: true, Message: "Question does not exist"}) return } // get current user id userID := auth.GetUserIDFromCookie(c) // start transaction tx := handler.db.Begin() // attempt to get existing vote var vote table.Vote isChangingVote := false if err := tx.Where("user_id = ? AND post_id = ?", userID, post.ID).First(&vote).Error; err != nil { // vote does not exist vote.PostID = post.ID vote.PostUUID = post.UUID vote.UserID = userID vote.Value = voteReq.Value if err := tx.Create(&vote).Error; err != nil { log.Printf("Unable to create vote: %s", err) c.JSON(http.StatusInternalServerError, resp.APIResponse{IsError: true, Message: "Unable to create vote"}) return } } else { // don't allow user to vote same way multiple times if vote.Value == voteReq.Value { c.JSON(http.StatusConflict, resp.APIResponse{IsError: true, Message: "Already voted that way"}) return } // changing vote vote.Value = voteReq.Value isChangingVote = true tx.Save(&vote) } // update question's vote counts if vote.Value == 1 { post.Upvotes++ if isChangingVote { post.Downvotes-- } } else { post.Downvotes++ if isChangingVote { post.Upvotes-- } } tx.Save(&post) // commit transaction tx.Commit() c.JSON(http.StatusOK, resp.APIResponse{IsError: false, Value: post}) }