From 51a35cb804c23516192f163ca02d690870eb1281 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Tue, 24 Dec 2024 00:26:14 -0500 Subject: [PATCH 1/3] reverted the endpoints, added the likes to projects --- backend/api/internal/database/dev.sqlite3 | Bin 102400 -> 102400 bytes .../api/internal/database/project_queries.go | 115 ++++++++++++++++++ .../api/internal/handlers/project_routes.go | 34 ++++++ backend/api/main.go | 44 ++++--- 4 files changed, 170 insertions(+), 23 deletions(-) diff --git a/backend/api/internal/database/dev.sqlite3 b/backend/api/internal/database/dev.sqlite3 index ffe85d4e15e27344bf1fc0d3da6b03fa216e2f5d..45e1e3e5ceae36a28631759d0b4d1682f2f5a2a2 100644 GIT binary patch delta 77 zcmZozz}B#UZGsdNAHzf$Cm^{oVaa_qM*d$6{J%B}3cTkRWME-r=452zVqjoE5@u&$ XW#MGxWLhMku!WJ)VLM|2<9~es1>O?P delta 40 vcmZozz}B#UZGsdN@9&8+PC#;F!jk)&Sp@#bZ&P4oblA)&@Lzse0;2%{Hy#e5 diff --git a/backend/api/internal/database/project_queries.go b/backend/api/internal/database/project_queries.go index a1906b9..ef7f239 100644 --- a/backend/api/internal/database/project_queries.go +++ b/backend/api/internal/database/project_queries.go @@ -429,3 +429,118 @@ func RemoveProjectFollow(username string, projectID string) (int, error) { return http.StatusOK, nil } + +// CreateProjectLike creates a like relationship between a user and a project. +// +// Parameters: +// - username: The username of the user creating the like. +// - projectID: The ID of the project to unfollow (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or the user is not liking the project. +func CreateProjectLike(username string, strProjId string) (int, error) { + // get user ID from username, implicitly checks if user exists + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse project ID + projId, err := strconv.Atoi(strProjId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing user id: %v", strProjId) + } + + // verify project exists + _, err = QueryProject(projId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred verifying the project exists: %v", err) + } + + // check if the like already exists + var exists bool + query := `SELECT EXISTS ( + SELECT 1 FROM ProjectLikes WHERE user_id = ? AND project_id = ? + )` + err = DB.QueryRow(query, user_id, projId).Scan(&exists) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred checking like existence: %v", err) + } + if exists { + // like already exists, but we return success to keep it idempotent + return http.StatusCreated, nil + } + + // insert the like + insertQuery := `INSERT INTO ProjectLikes (user_id, project_id) VALUES (?, ?)` + _, err = DB.Exec(insertQuery, user_id, projId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to insert project like: %v", err) + } + + // update the likes column + updateQuery := `UPDATE Projects SET likes = likes + 1 WHERE id = ?` + _, err = DB.Exec(updateQuery, projId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to update likes count: %v", err) + } + + return http.StatusCreated, nil +} + +// RemoveProjectLike deletes a like relationship between a user and a project. +// +// Parameters: +// - username: The username of the user removing the like. +// - projectID: The ID of the project to unfollow (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or the user is not liking the project. +func RemoveProjectLike(username string, strProjId string) (int, error) { + // get user ID + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse project ID + projId, err := strconv.Atoi(strProjId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing username id: %v", strProjId) + } + + // verify project exists + _, err = QueryProject(projId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred verifying the project exists: %v", err) + } + + // perform the delete operation + deleteQuery := `DELETE FROM ProjectLikes WHERE user_id = ? AND project_id = ?` + result, err := DB.Exec(deleteQuery, user_id, projId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to delete project like: %v", err) + } + + // check if any rows were actually deleted + rowsAffected, err := result.RowsAffected() + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to check rows affected: %v", err) + } + + if rowsAffected == 0 { + // if no rows were deleted, return success to keep idempotency + return http.StatusNoContent, nil + } + + // update the likes column + updateQuery := `UPDATE Projects SET likes = likes - 1 WHERE id = ?` + _, err = DB.Exec(updateQuery, projId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to update likes count: %v", err) + } + + return http.StatusOK, nil +} diff --git a/backend/api/internal/handlers/project_routes.go b/backend/api/internal/handlers/project_routes.go index d34eb78..5c5e89a 100644 --- a/backend/api/internal/handlers/project_routes.go +++ b/backend/api/internal/handlers/project_routes.go @@ -330,3 +330,37 @@ func UnfollowProject(context *gin.Context) { } context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v unfollowed %v", username, projectId)}) } + +// LikeProject handles POST requests to like a project. +// It expects the `username` and `project_id` parameters in the URL. +// Returns: +// - Appropriate error code (404 if missing data, 500 if error) for database failures or invalid input. +// On success, responds with a 200 OK status and a confirmation message. +func LikeProject(context *gin.Context) { + username := context.Param("username") + projectId := context.Param("project_id") + + httpcode, err := database.CreateProjectLike(username, projectId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to like project: %v", err)) + return + } + context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v likes %v", username, projectId)}) +} + +// UnlikeProject handles POST requests to unlike a project. +// It expects the `username` and `project_id` parameters in the URL. +// Returns: +// - Appropriate error code (404 if missing data, 500 if error) for database failures or invalid input. +// On success, responds with a 200 OK status and a confirmation message. +func UnlikeProject(context *gin.Context) { + username := context.Param("username") + projectId := context.Param("project_id") + + httpcode, err := database.RemoveProjectLike(username, projectId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to unlike project: %v", err)) + return + } + context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v unliked %v", username, projectId)}) +} diff --git a/backend/api/main.go b/backend/api/main.go index 98383f2..4e124a5 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -35,57 +35,55 @@ func main() { router.GET("/health", HealthCheck) - // Users router.GET("/users/:username", handlers.GetUserByUsername) router.POST("/users", handlers.CreateUser) router.PUT("/users/:username", handlers.UpdateUserInfo) router.DELETE("/users/:username", handlers.DeleteUser) router.GET("/users/:username/followers", handlers.GetUsersFollowers) - router.GET("/users/:username/following", handlers.GetUsersFollowing) + router.GET("/users/:username/follows", handlers.GetUsersFollowing) router.GET("/users/:username/followers/usernames", handlers.GetUsersFollowersUsernames) - router.GET("/users/:username/following/usernames", handlers.GetUsersFollowingUsernames) + router.GET("/users/:username/follows/usernames", handlers.GetUsersFollowingUsernames) - router.POST("/users/:username/follow/:target_user", handlers.FollowUser) - router.POST("/users/:username/unfollow/:target_user", handlers.UnfollowUser) + router.POST("/users/:username/follow/:new_follow", handlers.FollowUser) + router.POST("/users/:username/unfollow/:unfollow", handlers.UnfollowUser) - // Projects router.GET("/projects/:project_id", handlers.GetProjectById) router.POST("/projects", handlers.CreateProject) router.PUT("/projects/:project_id", handlers.UpdateProjectInfo) router.DELETE("/projects/:project_id", handlers.DeleteProject) - - router.GET("/users/:user_id/projects", handlers.GetProjectsByUserId) + router.GET("/projects/by-user/:user_id", handlers.GetProjectsByUserId) router.GET("/projects/:project_id/followers", handlers.GetProjectFollowers) - router.GET("/users/:username/projects/following", handlers.GetProjectFollowing) + router.GET("/projects/follows/:username", handlers.GetProjectFollowing) router.GET("/projects/:project_id/followers/usernames", handlers.GetProjectFollowersUsernames) - router.GET("/users/:username/projects/following/names", handlers.GetProjectFollowingNames) + router.GET("/projects/follows/:username/names", handlers.GetProjectFollowingNames) + + router.POST("/projects/:username/follow/:project_id", handlers.FollowProject) + router.POST("/projects/:username/unfollow/:project_id", handlers.UnfollowProject) - router.POST("/users/:username/follow/project/:project_id", handlers.FollowProject) - router.POST("/users/:username/unfollow/project/:project_id", handlers.UnfollowProject) + router.POST("/projects/:username/likes/:project_id", handlers.LikeProject) + router.POST("/projects/:username/unlikes/:project_id", handlers.UnlikeProject) - // Posts router.GET("/posts/:post_id", handlers.GetPostById) router.POST("/posts", handlers.CreatePost) router.PUT("/posts/:post_id", handlers.UpdatePostInfo) router.DELETE("/posts/:post_id", handlers.DeletePost) - router.GET("/users/:user_id/posts", handlers.GetPostsByUserId) - router.GET("/projects/:project_id/posts", handlers.GetPostsByProjectId) + router.GET("/posts/by-user/:user_id", handlers.GetPostsByUserId) + router.GET("/posts/by-project/:project_id", handlers.GetPostsByProjectId) - // Comments - router.POST("/posts/:post_id/comments", handlers.CreateCommentOnPost) - router.POST("/projects/:project_id/comments", handlers.CreateCommentOnProject) - router.POST("/comments/:comment_id/reply", handlers.CreateCommentOnComment) + router.POST("/comments/for-post/:post_id", handlers.CreateCommentOnPost) + router.POST("/comments/for-project/:project_id", handlers.CreateCommentOnProject) + router.POST("/comments/for-comment/:comment_id", handlers.CreateCommentOnComment) router.GET("/comments/:comment_id", handlers.GetCommentById) router.PUT("/comments/:comment_id", handlers.UpdateCommentContent) router.DELETE("/comments/:comment_id", handlers.DeleteComment) - router.GET("/users/:user_id/comments", handlers.GetCommentsByUserId) - router.GET("/posts/:post_id/comments", handlers.GetCommentsByPostId) - router.GET("/projects/:project_id/comments", handlers.GetCommentsByProjectId) - router.GET("/comments/:comment_id/replies", handlers.GetCommentsByCommentId) + router.GET("/comments/by-user/:user_id", handlers.GetCommentsByUserId) + router.GET("/comments/by-post/:post_id", handlers.GetCommentsByPostId) + router.GET("/comments/by-project/:project_id", handlers.GetCommentsByProjectId) + router.GET("/comments/by-comment/:comment_id", handlers.GetCommentsByCommentId) var dbinfo, dbtype string if DEBUG { From fa32bd281f9ee869567ee6be6b94fed89b8bc503 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Wed, 25 Dec 2024 23:22:49 -0500 Subject: [PATCH 2/3] added like functionality --- .../api/internal/database/comment_queries.go | 117 +++++++++++++++++ .../api/internal/database/create_tables.sql | 2 +- backend/api/internal/database/dev.sqlite3 | Bin 102400 -> 102400 bytes backend/api/internal/database/post_queries.go | 122 +++++++++++++++++- .../api/internal/database/project_queries.go | 8 +- .../api/internal/handlers/comment_routes.go | 34 +++++ backend/api/internal/handlers/post_routes.go | 34 +++++ backend/api/main.go | 18 ++- 8 files changed, 326 insertions(+), 9 deletions(-) diff --git a/backend/api/internal/database/comment_queries.go b/backend/api/internal/database/comment_queries.go index 580b1d2..c09d2e4 100644 --- a/backend/api/internal/database/comment_queries.go +++ b/backend/api/internal/database/comment_queries.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "time" + "strconv" "backend/api/internal/types" ) @@ -463,3 +464,119 @@ func QueryUpdateCommentContent(id int, newContent string) (int16, error) { return 200, nil } + + +// CreateCommentLike creates a like relationship between a user and a comment. +// +// Parameters: +// - username: The username of the user creating the like. +// - strCommentID: The ID of the comment to like (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or the user is not liking the comment. +func CreateCommentLike(username string, strCommentId string) (int, error) { + // get user ID from username, implicitly checks if user exists + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse comment ID + commentId, err := strconv.Atoi(strCommentId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing user id: %v", err) + } + + // verify comment exists + _, err = QueryComment(commentId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred verifying the comment exists: %v", err) + } + + // check if the like already exists + var exists bool + query := `SELECT EXISTS ( + SELECT 1 FROM CommentLikes WHERE user_id = ? AND post_id = ? + )` + err = DB.QueryRow(query, user_id, commentId).Scan(&exists) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred checking like existence: %v", err) + } + if exists { + // like already exists, but we return success to keep it idempotent + return http.StatusCreated, nil + } + + // insert the like + insertQuery := `INSERT INTO CommentLikes (user_id, post_id) VALUES (?, ?)` + _, err = DB.Exec(insertQuery, user_id, commentId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to insert comment like: %v", err) + } + + // update the likes column + updateQuery := `UPDATE Comments SET likes = likes + 1 WHERE id = ?` + _, err = DB.Exec(updateQuery, commentId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to update likes count: %v", err) + } + + return http.StatusCreated, nil +} + +// RemoveCommentLike deletes a like relationship between a user and a comment. +// +// Parameters: +// - username: The username of the user removing the like. +// - strPostId: The ID of the comment to unlike (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or the user is not liking the comment. +func RemoveCommentLike(username string, strCommentId string) (int, error) { + // get user ID + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse post ID + commentId, err := strconv.Atoi(strCommentId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing username id: %v", err) + } + + // verify post exists + _, err = QueryPost(commentId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred verifying the comment exists: %v", err) + } + + // perform the delete operation + deleteQuery := `DELETE FROM CommentLikes WHERE user_id = ? AND post_id = ?` + result, err := DB.Exec(deleteQuery, user_id, commentId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to delete comment like: %v", err) + } + + // check if any rows were actually deleted + rowsAffected, err := result.RowsAffected() + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to check rows affected: %v", err) + } + + if rowsAffected == 0 { + // if no rows were deleted, return success to keep idempotency + return http.StatusNoContent, nil + } + + // update the likes column + updateQuery := `UPDATE Comments SET likes = likes - 1 WHERE id = ?` + _, err = DB.Exec(updateQuery, commentId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to update likes count: %v", err) + } + + return http.StatusOK, nil +} diff --git a/backend/api/internal/database/create_tables.sql b/backend/api/internal/database/create_tables.sql index 28e5c08..9f844ab 100644 --- a/backend/api/internal/database/create_tables.sql +++ b/backend/api/internal/database/create_tables.sql @@ -86,7 +86,7 @@ CREATE TABLE Comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, parent_comment_id INTEGER, - likes INTEGER NOT NULL, + likes INTEGER DEFAULT 0, creation_date TIMESTAMP NOT NULL, user_id INTEGER NOT NULL, FOREIGN KEY (parent_comment_id) REFERENCES Comments(id) ON DELETE CASCADE, diff --git a/backend/api/internal/database/dev.sqlite3 b/backend/api/internal/database/dev.sqlite3 index 45e1e3e5ceae36a28631759d0b4d1682f2f5a2a2..667d1bff42acf46b201d5d4f4382f73648a7697e 100644 GIT binary patch delta 472 zcmZozz}B#UZGyDmAqEBpNg##-&J`1Nj2RDYOgK}^s5g0Gz1U%QDNX; z%kRbag>MR<74I9~b-Yo$0z5l;5_nWLHnQ;8Sn;qkcrr2?bNb}xXJ_W6E2QNYl_wUZ zD3s(Yl%y7yfP^wj6q%Wf42(>44UKe-j1>$FtiWhKhwhux*z}XYA!*WSPD!pD{)*mw|`v8w3AwK1*I!o-^EQxvRL`xOh3%aFnur<1pB+ sSitDR!N|NluY&O`D<`)AySS_@V{7JgmpaBJ(=TK)@^AlE$H*lJ0CRPzx6x#E->)_;Qzq?g8u>k4gL$81qJr-bFi_oa56IT{NH?IzovjZ-xmfR9!&=R zwftWEB79T$ta#t>uH%j372w&)lfa|7v5|$x#)^lX!P68-*fVk%Glpbj7Aqv@=jNv7 zl_-=a7AvHr=A@RSrsy#n85o)98XD;u87mkVSb@?1%?Wp=FtGq#tuXn=JsC3-W}qny z_)NiO$Yihkdsu!k@c#n(;61+}0}CTFCnFOV&{Rf#xG*~dE6`({Op62*wmC2|Dr{yH z_%A;#fl*$tn?;T_i0KUTYsPq{$&4C|8XFs5ux*z}XYA!*WS+h(pD{)*kAa8nI|Ki5 zp#Rx;&T_BguI6&*;^SD$QO5S2!*FBcb++x_${5QS8CkaHRxqAr<>V4z7nhY~Y)zl; eT*tU%`l~EPzUg(Dj4spnrZe(xcduvMDF6U|)qMg0 diff --git a/backend/api/internal/database/post_queries.go b/backend/api/internal/database/post_queries.go index a8e1531..eceec57 100644 --- a/backend/api/internal/database/post_queries.go +++ b/backend/api/internal/database/post_queries.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "net/http" + "strconv" "time" "backend/api/internal/types" @@ -159,7 +160,7 @@ func QueryPostsByUserId(userId int) ([]types.Post, int, error) { } return nil, http.StatusInternalServerError, err } - posts = append(posts, post) + posts = append(posts, post) } return posts, http.StatusOK, nil @@ -201,7 +202,124 @@ func QueryPostsByProjectId(projId int) ([]types.Post, int, error) { } return nil, http.StatusInternalServerError, err } - posts = append(posts, post) + posts = append(posts, post) } return posts, http.StatusOK, nil } + +// CreatePostLike creates a like relationship between a user and a post. +// +// Parameters: +// - username: The username of the user creating the like. +// - strPostID: The ID of the project to like (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or the user is not liking the post. +func CreatePostLike(username string, strPostId string) (int, error) { + // get user ID from username, implicitly checks if user exists + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse post ID + postId, err := strconv.Atoi(strPostId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing post_id id: %v", err) + } + + // verify post exists + post, err := QueryPost(postId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred verifying the post exists: %v", err) + } else if post == nil { + return http.StatusNotFound, fmt.Errorf("Post ID %d does not exist", postId) + } + + // check if the like already exists + var exists bool + query := `SELECT EXISTS ( + SELECT 1 FROM PostLikes WHERE user_id = ? AND post_id = ? + )` + err = DB.QueryRow(query, user_id, postId).Scan(&exists) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred checking like existence: %v", err) + } + if exists { + // like already exists, but we return success to keep it idempotent + return http.StatusCreated, nil + } + + // insert the like + insertQuery := `INSERT INTO PostLikes (user_id, post_id) VALUES (?, ?)` + _, err = DB.Exec(insertQuery, user_id, postId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to insert post like: %v", err) + } + + // update the likes column + updateQuery := `UPDATE Posts SET likes = likes + 1 WHERE id = ?` + _, err = DB.Exec(updateQuery, postId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to update likes count: %v", err) + } + + return http.StatusCreated, nil +} + +// RemovePostLike deletes a like relationship between a user and a post. +// +// Parameters: +// - username: The username of the user removing the like. +// - strPostID: The ID of the post to unlike (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or the user is not liking the post. +func RemovePostLike(username string, strPostId string) (int, error) { + // get user ID + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse post ID + postId, err := strconv.Atoi(strPostId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing username id: %v", err) + } + + // verify post exists + _, err = QueryPost(postId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("An error occurred verifying the post exists: %v", err) + } + + // perform the delete operation + deleteQuery := `DELETE FROM PostLikes WHERE user_id = ? AND post_id = ?` + result, err := DB.Exec(deleteQuery, user_id, postId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to delete post like: %v", err) + } + + // check if any rows were actually deleted + rowsAffected, err := result.RowsAffected() + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to check rows affected: %v", err) + } + + if rowsAffected == 0 { + // if no rows were deleted, return success to keep idempotency + return http.StatusNoContent, nil + } + + // update the likes column + updateQuery := `UPDATE Posts SET likes = likes - 1 WHERE id = ?` + _, err = DB.Exec(updateQuery, postId) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to update likes count: %v", err) + } + + return http.StatusOK, nil +} diff --git a/backend/api/internal/database/project_queries.go b/backend/api/internal/database/project_queries.go index ef7f239..c57559d 100644 --- a/backend/api/internal/database/project_queries.go +++ b/backend/api/internal/database/project_queries.go @@ -434,7 +434,7 @@ func RemoveProjectFollow(username string, projectID string) (int, error) { // // Parameters: // - username: The username of the user creating the like. -// - projectID: The ID of the project to unfollow (as a string, converted internally). +// - projectID: The ID of the project to like (as a string, converted internally). // // Returns: // - int: HTTP-like status code indicating the result of the operation. @@ -449,7 +449,7 @@ func CreateProjectLike(username string, strProjId string) (int, error) { // parse project ID projId, err := strconv.Atoi(strProjId) if err != nil { - return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing user id: %v", strProjId) + return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing proj_id: %v", err) } // verify project exists @@ -493,7 +493,7 @@ func CreateProjectLike(username string, strProjId string) (int, error) { // // Parameters: // - username: The username of the user removing the like. -// - projectID: The ID of the project to unfollow (as a string, converted internally). +// - projectID: The ID of the project to unlike (as a string, converted internally). // // Returns: // - int: HTTP-like status code indicating the result of the operation. @@ -508,7 +508,7 @@ func RemoveProjectLike(username string, strProjId string) (int, error) { // parse project ID projId, err := strconv.Atoi(strProjId) if err != nil { - return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing username id: %v", strProjId) + return http.StatusInternalServerError, fmt.Errorf("An error occurred parsing username id: %v", err) } // verify project exists diff --git a/backend/api/internal/handlers/comment_routes.go b/backend/api/internal/handlers/comment_routes.go index 7ba0565..dd927c5 100644 --- a/backend/api/internal/handlers/comment_routes.go +++ b/backend/api/internal/handlers/comment_routes.go @@ -411,3 +411,37 @@ func UpdateCommentContent(context *gin.Context) { "comment": updatedComment, }) } + +// LikeComment handles POST requests to like a comment. +// It expects the `username` and `comment_id` parameters in the URL. +// Returns: +// - Appropriate error code (404 if missing data, 500 if error) for database failures or invalid input. +// On success, responds with a 200 OK status and a confirmation message. +func LikeComment(context *gin.Context) { + username := context.Param("username") + commentId := context.Param("comment_id") + + httpcode, err := database.CreateCommentLike(username, commentId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to like comment: %v", err)) + return + } + context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v likes %v", username, commentId)}) +} + +// UnlikeComment handles POST requests to unlike a comment. +// It expects the `username` and `comment_id` parameters in the URL. +// Returns: +// - Appropriate error code (404 if missing data, 500 if error) for database failures or invalid input. +// On success, responds with a 200 OK status and a confirmation message. +func UnlikeComment(context *gin.Context) { + username := context.Param("username") + commentId := context.Param("comment_id") + + httpcode, err := database.RemoveCommentLike(username, commentId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to unlike comment: %v", err)) + return + } + context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v unliked %v", username, commentId)}) +} diff --git a/backend/api/internal/handlers/post_routes.go b/backend/api/internal/handlers/post_routes.go index e2c0e46..82ef960 100644 --- a/backend/api/internal/handlers/post_routes.go +++ b/backend/api/internal/handlers/post_routes.go @@ -268,3 +268,37 @@ func UpdatePostInfo(context *gin.Context) { "post": updatedPost, }) } + +// LikePost handles POST requests to like a post. +// It expects the `username` and `post_id` parameters in the URL. +// Returns: +// - Appropriate error code (404 if missing data, 500 if error) for database failures or invalid input. +// On success, responds with a 200 OK status and a confirmation message. +func LikePost(context *gin.Context) { + username := context.Param("username") + postId := context.Param("post_id") + + httpcode, err := database.CreatePostLike(username, postId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to like post: %v", err)) + return + } + context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v likes %v", username, postId)}) +} + +// UnlikePost handles POST requests to unlike a post. +// It expects the `username` and `post_id` parameters in the URL. +// Returns: +// - Appropriate error code (404 if missing data, 500 if error) for database failures or invalid input. +// On success, responds with a 200 OK status and a confirmation message. +func UnlikePost(context *gin.Context) { + username := context.Param("username") + postId := context.Param("post_id") + + httpcode, err := database.RemovePostLike(username, postId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to unlike post: %v", err)) + return + } + context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v unliked %v", username, postId)}) +} diff --git a/backend/api/main.go b/backend/api/main.go index 4e124a5..1971365 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "os" @@ -20,6 +21,10 @@ func HealthCheck(context *gin.Context) { } func main() { + if DEBUG { + gin.SetMode(gin.DebugMode) + } + log.SetOutput(os.Stdout) logger.InitLogger() router := gin.Default() @@ -62,8 +67,8 @@ func main() { router.POST("/projects/:username/follow/:project_id", handlers.FollowProject) router.POST("/projects/:username/unfollow/:project_id", handlers.UnfollowProject) - router.POST("/projects/:username/likes/:project_id", handlers.LikeProject) - router.POST("/projects/:username/unlikes/:project_id", handlers.UnlikeProject) + router.POST("/projects/:username/likes/:project_id", handlers.LikeProject) + router.POST("/projects/:username/unlikes/:project_id", handlers.UnlikeProject) router.GET("/posts/:post_id", handlers.GetPostById) router.POST("/posts", handlers.CreatePost) @@ -73,6 +78,9 @@ func main() { router.GET("/posts/by-user/:user_id", handlers.GetPostsByUserId) router.GET("/posts/by-project/:project_id", handlers.GetPostsByProjectId) + router.POST("/posts/:username/likes/:post_id", handlers.LikePost) + router.POST("/posts/:username/unlikes/:post_id", handlers.UnlikePost) + router.POST("/comments/for-post/:post_id", handlers.CreateCommentOnPost) router.POST("/comments/for-project/:project_id", handlers.CreateCommentOnProject) router.POST("/comments/for-comment/:comment_id", handlers.CreateCommentOnComment) @@ -85,6 +93,12 @@ func main() { router.GET("/comments/by-project/:project_id", handlers.GetCommentsByProjectId) router.GET("/comments/by-comment/:comment_id", handlers.GetCommentsByCommentId) + router.POST("/comments/:username/likes/:comment_id", handlers.LikeComment) + router.POST("/comments/:username/unlikes/:comment_id", handlers.UnlikeComment) + + log.Println("we are in the func (log.Println)") + fmt.Println("we are in the func (fmt.Println)") + var dbinfo, dbtype string if DEBUG { dbinfo = "./api/internal/database/dev.sqlite3" From d771d5df705639e21805c4faf3ea0b84a9c91a02 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Thu, 26 Dec 2024 23:28:04 -0500 Subject: [PATCH 3/3] bugfixes to like creating and deleting, implemented query for like --- .../api/internal/database/comment_queries.go | 56 ++++++++++++++++-- backend/api/internal/database/dev.sqlite3 | Bin 102400 -> 102400 bytes backend/api/internal/database/post_queries.go | 47 ++++++++++++++- .../api/internal/database/project_queries.go | 47 ++++++++++++++- .../api/internal/handlers/comment_routes.go | 17 ++++++ backend/api/internal/handlers/post_routes.go | 17 ++++++ .../api/internal/handlers/project_routes.go | 21 ++++++- backend/api/main.go | 7 +-- 8 files changed, 198 insertions(+), 14 deletions(-) diff --git a/backend/api/internal/database/comment_queries.go b/backend/api/internal/database/comment_queries.go index c09d2e4..f6814c8 100644 --- a/backend/api/internal/database/comment_queries.go +++ b/backend/api/internal/database/comment_queries.go @@ -4,8 +4,8 @@ import ( "database/sql" "fmt" "net/http" + "strconv" "time" - "strconv" "backend/api/internal/types" ) @@ -465,7 +465,6 @@ func QueryUpdateCommentContent(id int, newContent string) (int16, error) { return 200, nil } - // CreateCommentLike creates a like relationship between a user and a comment. // // Parameters: @@ -497,7 +496,7 @@ func CreateCommentLike(username string, strCommentId string) (int, error) { // check if the like already exists var exists bool query := `SELECT EXISTS ( - SELECT 1 FROM CommentLikes WHERE user_id = ? AND post_id = ? + SELECT 1 FROM CommentLikes WHERE user_id = ? AND comment_id = ? )` err = DB.QueryRow(query, user_id, commentId).Scan(&exists) if err != nil { @@ -505,11 +504,11 @@ func CreateCommentLike(username string, strCommentId string) (int, error) { } if exists { // like already exists, but we return success to keep it idempotent - return http.StatusCreated, nil + return http.StatusOK, nil } // insert the like - insertQuery := `INSERT INTO CommentLikes (user_id, post_id) VALUES (?, ?)` + insertQuery := `INSERT INTO CommentLikes (user_id, comment_id) VALUES (?, ?)` _, err = DB.Exec(insertQuery, user_id, commentId) if err != nil { return http.StatusInternalServerError, fmt.Errorf("Failed to insert comment like: %v", err) @@ -554,7 +553,7 @@ func RemoveCommentLike(username string, strCommentId string) (int, error) { } // perform the delete operation - deleteQuery := `DELETE FROM CommentLikes WHERE user_id = ? AND post_id = ?` + deleteQuery := `DELETE FROM CommentLikes WHERE user_id = ? AND comment_id = ?` result, err := DB.Exec(deleteQuery, user_id, commentId) if err != nil { return http.StatusInternalServerError, fmt.Errorf("Failed to delete comment like: %v", err) @@ -580,3 +579,48 @@ func RemoveCommentLike(username string, strCommentId string) (int, error) { return http.StatusOK, nil } + +// QueryCommentLike queries for a like relationship between a user and a post. +// +// Parameters: +// - username: The username of the user removing the like. +// - postID: The ID of the post to unlike (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or. +func QueryCommentLike(username string, strCommId string) (int, bool, error) { + // get user ID from username, implicitly checks if user exists + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse post ID + commId, err := strconv.Atoi(strCommId) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred parsing comment_id: %v", err) + } + + // verify post exists + _, err = QueryComment(commId) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred verifying the comment exists: %v", err) + } + + // check if the like already exists + var exists bool + query := `SELECT EXISTS ( + SELECT 1 FROM CommentLikes WHERE user_id = ? AND comment_id = ? + )` + err = DB.QueryRow(query, user_id, commId).Scan(&exists) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred checking like existence: %v", err) + } + if exists { + return http.StatusOK, true, nil + } else { + return http.StatusOK, false, nil + } + +} diff --git a/backend/api/internal/database/dev.sqlite3 b/backend/api/internal/database/dev.sqlite3 index 667d1bff42acf46b201d5d4f4382f73648a7697e..eaf8fdbc33beb8874488ae787b00d19481129e0a 100644 GIT binary patch delta 201 zcmZozz}B#UZGw~#zbFF(gCr0`0q2d0Iz~+Vq8k&=)H3Q!URW0?ri^b&Qz!4sA?0Q_H9~d11ZSWHCR!&4G1)3>XDA z`>o$*z{be`lY#%|W5+ NnML4_{IUc_0|3UYCK3Pu diff --git a/backend/api/internal/database/post_queries.go b/backend/api/internal/database/post_queries.go index eceec57..1a9da36 100644 --- a/backend/api/internal/database/post_queries.go +++ b/backend/api/internal/database/post_queries.go @@ -248,7 +248,7 @@ func CreatePostLike(username string, strPostId string) (int, error) { } if exists { // like already exists, but we return success to keep it idempotent - return http.StatusCreated, nil + return http.StatusOK, nil } // insert the like @@ -323,3 +323,48 @@ func RemovePostLike(username string, strPostId string) (int, error) { return http.StatusOK, nil } + +// QueryPostLike queries for a like relationship between a user and a post. +// +// Parameters: +// - username: The username of the user removing the like. +// - postID: The ID of the post to unlike (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or. +func QueryPostLike(username string, strPostId string) (int, bool, error) { + // get user ID from username, implicitly checks if user exists + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse post ID + postId, err := strconv.Atoi(strPostId) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred parsing post_id: %v", err) + } + + // verify post exists + _, err = QueryPost(postId) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred verifying the post exists: %v", err) + } + + // check if the like already exists + var exists bool + query := `SELECT EXISTS ( + SELECT 1 FROM PostLikes WHERE user_id = ? AND post_id = ? + )` + err = DB.QueryRow(query, user_id, postId).Scan(&exists) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred checking like existence: %v", err) + } + if exists { + return http.StatusOK, true, nil + } else { + return http.StatusOK, false, nil + } + +} diff --git a/backend/api/internal/database/project_queries.go b/backend/api/internal/database/project_queries.go index c57559d..4cff323 100644 --- a/backend/api/internal/database/project_queries.go +++ b/backend/api/internal/database/project_queries.go @@ -469,7 +469,7 @@ func CreateProjectLike(username string, strProjId string) (int, error) { } if exists { // like already exists, but we return success to keep it idempotent - return http.StatusCreated, nil + return http.StatusOK, nil } // insert the like @@ -544,3 +544,48 @@ func RemoveProjectLike(username string, strProjId string) (int, error) { return http.StatusOK, nil } + +// QueryProjectLike queries for a like relationship between a user and a project. +// +// Parameters: +// - username: The username of the user removing the like. +// - projectID: The ID of the project to unlike (as a string, converted internally). +// +// Returns: +// - int: HTTP-like status code indicating the result of the operation. +// - error: An error if the operation fails or. +func QueryProjectLike(username string, strProjId string) (int, bool, error) { + // get user ID from username, implicitly checks if user exists + user_id, err := GetUserIdByUsername(username) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred getting id for username: %v", err) + } + + // parse project ID + projId, err := strconv.Atoi(strProjId) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred parsing proj_id: %v", err) + } + + // verify project exists + _, err = QueryProject(projId) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred verifying the project exists: %v", err) + } + + // check if the like already exists + var exists bool + query := `SELECT EXISTS ( + SELECT 1 FROM ProjectLikes WHERE user_id = ? AND project_id = ? + )` + err = DB.QueryRow(query, user_id, projId).Scan(&exists) + if err != nil { + return http.StatusInternalServerError, false, fmt.Errorf("An error occurred checking like existence: %v", err) + } + if exists { + return http.StatusOK, true, nil + } else { + return http.StatusOK, false, nil + } + +} diff --git a/backend/api/internal/handlers/comment_routes.go b/backend/api/internal/handlers/comment_routes.go index dd927c5..4f37c0e 100644 --- a/backend/api/internal/handlers/comment_routes.go +++ b/backend/api/internal/handlers/comment_routes.go @@ -445,3 +445,20 @@ func UnlikeComment(context *gin.Context) { } context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v unliked %v", username, commentId)}) } + +// IsCommentLiked handles GET requests to query for a comment like. +// It expects the `username` and `comment_id` parameters in the URL. +// Returns: +// - Appropriate error code for database failures or invalid input. +// On success, responds with a 200 OK status and a status message. +func IsCommentLiked(context *gin.Context) { + username := context.Param("username") + commentId := context.Param("comment_id") + + httpcode, exists, err := database.QueryCommentLike(username, commentId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to query for comment like: %v", err)) + return + } + context.JSON(httpcode, gin.H{"status": exists}) +} diff --git a/backend/api/internal/handlers/post_routes.go b/backend/api/internal/handlers/post_routes.go index 82ef960..9b68aca 100644 --- a/backend/api/internal/handlers/post_routes.go +++ b/backend/api/internal/handlers/post_routes.go @@ -302,3 +302,20 @@ func UnlikePost(context *gin.Context) { } context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v unliked %v", username, postId)}) } + +// IsPostLiked handles GET requests to query for a post like. +// It expects the `username` and `post_id` parameters in the URL. +// Returns: +// - Appropriate error code for database failures or invalid input. +// On success, responds with a 200 OK status and a status message. +func IsPostLiked(context *gin.Context) { + username := context.Param("username") + postId := context.Param("post_id") + + httpcode, exists, err := database.QueryPostLike(username, postId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to query for post like: %v", err)) + return + } + context.JSON(httpcode, gin.H{"status": exists}) +} diff --git a/backend/api/internal/handlers/project_routes.go b/backend/api/internal/handlers/project_routes.go index 5c5e89a..54f8d06 100644 --- a/backend/api/internal/handlers/project_routes.go +++ b/backend/api/internal/handlers/project_routes.go @@ -345,7 +345,7 @@ func LikeProject(context *gin.Context) { RespondWithError(context, httpcode, fmt.Sprintf("Failed to like project: %v", err)) return } - context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v likes %v", username, projectId)}) + context.JSON(httpcode, gin.H{"message": fmt.Sprintf("%v likes %v", username, projectId)}) } // UnlikeProject handles POST requests to unlike a project. @@ -362,5 +362,22 @@ func UnlikeProject(context *gin.Context) { RespondWithError(context, httpcode, fmt.Sprintf("Failed to unlike project: %v", err)) return } - context.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%v unliked %v", username, projectId)}) + context.JSON(httpcode, gin.H{"message": fmt.Sprintf("%v unliked %v", username, projectId)}) +} + +// IsProjectLiked handles GET requests to query for a project like. +// It expects the `username` and `project_id` parameters in the URL. +// Returns: +// - Appropriate error code for database failures or invalid input. +// On success, responds with a 200 OK status and a status message. +func IsProjectLiked(context *gin.Context) { + username := context.Param("username") + projectId := context.Param("project_id") + + httpcode, exists, err := database.QueryProjectLike(username, projectId) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to query for project like: %v", err)) + return + } + context.JSON(httpcode, gin.H{"status": exists}) } diff --git a/backend/api/main.go b/backend/api/main.go index 1971365..6af8ed8 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "log" "os" @@ -69,6 +68,7 @@ func main() { router.POST("/projects/:username/likes/:project_id", handlers.LikeProject) router.POST("/projects/:username/unlikes/:project_id", handlers.UnlikeProject) + router.GET("/projects/does-like/:username/:project_id", handlers.IsProjectLiked) router.GET("/posts/:post_id", handlers.GetPostById) router.POST("/posts", handlers.CreatePost) @@ -80,6 +80,7 @@ func main() { router.POST("/posts/:username/likes/:post_id", handlers.LikePost) router.POST("/posts/:username/unlikes/:post_id", handlers.UnlikePost) + router.GET("/posts/does-like/:username/:post_id", handlers.IsPostLiked) router.POST("/comments/for-post/:post_id", handlers.CreateCommentOnPost) router.POST("/comments/for-project/:project_id", handlers.CreateCommentOnProject) @@ -95,9 +96,7 @@ func main() { router.POST("/comments/:username/likes/:comment_id", handlers.LikeComment) router.POST("/comments/:username/unlikes/:comment_id", handlers.UnlikeComment) - - log.Println("we are in the func (log.Println)") - fmt.Println("we are in the func (fmt.Println)") + router.GET("/comments/does-like/:username/:comment_id", handlers.IsCommentLiked) var dbinfo, dbtype string if DEBUG {