diff --git a/controllers/tweets_controller.go b/controllers/tweets_controller.go index f556d45..6a092ee 100644 --- a/controllers/tweets_controller.go +++ b/controllers/tweets_controller.go @@ -11,6 +11,44 @@ import ( const Per_page int = 30 +type JoinedMessage struct { + Author_id uint + Username string + Text string + Pub_Date uint + } + +func GetMessages(timelineType string, user int) []JoinedMessage { + + var joinedMessages []JoinedMessage + + if timelineType == "public" { + // Join messages and users tables for public timeline + database.DB.Table("messages"). + Select("messages.Author_id", "users.Username" ,"messages.Text", "messages.Pub_Date"). + Joins("left join users on users.id = messages.Author_id").Scan(&joinedMessages) + + } else if timelineType == "myTimeline" { + // Join messages, users, and followers for my timeline + database.DB.Table("messages"). + Select("messages.Author_id", "users.Username" ,"messages.Text", "messages.Pub_Date"). + Joins("left join followers on messages.author_id = followers.whom_id"). + Joins("left join users on users.id = messages.Author_id"). + Where("messages.flagged = ? AND (messages.author_id = ? OR followers.who_id = ?)", false, user, user). + Scan(&joinedMessages) + } else if timelineType == "individual"{ + // Join messages and users for an individual's timeline + database.DB.Table("messages"). + Select("messages.Author_id", "users.Username" ,"messages.Text", "messages.Pub_Date"). + Joins("left join users on users.id = messages.Author_id"). + Where("messages.flagged = ? AND (messages.author_id = ?)", false, user). + Scan(&joinedMessages) + } + + return joinedMessages + +} + // ENDPOINT: GET /ping func ping(c *gin.Context) { c.JSON(200, gin.H{ @@ -18,44 +56,102 @@ func ping(c *gin.Context) { }) } -// ENDPOINT: GET /public -func public(c *gin.Context) { //Displays the latest messages of all users - - var messages []entities.Message - - if err := database.DB.Where("Flagged = false").Order("Pub_Date desc").Limit(Per_page).Find(&messages).Error; err != nil { - log.Print("Ran into error during " + c.Request.RequestURI + ": " + err.Error()) - c.AbortWithStatus(400) - return +// ENDPOINT: GET / +func timeline(c *gin.Context) { + + // if there is NO session user, show public timeline + if user == -1 { + c.HTML(http.StatusOK, "timeline.html", gin.H{ + "messages": GetMessages("public", user), + }) + } else { + // if there exists a session user, show my timeline + c.HTML(http.StatusOK, "timeline.html", gin.H{ + "messages": GetMessages("myTimeline", user), + "user": user, + }) } - - c.HTML(http.StatusOK, "timeline.html", gin.H{ - "messages": messages, - }) } -// ENDPOINT: GET / -func timeline(c *gin.Context) { +// ENDPOINT: GET /public +func public(c *gin.Context) { - // check if there exists a session user, if not, return all messages - // For now just reuse the same endpoint handler as /public if user == -1 { - public(c) - return + c.HTML(http.StatusOK, "timeline.html", gin.H{ + "messages": GetMessages("public", user), + }) + } else { + // if there exists a session user, show my timeline + c.HTML(http.StatusOK, "timeline.html", gin.H{ + "messages": GetMessages("public", user), + "user": user, + }) } +} - var messages []entities.Message +// ENDPOINT: GET /:username +func username(c *gin.Context) { // Displays an individual's timeline - if err := database.DB.Table("messages"). - Joins("left join followers on messages.author_id = followers.whom_id"). - Where("messages.flagged = ? AND (messages.author_id = ? OR followers.who_id = ?)", false, user, user). - Limit(Per_page).Find(&messages).Error; err != nil { // ORDER BY DATE - log.Print("Ran into error during " + c.Request.RequestURI + ": " + err.Error()) + username := c.Param("username") // gets the from the url + userID, err := getUserId(username) + if err != nil { + log.Print("Bad request during " + c.Request.RequestURI + ": " + " User " + username + " not found") + c.Status(404) + return } - - c.HTML(http.StatusOK, "timeline.html", gin.H{ - "messages": messages, - }) + // if endpoint is a username + if username != "" { + // if logged in + if user != -1 { + followed := GetFollower(uint(userID), uint(user)) + var users_page = false + // If logged in user == endpoint + if user == int(userID) { + users_page = true + + c.HTML(http.StatusOK, "timeline.html", gin.H{ + "title": "My Timeline ONE", + "user": user, + "private": true, + "user_page": true, + "messages": GetMessages("myTimeline", user), + }) + } else { + // If following + if followed == true { + // If logged in and user != endpoint + c.HTML(http.StatusOK, "timeline.html", gin.H{ + "title": username + "'s Timeline TWO", + "user_timeline": true, + "private": true, + "user": username, + "followed": followed, + "user_page": users_page, + "messages": GetMessages("individual", int(userID)), + }) + } else { + // If not following + // If logged in and user != endpoint + c.HTML(http.StatusOK, "timeline.html", gin.H{ + "title": username + "'s Timeline THREE", + "user_timeline": true, + "private": true, + "user": username, + "user_page": users_page, + "messages": GetMessages("individual", int(userID)), + }) + } + } + } else { + // If not logged in + c.HTML(http.StatusOK, "timeline.html", gin.H{ + "title": username + "'s Timeline FOUR", + "user_timeline": true, + "private": true, + "messages": GetMessages("individual", int(userID)), + }) + } + } } // ENDPOINT: POST /add_message @@ -74,7 +170,7 @@ func addMessage(c *gin.Context) { //Registers a new message for the user. c.Bind(&body) message := entities.Message{ - Author_id: uint(user), // AUTHOR ID SHOULD GET SESSION USER ID + Author_id: uint(user), // GET SESSION USER ID Text: body.Text, Pub_Date: uint(time.Now().Unix()), Flagged: false, @@ -87,10 +183,7 @@ func addMessage(c *gin.Context) { //Registers a new message for the user. return } - //redirect to timeline ("/") - //c.Redirect(200, "/") // For some reason, this returns error 500, but I assume it's because the path doesn't exist yet? - // Temporarily dont redirect - c.String(200, "Your message was recorded") + c.Redirect(http.StatusFound, "/") } @@ -104,14 +197,14 @@ func SetupRouter() *gin.Engine { router.GET("/", timeline) router.GET("/public", public) router.GET("/:username", username) - router.POST("/:username/follow", usernameFollow) - router.DELETE("/:username/unfollow", usernameUnfollow) + router.GET("/:username/follow", usernameFollow) + router.GET("/:username/unfollow", usernameUnfollow) router.POST("/register", register_user) router.GET("/register", register) router.POST("/add_message", addMessage) router.POST("/login", login_user) router.GET("/login", loginf) - router.PUT("/logout", logoutf) // Changed temporarily to satisfy tests, should it be put or get? + router.GET("/logout", logout_user) router.GET("/sim/latest", simLatest) router.POST("/sim/register", simRegister) diff --git a/controllers/user_controller.go b/controllers/user_controller.go index c1225b7..82828d4 100644 --- a/controllers/user_controller.go +++ b/controllers/user_controller.go @@ -1,7 +1,7 @@ package controllers import ( - "fmt" + //"fmt" "github.com/ContainerMaintainers/MiniTwit-Golang/database" "github.com/ContainerMaintainers/MiniTwit-Golang/infrastructure/entities" "github.com/gin-gonic/gin" @@ -17,113 +17,88 @@ var ( user = -1 ) -// ENDPOINT: POST /:username/follow -func usernameFollow(c *gin.Context) { //Adds the current user as follower of the given user +// ENDPOINT: GET /:username/follow +func usernameFollow(c *gin.Context) { // Adds the current user as follower of the given user if user == -1 { log.Print("Bad request during " + c.Request.RequestURI + ": " + " No user logged in") c.AbortWithStatus(401) return - } - - username := c.Param("username") - who := uint(user) // SHOULD GET SESSION USER ID + } else { - whom, err := getUserId(username) - if err != nil { - log.Print("Ran into error during " + c.Request.RequestURI + ": " + err.Error()) - c.Status(404) - return - } + username := c.Param("username") + who := uint(user) // SESSION USER ID + whom, err := getUserId(username) + if err != nil { + log.Print("Ran into error during " + c.Request.RequestURI + ": " + err.Error()) + c.Status(404) + return + } - follow := entities.Follower{ - Who_ID: who, // ! - Whom_ID: whom, - } + follow := entities.Follower{ + Who_ID: who, + Whom_ID: whom, + } - err = database.DB.Create(&follow).Error - if err != nil { //when user is already following 'whom' - log.Print("Bad request during " + c.Request.RequestURI + ": " + "Already following " + username) - c.Status(400) - return + err = database.DB.Create(&follow).Error + if err != nil { //when user is already following 'whom' + log.Print("Bad request during " + c.Request.RequestURI + ": " + "Already following " + username) + c.Status(400) + return + } + //database.DB.Create(&follow) + //c.String(200, fmt.Sprintf("You are now following \"%s\"", username)) + c.Redirect(http.StatusFound, "/"+username) } - - // c.JSON(200, gin.H{ - // "follower": follow, - // }) - c.String(200, fmt.Sprintf("You are now following \"%s\"", username)) - } -// ENDPOINT: GET /:username -func username(c *gin.Context) { //Displays a user's tweets - - username := c.Param("username") //gets the from the url - - userID, err := getUserId(username) - if err != nil { - log.Print("Bad request during " + c.Request.RequestURI + ": " + " User " + username + " not found") - c.Status(404) - return - } - - var messagesFromUser []entities.Message - - if err := database.DB.Where("author_id = ?", userID).Limit(Per_page).Find(&messagesFromUser).Error; err != nil { - log.Print("Ran into error during " + c.Request.RequestURI + ": " + err.Error()) - c.AbortWithStatus(404) - return +func GetFollower(follower uint, following uint) bool { + var follows []entities.Follower + if follower == following { + return false + } else { + database.DB.Find(&follows).Where("who_ID = ?", following).Where("whom_ID = ?", follower).First(&follows) + return len(follows) > 0 } - - c.HTML(http.StatusOK, "timeline.html", gin.H{ - "messagesFromUser": messagesFromUser, - }) - } -// ENDPOINT: DELETE /:username/unfollow -func usernameUnfollow(c *gin.Context) { //Adds the current user as follower of the given user +// ENDPOINT: GET /:username/unfollow +func usernameUnfollow(c *gin.Context) { // Adds the current user as follower of the given user if user == -1 { log.Print("Bad request during " + c.Request.RequestURI + ": " + " No user logged in") c.AbortWithStatus(401) return - } - - username := c.Param("username") - - who := uint(user) // SHOULD GET SESSION USER ID + } else { + username := c.Param("username") + who := uint(user) // SESSION USER ID + whom, err := getUserId(username) + if err != nil { + log.Print("Bad request during " + c.Request.RequestURI + ": " + " User " + username + " not found") + c.Status(404) + return + } - whom, err := getUserId(username) - if err != nil { - log.Print("Bad request during " + c.Request.RequestURI + ": " + " User " + username + " not found") - c.Status(404) - return - } + unfollow := entities.Follower{ + Who_ID: who, + Whom_ID: whom, + } - unfollow := entities.Follower{ - Who_ID: who, // ! - Whom_ID: whom, - } + err = database.DB.Where("Who_ID = ? AND Whom_ID = ?", unfollow.Who_ID, unfollow.Whom_ID).Delete(&unfollow).Error + if err != nil { // when user is already following 'whom' + log.Print("Ran into error during " + c.Request.RequestURI + ": " + err.Error()) + c.Status(400) + return + } - err = database.DB.Where("Who_ID = ? AND Whom_ID = ?", unfollow.Who_ID, unfollow.Whom_ID).Delete(&unfollow).Error - if err != nil { //when user is already following 'whom' - log.Print("Ran into error during " + c.Request.RequestURI + ": " + err.Error()) - c.Status(400) - return + //c.String(200, fmt.Sprintf("You are no longer following \"%s\"", username)) // Had to make it 200 to satisfy tests + c.Redirect(http.StatusFound, "/"+username) } - - // c.JSON(204, gin.H{ - // "follower": unfollow, - // }) - c.String(200, fmt.Sprintf("You are no longer following \"%s\"", username)) // Had to make it 200 to satisfy tests for some reason - } // ENDPOINT: POST /login func login_user(c *gin.Context) { //Logs the user in. - //check if there exists a session user, if yes, redirect to timeline ("/") var body struct { Username string `form:"username" json:"username"` @@ -139,7 +114,7 @@ func login_user(c *gin.Context) { //Logs the user in. error := "" - //if POST req? + // if POST req? if _, err := getUserId(body.Username); err != nil { log.Print("Bad request during " + c.Request.RequestURI + ": Invalid username " + body.Username) error = "Invalid username" @@ -149,41 +124,33 @@ func login_user(c *gin.Context) { //Logs the user in. } if error == "" { - //give message "You were logged in." - //set session user to body.Username - - // Until session stuff is working, just keep track of the user through a global variable - // In this case the id is replaced with the username + // give message "You were logged in." + if userID, err := getUserId(body.Username); err != nil { log.Print("Ran into error during " + c.Request.RequestURI + ": " + err.Error()) user = -1 } else { + // set session user to body.Username user = int(userID) } - //redirect to timeline ("/") - //c.Redirect(200, "/") - user_path := "/" + body.Username - location := url.URL{Path: user_path} + // redirect to timeline ("/") + location := url.URL{Path: "/"} c.Redirect(http.StatusFound, location.RequestURI()) - - // Temporarily dont redirect //c.String(200, "You were logged in") } else { c.String(400, error) } - } -// ENDPOINT: PUT /logout -func logoutf(c *gin.Context) { +// ENDPOINT: GET /logout +func logout_user(c *gin.Context) { //clear session user user = -1 - - //c.Redirect(200, "/") - // Temporarily don't redirect - c.String(200, "You were logged out") + + //c.String(200, "You were logged out") + c.Redirect(http.StatusFound, "/") } // ENDPOINT: GET /register @@ -200,6 +167,39 @@ func loginf(c *gin.Context) { }) } +// Registration Helper Function +func ValidRegistration(c *gin.Context, username string, email string, password1 string, password2 string) bool { + + //error = "" + if password1 == "" || email == "" || username == "" { + log.Print("Bad request during " + c.Request.RequestURI + ": " + " Missing Field") + c.HTML(http.StatusOK, "register.html", gin.H{ + "error": "All fields are required", + }) + return false + } else if email == "" || !strings.Contains(email, "@") { + log.Print("Bad request during " + c.Request.RequestURI + ": " + " Invalid email") + c.HTML(http.StatusOK, "register.html", gin.H{ + "error": "You have to enter a valid email address", + }) + return false + } else if password1 != password2 { + log.Print("Bad request during " + c.Request.RequestURI + ": " + " Passwords do not match") + c.HTML(http.StatusOK, "register.html", gin.H{ + "error": "The two passwords do not match", + }) + return false + } else if _, err := getUserId(username); err == nil { + log.Print("Ran into error during " + c.Request.RequestURI + ": " + " Username already taken") + c.HTML(http.StatusOK, "register.html", gin.H{ + "error": "The username is already taken", + }) + return false + } + return true +} + + // ENDPOINT: POST /register func register_user(c *gin.Context) { @@ -219,24 +219,7 @@ func register_user(c *gin.Context) { error := "" - if body.Username == "" { - log.Print("Bad request during " + c.Request.RequestURI + ": " + " No username provided") - error = "You have to enter a username" - } else if body.Email == "" || !strings.Contains(body.Email, "@") { - log.Print("Bad request during " + c.Request.RequestURI + ": " + " Invalid email") - error = "You have to enter a valid email address" - } else if body.Password == "" { - log.Print("Bad request during " + c.Request.RequestURI + ": " + " No password provided") - error = "You have to enter a password" - } else if body.Password != body.Password2 { - log.Print("Bad request during " + c.Request.RequestURI + ": " + " Passwords do not match") - error = "The two passwords do not match" - } else if _, err := getUserId(body.Username); err == nil { - log.Print("Ran into error during " + c.Request.RequestURI + ": " + " Username already taken") - error = "The username is already taken" - } - - if error == "" { + if ValidRegistration(c, body.Username, body.Email, body.Password, body.Password2) { user := entities.User{ Username: body.Username, Email: body.Email, @@ -287,7 +270,7 @@ func checkPasswordHash(username string, enteredPW string) (bool, error) { return true, nil } -func getUserId(username string) (uint, error) { //Convenience method to look up the id for a username. +func getUserId(username string) (uint, error) { // Convenience method to look up the id for a username. var user entities.User diff --git a/static/favicon_io/about.txt b/static/favicon_io/about.txt new file mode 100644 index 0000000..e141ac2 --- /dev/null +++ b/static/favicon_io/about.txt @@ -0,0 +1,6 @@ +This favicon was generated using the following graphics from Twitter Twemoji: + +- Graphics Title: 1f423.svg +- Graphics Author: Copyright 2020 Twitter, Inc and other contributors (https://github.com/twitter/twemoji) +- Graphics Source: https://github.com/twitter/twemoji/blob/master/assets/svg/1f423.svg +- Graphics License: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) diff --git a/static/favicon_io/android-chrome-192x192.png b/static/favicon_io/android-chrome-192x192.png new file mode 100644 index 0000000..63c5c35 Binary files /dev/null and b/static/favicon_io/android-chrome-192x192.png differ diff --git a/static/favicon_io/android-chrome-512x512.png b/static/favicon_io/android-chrome-512x512.png new file mode 100644 index 0000000..bbcc390 Binary files /dev/null and b/static/favicon_io/android-chrome-512x512.png differ diff --git a/static/favicon_io/apple-touch-icon.png b/static/favicon_io/apple-touch-icon.png new file mode 100644 index 0000000..53e216a Binary files /dev/null and b/static/favicon_io/apple-touch-icon.png differ diff --git a/static/favicon_io/favicon-16x16.png b/static/favicon_io/favicon-16x16.png new file mode 100644 index 0000000..b6ffee8 Binary files /dev/null and b/static/favicon_io/favicon-16x16.png differ diff --git a/static/favicon_io/favicon-32x32.png b/static/favicon_io/favicon-32x32.png new file mode 100644 index 0000000..ca19c30 Binary files /dev/null and b/static/favicon_io/favicon-32x32.png differ diff --git a/static/favicon_io/favicon.ico b/static/favicon_io/favicon.ico new file mode 100644 index 0000000..0d9784e Binary files /dev/null and b/static/favicon_io/favicon.ico differ diff --git a/static/favicon_io/site.webmanifest b/static/favicon_io/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/static/favicon_io/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 4573a55..78dd763 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,11 +1,17 @@ -Welcome | MiniTwit + + Welcome | MiniTwit + + + + +

MiniTwit