From 8efca72100db0824a89d94192d71844bc0a55237 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Thu, 19 Dec 2024 20:41:20 -0500 Subject: [PATCH 1/8] go mod updates, starting comments --- backend/api/internal/database/comment_queries.go | 5 +++++ backend/api/internal/handlers/comment_routes.go | 3 +++ backend/go.mod | 3 ++- backend/go.sum | 13 ++++++++++--- 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 backend/api/internal/database/comment_queries.go create mode 100644 backend/api/internal/handlers/comment_routes.go diff --git a/backend/api/internal/database/comment_queries.go b/backend/api/internal/database/comment_queries.go new file mode 100644 index 0000000..7827656 --- /dev/null +++ b/backend/api/internal/database/comment_queries.go @@ -0,0 +1,5 @@ +package database + +import ( + +) diff --git a/backend/api/internal/handlers/comment_routes.go b/backend/api/internal/handlers/comment_routes.go new file mode 100644 index 0000000..73e147b --- /dev/null +++ b/backend/api/internal/handlers/comment_routes.go @@ -0,0 +1,3 @@ +package handlers + +import () diff --git a/backend/go.mod b/backend/go.mod index efd9e7e..bd0b1c2 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -3,6 +3,7 @@ module backend go 1.23.2 require ( + github.com/gin-contrib/cors v1.7.2 github.com/gin-gonic/gin v1.10.0 github.com/mattn/go-sqlite3 v1.14.24 github.com/sirupsen/logrus v1.9.3 @@ -17,7 +18,6 @@ require ( github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/cors v1.7.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -25,6 +25,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/backend/go.sum b/backend/go.sum index 8366709..19fd877 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -6,6 +6,7 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -36,6 +37,10 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -51,6 +56,8 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -74,8 +81,8 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -87,9 +94,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 04a127f4d6a7cbd606a6894da1410df8b4ae4673 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Sun, 22 Dec 2024 22:52:30 -0500 Subject: [PATCH 2/8] added query projects by userid --- .../api/internal/database/project_queries.go | 49 +++++++++++++++++++ .../api/internal/handlers/project_routes.go | 27 ++++++++++ backend/api/main.go | 1 + 3 files changed, 77 insertions(+) diff --git a/backend/api/internal/database/project_queries.go b/backend/api/internal/database/project_queries.go index c05636f..a1906b9 100644 --- a/backend/api/internal/database/project_queries.go +++ b/backend/api/internal/database/project_queries.go @@ -53,6 +53,55 @@ func QueryProject(id int) (*types.Project, error) { return &project, nil } +// QueryProjectsByUserId retrieves a user's projects by a user ID from the database. +// +// Parameters: +// - id: The unique identifier of the user to query projects on. +// +// Returns: +// - *[]types.Project: A list of the projects' details if found. +// - error: An error if the query fails. Returns nil for both if no project exists. +func QueryProjectsByUserId(userId int) ([]types.Project, int, error) { + query := `SELECT id, name, description, status, likes, links, tags, owner, creation_date FROM Projects WHERE owner = ?;` + rows, err := DB.Query(query, userId) + if err != nil { + return nil, http.StatusNotFound, err + } + var projects []types.Project + defer rows.Close() + + for rows.Next() { + var project types.Project + var linksJSON, tagsJSON string + err := rows.Scan( + &project.ID, + &project.Name, + &project.Description, + &project.Status, + &project.Likes, + &linksJSON, + &tagsJSON, + &project.Owner, + &project.CreationDate, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, http.StatusOK, nil + } + return nil, http.StatusInternalServerError, err + } + + if err := UnmarshalFromJSON(linksJSON, &project.Links); err != nil { + return nil, http.StatusBadRequest, err + } + if err := UnmarshalFromJSON(tagsJSON, &project.Tags); err != nil { + return nil, http.StatusBadRequest, err + } + } + + return projects, http.StatusOK, nil +} + // QueryCreateProject creates a new project in the database. // // Parameters: diff --git a/backend/api/internal/handlers/project_routes.go b/backend/api/internal/handlers/project_routes.go index f3caeaa..d34eb78 100644 --- a/backend/api/internal/handlers/project_routes.go +++ b/backend/api/internal/handlers/project_routes.go @@ -38,6 +38,33 @@ func GetProjectById(context *gin.Context) { context.JSON(http.StatusOK, project) } +// GetProjectsByUserId handles GET requests to retrieve projects information by its owning user's id. +// It expects the `user_id` parameter in the URL and does not require a request body. +// Returns: +// - 400 Bad Request if the ID is invalid. +// - 404 Not Found if the user id does not exist. +// - 500 Internal Server Error if the database query fails. +// On success, responds with a 200 OK status and the projects' details in JSON format. +func GetProjectsByUserId(context *gin.Context) { + strId := context.Param("user_id") + id, err := strconv.Atoi(strId) + if err != nil { + return + } + project, httpcode, err := database.QueryProjectsByUserId(id) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to fetch projects: %v", err)) + return + } + + if project == nil { + RespondWithError(context, httpcode, fmt.Sprintf("User with id '%v' not found", strId)) + return + } + + context.JSON(http.StatusOK, project) +} + // CreateProject handles POST requests to create a new project. // It expects a JSON payload that can be bound to a `types.Project` object. // Validates the provided owner's ID and ensures the user exists. diff --git a/backend/api/main.go b/backend/api/main.go index 68bd1ec..e742c26 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -52,6 +52,7 @@ func main() { router.POST("/projects", handlers.CreateProject) router.PUT("/projects/:project_id", handlers.UpdateProjectInfo) router.DELETE("/projects/:project_id", handlers.DeleteProject) + router.GET("/projects/by-user/:user_id", handlers.GetProjectsByUserId) router.GET("/projects/:project_id/followers", handlers.GetProjectFollowers) router.GET("/projects/follows/:username", handlers.GetProjectFollowing) From 52c11c5be7e68e32e397664dd970ff4efd22c854 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Mon, 23 Dec 2024 01:25:05 -0500 Subject: [PATCH 3/8] added routes for comments, updated sql for comment structure --- backend/api/internal/database/create_tables.sql | 2 -- backend/api/main.go | 13 ++++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/backend/api/internal/database/create_tables.sql b/backend/api/internal/database/create_tables.sql index 85ef8ea..3a39361 100644 --- a/backend/api/internal/database/create_tables.sql +++ b/backend/api/internal/database/create_tables.sql @@ -81,11 +81,9 @@ CREATE TABLE PostComments ( CREATE TABLE Comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, - post_id INTEGER NOT NULL, parent_comment_id INTEGER, creation_date TIMESTAMP NOT NULL, user_id INTEGER NOT NULL, - FOREIGN KEY (post_id) REFERENCES Posts(id) ON DELETE CASCADE, FOREIGN KEY (parent_comment_id) REFERENCES Comments(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE ); diff --git a/backend/api/main.go b/backend/api/main.go index e742c26..64200e7 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -52,7 +52,7 @@ func main() { router.POST("/projects", handlers.CreateProject) router.PUT("/projects/:project_id", handlers.UpdateProjectInfo) router.DELETE("/projects/:project_id", handlers.DeleteProject) - router.GET("/projects/by-user/:user_id", handlers.GetProjectsByUserId) + router.GET("/projects/by-user/:user_id", handlers.GetProjectsByUserId) router.GET("/projects/:project_id/followers", handlers.GetProjectFollowers) router.GET("/projects/follows/:username", handlers.GetProjectFollowing) @@ -70,6 +70,17 @@ func main() { router.GET("/posts/by-user/:user_id", handlers.GetPostsByUserId) router.GET("/posts/by-project/:project_id", handlers.GetPostsByProjectId) + router.POST("/comments/for-post/:post_id", handlers.CreateCommentForPost) + router.POST("/comments/for-project/:project_id", handlers.CreateCommentForProject) + router.POST("/comments/for-comment/:comment_id", handlers.CreateCommentForComment) + router.GET("/comments/:comment_id", handlers.GetCommentById) + router.PUT("/comments/:comment_id", handlers.UpdateCommentInfo) + router.DELETE("/comments/:comment_id", handlers.DeleteComment) + + router.GET("/comments/by-user/:user_id", handlers.GetCommentsByUserId) + router.GET("/comments/by-project/:project_id", handlers.GetCommentsByProjectId) + router.GET("/comments/by-comment/:comment_id", handlers.GetCommentsByCommentId) + var dbinfo, dbtype string if DEBUG { dbinfo = "./api/internal/database/dev.sqlite3" From a3b6f7cd6161a18291412ddd0c46d9faafa5617c Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Mon, 23 Dec 2024 01:39:37 -0500 Subject: [PATCH 4/8] tired, need reset --- backend/api/internal/database/comment_queries.go | 4 +--- backend/api/main.go | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/api/internal/database/comment_queries.go b/backend/api/internal/database/comment_queries.go index 7827656..111cd44 100644 --- a/backend/api/internal/database/comment_queries.go +++ b/backend/api/internal/database/comment_queries.go @@ -1,5 +1,3 @@ package database -import ( - -) +import () diff --git a/backend/api/main.go b/backend/api/main.go index 64200e7..95fdd95 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -78,6 +78,7 @@ func main() { router.DELETE("/comments/:comment_id", handlers.DeleteComment) 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) From 396bfa61a6070caa9eb166c9a2ae2a1163005ac3 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Mon, 23 Dec 2024 09:57:59 -0500 Subject: [PATCH 5/8] misc updates, changes to db --- .../api/internal/database/create_tables.sql | 5 ++ .../internal/database/create_test_data.sql | 80 +++++++++--------- backend/api/internal/database/dev.sqlite3 | Bin 102400 -> 102400 bytes backend/api/internal/database/post_queries.go | 4 +- 4 files changed, 46 insertions(+), 43 deletions(-) diff --git a/backend/api/internal/database/create_tables.sql b/backend/api/internal/database/create_tables.sql index 3a39361..28e5c08 100644 --- a/backend/api/internal/database/create_tables.sql +++ b/backend/api/internal/database/create_tables.sql @@ -51,6 +51,8 @@ CREATE TABLE Projects ( CREATE TABLE ProjectComments ( project_id INTEGER NOT NULL, comment_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE, FOREIGN KEY (project_id) REFERENCES Projects(id) ON DELETE CASCADE, FOREIGN KEY (comment_id) REFERENCES Comments(id) ON DELETE CASCADE, PRIMARY KEY (project_id, comment_id) @@ -72,6 +74,8 @@ CREATE TABLE Posts ( CREATE TABLE PostComments ( post_id INTEGER NOT NULL, comment_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE, FOREIGN KEY (post_id) REFERENCES Posts(id) ON DELETE CASCADE, FOREIGN KEY (comment_id) REFERENCES Comments(id) ON DELETE CASCADE, PRIMARY KEY (post_id, comment_id) @@ -82,6 +86,7 @@ CREATE TABLE Comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, parent_comment_id INTEGER, + likes INTEGER NOT NULL, 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/create_test_data.sql b/backend/api/internal/database/create_test_data.sql index 514f775..a4d96fa 100644 --- a/backend/api/internal/database/create_test_data.sql +++ b/backend/api/internal/database/create_test_data.sql @@ -1,54 +1,52 @@ --- Insert Users with fixed creation dates +-- Users INSERT INTO Users (username, picture, bio, links, creation_date) VALUES ('dev_user1', 'https://example.com/dev_user1.jpg', 'Full-stack developer passionate about open-source projects.', '["https://github.com/dev_user1", "https://devuser1.com"]', '2023-12-13 00:00:00'), ('tech_writer2', 'https://example.com/tech_writer2.jpg', 'Technical writer and Python enthusiast.', '["https://blog.techwriter.com"]', '2022-12-13 00:00:00'), - ('data_scientist3', 'https://example.com/data_scientist3.jpg', 'Data scientist with a passion for machine learning.', '["https://github.com/data_scientist3", "https://datascientist3.com"]', '2023-06-13 00:00:00'); + ('data_scientist3', 'https://example.com/data_scientist3.jpg', 'Data scientist with a passion for machine learning.', '["https://github.com/data_scientist3", "https://datascientist3.com"]', '2023-06-13 00:00:00'), + ('backend_guru4', 'https://example.com/backend_guru4.jpg', 'Backend expert specializing in scalable systems.', '["https://github.com/backend_guru4"]', '2024-01-15 00:00:00'), + ('ui_designer5', 'https://example.com/ui_designer5.jpg', 'UI/UX designer with a love for user-friendly apps.', '["https://portfolio.uidesigner5.com"]', '2023-05-10 00:00:00'); --- Insert Projects with fixed creation dates +-- Projects INSERT INTO Projects (name, description, status, likes, tags, links, owner, creation_date) VALUES ('OpenAPI Toolkit', 'A toolkit for generating and testing OpenAPI specs.', 1, 120, '["OpenAPI", "Go", "Tooling"]', '["https://github.com/dev_user1/openapi-toolkit"]', (SELECT id FROM Users WHERE username = 'dev_user1'), '2023-06-13 00:00:00'), ('DocuHelper', 'A library for streamlining technical documentation processes.', 2, 85, '["Documentation", "Python"]', '["https://github.com/tech_writer2/docuhelper"]', (SELECT id FROM Users WHERE username = 'tech_writer2'), '2021-12-13 00:00:00'), - ('ML Research', 'Research repository for various machine learning algorithms.', 1, 45, '["Machine Learning", "Python", "Research"]', '["https://github.com/data_scientist3/ml-research"]', (SELECT id FROM Users WHERE username = 'data_scientist3'), '2024-09-13 00:00:00'); + ('ML Research', 'Research repository for various machine learning algorithms.', 1, 45, '["Machine Learning", "Python", "Research"]', '["https://github.com/data_scientist3/ml-research"]', (SELECT id FROM Users WHERE username = 'data_scientist3'), '2024-09-13 00:00:00'), + ('ScaleDB', 'A scalable database system for modern apps.', 1, 70, '["Database", "Scalability", "Backend"]', '["https://github.com/backend_guru4/scaledb"]', (SELECT id FROM Users WHERE username = 'backend_guru4'), '2024-03-15 00:00:00'); --- Insert Posts with fixed creation dates +-- Posts INSERT INTO Posts (content, project_id, creation_date, user_id, likes) VALUES ('Excited to release the first version of OpenAPI Toolkit!', (SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), '2024-09-13 00:00:00', (SELECT id FROM Users WHERE username = 'dev_user1'), 40), ('We''ve archived DocuHelper, but feel free to explore the code.', (SELECT id FROM Projects WHERE name = 'DocuHelper'), '2024-06-13 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2'), 25), ('Updated ML Research repo with new algorithms for data analysis.', (SELECT id FROM Projects WHERE name = 'ML Research'), '2024-11-13 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3'), 15); --- Insert Comments with fixed creation dates -INSERT INTO Comments (content, post_id, parent_comment_id, creation_date, user_id) VALUES - ('This is amazing! Can''t wait to try it out.', (SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), NULL, '2024-10-13 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2')), - ('Thanks for the kind words!', (SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'This is amazing! Can''t wait to try it out.'), '2024-11-13 00:00:00', (SELECT id FROM Users WHERE username = 'dev_user1')), - ('Looks great! I''ll test it and report back.', (SELECT id FROM Posts WHERE content = 'Updated ML Research repo with new algorithms for data analysis.'), NULL, '2024-11-13 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3')); - --- Insert Project Likes with fixed dates -INSERT INTO ProjectLikes (project_id, user_id) VALUES - ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Users WHERE username = 'dev_user1')), - ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Users WHERE username = 'tech_writer2')), - ((SELECT id FROM Projects WHERE name = 'DocuHelper'), (SELECT id FROM Users WHERE username = 'dev_user1')), - ((SELECT id FROM Projects WHERE name = 'ML Research'), (SELECT id FROM Users WHERE username = 'data_scientist3')); - --- Insert Post Likes with fixed dates -INSERT INTO PostLikes (post_id, user_id) VALUES - ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Users WHERE username = 'dev_user1')), - ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Users WHERE username = 'tech_writer2')), - ((SELECT id FROM Posts WHERE content = 'We''ve archived DocuHelper, but feel free to explore the code.'), (SELECT id FROM Users WHERE username = 'tech_writer2')), - ((SELECT id FROM Posts WHERE content = 'Updated ML Research repo with new algorithms for data analysis.'), (SELECT id FROM Users WHERE username = 'data_scientist3')); - --- Insert Comment Likes with fixed dates -INSERT INTO CommentLikes (comment_id, user_id) VALUES - ((SELECT id FROM Comments WHERE content = 'This is amazing! Can''t wait to try it out.'), (SELECT id FROM Users WHERE username = 'dev_user1')), - ((SELECT id FROM Comments WHERE content = 'Thanks for the kind words!'), (SELECT id FROM Users WHERE username = 'tech_writer2')); - --- Insert User Follows with fixed dates -INSERT INTO UserFollows (follower_id, follows_id) VALUES - ((SELECT id FROM Users WHERE username = 'dev_user1'), (SELECT id FROM Users WHERE username = 'tech_writer2')), - ((SELECT id FROM Users WHERE username = 'tech_writer2'), (SELECT id FROM Users WHERE username = 'data_scientist3')), - ((SELECT id FROM Users WHERE username = 'dev_user1'), (SELECT id FROM Users WHERE username = 'data_scientist3')), - ((SELECT id FROM Users WHERE username = 'data_scientist3'), (SELECT id FROM Users WHERE username = 'dev_user1')); - --- Insert Project Follows with fixed dates -INSERT INTO ProjectFollows (project_id, user_id) VALUES - ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Users WHERE username = 'tech_writer2')), - ((SELECT id FROM Projects WHERE name = 'ML Research'), (SELECT id FROM Users WHERE username = 'dev_user1')); +-- Comments on Projects +INSERT INTO Comments (content, parent_comment_id, likes, creation_date, user_id, likes) VALUES + ('This is a fantastic project! Can''t wait to contribute.', NULL, 5, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'dev_user1'), 0), + ('I love the concept, but I think the documentation could be improved.', NULL, 3, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2'), 0), + ('Great to see more open-source tools for API development!', NULL, 4, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'backend_guru4'), 0), + ('I agree, but the API specs seem a bit too complex for beginners.', (SELECT id FROM Comments WHERE content = 'Great to see more open-source tools for API development!'), 2, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3'), 0), + ('I hope this toolkit will integrate with other Go tools soon!', (SELECT id FROM Comments WHERE content = 'This is a fantastic project! Can''t wait to contribute.'), 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'ui_designer5'), 0); + +-- ProjectComments relations (Mapping comments to projects) +INSERT INTO ProjectComments (project_id, comment_id, user_id) VALUES + ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'This is a fantastic project! Can''t wait to contribute.'), (SELECT id FROM Users WHERE username = 'dev_user1')), + ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'I love the concept, but I think the documentation could be improved.'), (SELECT id FROM Users WHERE username = 'tech_writer2')), + ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'Great to see more open-source tools for API development!'), (SELECT id FROM Users WHERE username = 'backend_guru4')), + ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'I agree, but the API specs seem a bit too complex for beginners.'), (SELECT id FROM Users WHERE username = 'data_scientist3')), + ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'I hope this toolkit will integrate with other Go tools soon!'), (SELECT id FROM Users WHERE username = 'ui_designer5')); + +-- Comments on Posts +INSERT INTO Comments (content, parent_comment_id, likes, creation_date, user_id) VALUES + ('Awesome update! I''ll try it out.', NULL, 2, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'backend_guru4')), + ('Thanks for sharing! Will this feature be extended soon?', NULL, 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3')), + ('Great work, looking forward to more updates!', NULL, 4, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'ui_designer5')), + ('Will this be compatible with earlier versions of OpenAPI?', (SELECT id FROM Comments WHERE content = 'Thanks for sharing! Will this feature be extended soon?'), 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2')), + ('I hope the next update addresses performance improvements.', (SELECT id FROM Comments WHERE content = 'Awesome update! I''ll try it out.'), 3, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3')); + +-- PostComments relations (Mapping comments to posts) +INSERT INTO PostComments (post_id, comment_id, user_id) VALUES + ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'Awesome update! I''ll try it out.'), (SELECT id FROM Users WHERE username = 'backend_guru4')), + ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'Thanks for sharing! Will this feature be extended soon?'), (SELECT id FROM Users WHERE username = 'data_scientist3')), + ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'Great work, looking forward to more updates!'), (SELECT id FROM Users WHERE username = 'ui_designer5')), + ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'Will this be compatible with earlier versions of OpenAPI?'), (SELECT id FROM Users WHERE username = 'tech_writer2')), + ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'I hope the next update addresses performance improvements.'), (SELECT id FROM Users WHERE username = 'data_scientist3')); diff --git a/backend/api/internal/database/dev.sqlite3 b/backend/api/internal/database/dev.sqlite3 index fded2948f4bca4a9aaeed5d30547b7c36fefcdf2..4169563a9b8e60ee07b3c53811b97eb463811d25 100644 GIT binary patch delta 2033 zcmZuxU1$_n6rQ{Dx4YSyyRo&2E!w*)X#}&IP1dv~idGw|5vl}zr3#jc&~s;#=t7nmE_3&u zbH4L^-?>L;q@y#^$yb*tn>dbJ0q^`TICH9_o9!l#>=$7V;iv4M)>-=m4z#GJqhyYt zXfteykr@teQ|FZXN`GW#wsIo!N+l-BT-(sc!|9Sfnxn3sH>uN8Men^_esLpt(9RDH zbPnw!3unlr?u`?T7}f+OW0pfoE_D)Pj!w;-F-Y4A<8B z5kjYI>UhMpX;#+^{S)2H6Wt_kRx`AWL5Vx!tKXKF#~ZTvAl4;PU5T!qMG%+n z?|R&f!qs0#TUQAo#J8X!=+G6zj7WTz!$0C{_z}K~&t7VdevMa0`8M<-WnwemjvC4c z)oa$95kcwV@E-LW<%ZHVTZt)eS5^yQ?zL0idP*Tem!iozpxmqqz(CeXfgE zjyh(YNMTq@KgL4|Al|Y zcOd#Ij!6r_ELQNpc-~(ik_FDq2vMkelf(bwzwrb78@>sVKjFT5-7p+xDMEpI-Jn>n zEAtZ6MMwfNnLxK0?nn3`zK3r^{1v>(FA@gWEV^DfSg$MxSW6*Efyi*=B8Si8o$6!t zOLc?tO!;0JRYH-Avz0F*<>7FUs~`@g(Eu5@Y)ZUwN=!QCk&+EMQKIE?4s~7X67V8S zMNu=el;}mW~RAqHdWkvBt=5o0?nRAK1J^+Q5#W%uxn+yF)dPOj=H%118ogu-O23lbVww zo<)k5L+4v?V|6z)0u=YkGIz*FI5@7E1=qKsJFYp9DMp^79)mh1NCxPE7MNU`Bd%qc zOTpteN;0>L!P_!PU8}eNAw~w;84J!d(LG|7ykwx>T)BZXVV?+Uh>W#GHGntIvWx=6 zO)@5R)1!HZDdQ7pfnE;TZuy5?A6RS&-oz0BGAnhRwY)=V2g#H?Qf6`k*Zv&?;glBP z2FyS)DC(DLphgl{ZD;YT^+q_wG zOBnUTR4T9s$=o!Q2n{lm!lRKf4U)K?o+V)OgEZ^K$UBT=g+91NA*jupqAR$2nJruN0g+@JvvO-#rR-ip}XJ(bKhCAHhe&;*iIcM(3q;q7_ zIiA1A`-Tvb$KUMZYPfs2+B~foy5QhvfLmrIkm|jI0>df4QZ3^Q@ra8ApZMl6D$n`G zz3uK-UZ2z~E=pdf$nHDRw3UV6BSq2#k8mcHoDtolRX#CLTnuBy#{Lit8qY%@8{RNX z3dhj!55PWSc^LN2Er($Q+@mcXF;G?pV@-y31#a8m)d;j29bsrQ9t?u#G4ueFxi%_i z2sZnk%9&L1gFKsQ-^c+v@#$i2K9zt5;9MdYPf)js#5BP)uH)y~9RCZPNNY#kR9+x> z#`jQONF`s(vkQae!YuRuF1**krrD%_``b;ip5QaQiS?;uIS#i_k*Gjw;IyVi6G|u^ zRP`dIt)!<%(Sr$HiRg+N>sI2yUM;RGm(;Fk1+A{Et}U;sDzBI|zfPIOs|D|$GnL?gVrPm6aaivH8qd>;6C-UglFNJ8ODy{ZmGVxc0XNsX20N}tLF z=$fL(Zf!}b2$Fhi}4n_eeWt4&>FoCBiP@s}zM#ym4 zL~r04E_3vIEE5@6XeR>`=wuRrATTCLl1fq=V%yn?7oOMU9fa_*;omVqhkmcB=Zj;}LwQY5bo0m-Q{O;If zlyUPQ*zUQyP5gr2v+C+B*OXbVDYf~j;NT%hg6!%d9_K3Y9r12?UwWoJz3$^~>Y8#z zovW^csbtz|{JIWhkUMvv4^9E(&BglRtwZYYi-CdyxTP5 Date: Mon, 23 Dec 2024 09:58:23 -0500 Subject: [PATCH 6/8] fixed types --- backend/api/internal/types/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/api/internal/types/types.go b/backend/api/internal/types/types.go index 8cf2e0c..2b3537c 100644 --- a/backend/api/internal/types/types.go +++ b/backend/api/internal/types/types.go @@ -43,7 +43,7 @@ type Post struct { type Comment struct { ID int64 `json:"id"` User int64 `json:"user" binding:"required"` - Post int64 `json:"post" binding:"required"` + Likes int64 `json:"likes"` ParentComment int64 `json:"parent_comment" binding:"required"` CreationDate time.Time `json:"created_on"` Content string `json:"content" binding:"required"` From dc37a89c3e639453b88e97d588c5a25cb38ebba4 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Mon, 23 Dec 2024 11:19:58 -0500 Subject: [PATCH 7/8] comments prelim commit --- .../api/internal/database/comment_queries.go | 464 +++++++++++++++++- .../api/internal/handlers/comment_routes.go | 405 ++++++++++++++- backend/api/main.go | 8 +- 3 files changed, 871 insertions(+), 6 deletions(-) diff --git a/backend/api/internal/database/comment_queries.go b/backend/api/internal/database/comment_queries.go index 111cd44..ac115fa 100644 --- a/backend/api/internal/database/comment_queries.go +++ b/backend/api/internal/database/comment_queries.go @@ -1,3 +1,465 @@ package database -import () +import ( + "database/sql" + "fmt" + "net/http" + "time" + + "backend/api/internal/types" +) + +// QueryComment retrieves a comment by its ID from the database. +// +// Parameters: +// - id: The unique identifier of the comment to query. +// +// Returns: +// - *types.Comment: The comment details if found. +// - error: An error if the query fails. Returns nil for both if no comment exists. +func QueryComment(id int) (*types.Comment, error) { + query := `SELECT id, user_id, content, likes, creation_date, parent_comment_id FROM Posts WHERE id = ?;` + row := DB.QueryRow(query, id) + var comment types.Comment + + err := row.Scan( + &comment.ID, + &comment.User, + &comment.Content, + &comment.Likes, + &comment.CreationDate, + &comment.ParentComment, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + + return &comment, nil +} + +// QueryCommentsByUserId retrieves a set of comments by its owning user id from the database. +// +// Parameters: +// - id: The unique identifier of the user to query. +// +// Returns: +// - []types.Post: The post details if found. +// - error: An error if the query fails. Returns nil for both if no comments exists. +func QueryCommentsByUserId(userId int) ([]types.Comment, int, error) { + query := ` + SELECT + c.id AS comment_id, + c.user_id, + c.content, + c.likes, + c.creation_date, + c.parent_comment_id + FROM Comments c + JOIN PostComments pc ON c.id = pc.comment_id + WHERE c.user_id = ?; + ` + + postRows, err := DB.Query(query, userId) + if err != nil { + return nil, http.StatusNotFound, err + } + defer postRows.Close() + + query = ` + SELECT + c.id AS comment_id, + c.user_id, + c.content, + (SELECT COUNT(*) FROM CommentLikes cl WHERE cl.comment_id = c.id) AS likes, + c.creation_date, + c.parent_comment_id + FROM Comments c + JOIN ProjectComments pc ON c.id = pc.comment_id + WHERE pc.project_id = ?; + ` + projRows, err := DB.Query(query, userId) + if err != nil { + return nil, http.StatusNotFound, err + } + defer projRows.Close() + var comments []types.Comment + + for projRows.Next() { + var comment types.Comment + err := projRows.Scan( + &comment.ID, + &comment.User, + &comment.Content, + &comment.Likes, + &comment.CreationDate, + &comment.ParentComment, + ) + + if err != nil { + if err == sql.ErrNoRows { + return nil, http.StatusOK, nil + } + return nil, http.StatusInternalServerError, err + } + comments = append(comments, comment) + } + for postRows.Next() { + var comment types.Comment + err := postRows.Scan( + &comment.ID, + &comment.User, + &comment.Content, + &comment.Likes, + &comment.CreationDate, + &comment.ParentComment, + ) + + if err != nil { + if err == sql.ErrNoRows { + return nil, http.StatusOK, nil + } + return nil, http.StatusInternalServerError, err + } + comments = append(comments, comment) + } + + return comments, http.StatusOK, nil +} + +// QueryCommentsByProjectId retrieves a comment by its project ID from the database. +// +// Parameters: +// - id: The unique identifier of the project to query. +// +// Returns: +// - *types.Comment: The comment details if found. +// - error: An error if the query fails. Returns nil for both if no comment exists. +func QueryCommentsByProjectId(id int) ([]types.Comment, int, error) { + query := ` + SELECT + c.id AS comment_id, + c.user_id, + c.content, + c.likes, + c.creation_date, + c.parent_comment_id + FROM Comments c + JOIN ProjectComments pc ON c.id = pc.comment_id + WHERE pc.project_id = ?; + ` + rows, err := DB.Query(query, id) + if err != nil { + return nil, http.StatusNotFound, err + } + defer rows.Close() + var comments []types.Comment + + for rows.Next() { + var comment types.Comment + err := rows.Scan( + &comment.ID, + &comment.User, + &comment.Content, + &comment.Likes, + &comment.CreationDate, + &comment.ParentComment, + ) + + if err != nil { + if err == sql.ErrNoRows { + return nil, http.StatusOK, nil + } + return nil, http.StatusInternalServerError, err + } + comments = append(comments, comment) + } + return comments, http.StatusOK, nil +} + +// QueryCommentsByPostId retrieves a comment by its post ID from the database. +// +// Parameters: +// - id: The unique identifier of the post to query. +// +// Returns: +// - *types.Comment: The comment details if found. +// - error: An error if the query fails. Returns nil for both if no comment exists. +func QueryCommentsByPostId(id int) ([]types.Comment, int, error) { + query := ` + SELECT + c.id AS comment_id, + c.user_id, + c.content, + c.likes, + c.creation_date, + c.parent_comment_id + FROM Comments c + JOIN PostComments pc ON c.id = pc.comment_id + WHERE pc.post_id = ?; + ` + rows, err := DB.Query(query, id) + if err != nil { + return nil, http.StatusNotFound, err + } + defer rows.Close() + var comments []types.Comment + + for rows.Next() { + var comment types.Comment + err := rows.Scan( + &comment.ID, + &comment.User, + &comment.Content, + &comment.Likes, + &comment.CreationDate, + &comment.ParentComment, + ) + + if err != nil { + if err == sql.ErrNoRows { + return nil, http.StatusOK, nil + } + return nil, http.StatusInternalServerError, err + } + comments = append(comments, comment) + } + return comments, http.StatusOK, nil +} + +// QueryCommentsByCommentId retrieves a comment by its comment ID from the database. +// +// Parameters: +// - id: The unique identifier of the comment to query. +// +// Returns: +// - *types.Comment: The comment details if found. +// - error: An error if the query fails. Returns nil for both if no comment exists. +func QueryCommentsByCommentId(id int) ([]types.Comment, int, error) { + query := ` + SELECT + c.id AS comment_id, + c.user_id, + c.content, + c.likes, + c.creation_date, + c.parent_comment_id + FROM Comments c + WHERE c.parent_comment_id = ?; + ` + rows, err := DB.Query(query, id) + if err != nil { + return nil, http.StatusNotFound, err + } + defer rows.Close() + var comments []types.Comment + + for rows.Next() { + var comment types.Comment + err := rows.Scan( + &comment.ID, + &comment.User, + &comment.Content, + &comment.Likes, + &comment.CreationDate, + &comment.ParentComment, + ) + + if err != nil { + if err == sql.ErrNoRows { + return nil, http.StatusOK, nil + } + return nil, http.StatusInternalServerError, err + } + comments = append(comments, comment) + } + return comments, http.StatusOK, nil +} + +// QueryCreateCommentOnPost creates a new comment on a post in the database. +// +// Parameters: +// - commment: The comment to be created, containing all necessary fields. +// - postId: The id of the post for the comment to be added to +// +// Returns: +// - int64: The ID of the newly created comment. +// - error: An error if the operation fails. +func QueryCreateCommentOnPost(comment types.Comment, postId int) (int64, error) { + currentTime := time.Now().Format("2006-01-02 15:04:05") + + query := `INSERT INTO Comments (user_id, content, parent_comment_id, likes, creation_date) + VALUES (?, ?, ?, ?, ?);` + + res, err := DB.Exec(query, comment.User, comment.Content, comment.ParentComment, 0, currentTime) + + if err != nil { + return -1, fmt.Errorf("Failed to create comment: %v", err) + } + + lastId, err := res.LastInsertId() + if err != nil { + return -1, fmt.Errorf("Failed to ensure post was created: %v", err) + } + + query = `INSERT INTO PostComments (user_id, post_id, comment_id) + VALUES (?, ?, ?)` + + res, err = DB.Exec(query, comment.User, postId, lastId) + if err != nil { + return -1, fmt.Errorf("Failed to link comment to post: %v", err) + } + + return lastId, nil +} + +// QueryCreateCommentOnProject creates a new comment on a project in the database. +// +// Parameters: +// - commment: The comment to be created, containing all necessary fields. +// - projectId: The id of the project for the comment to be added to +// +// Returns: +// - int64: The ID of the newly created comment. +// - error: An error if the operation fails. +func QueryCreateCommentOnProject(comment types.Comment, projectId int) (int64, error) { + currentTime := time.Now().Format("2006-01-02 15:04:05") + + query := `INSERT INTO Comments (user_id, content, parent_comment_id, likes, creation_date) + VALUES (?, ?, ?, ?, ?);` + + res, err := DB.Exec(query, comment.User, comment.Content, comment.ParentComment, 0, currentTime) + + if err != nil { + return -1, fmt.Errorf("Failed to create comment: %v", err) + } + + lastId, err := res.LastInsertId() + if err != nil { + return -1, fmt.Errorf("Failed to ensure project was created: %v", err) + } + + query = `INSERT INTO ProjectComments (user_id, project_id, comment_id) + VALUES (?, ?, ?)` + + res, err = DB.Exec(query, comment.User, projectId, lastId) + if err != nil { + return -1, fmt.Errorf("Failed to link comment to project: %v", err) + } + + return lastId, nil +} + +// QueryCreateCommentOnComment creates a new comment on a comment in the database. +// +// Parameters: +// - commment: The comment to be created, containing all necessary fields. +// - commentId: The id of the comment for the comment to be added to +// +// Returns: +// - int64: The ID of the newly created comment. +// - error: An error if the operation fails. +func QueryCreateCommentOnComment(comment types.Comment, commentId int) (int64, error) { + currentTime := time.Now().Format("2006-01-02 15:04:05") + + query := `INSERT INTO Comments (user_id, content, parent_comment_id, likes, creation_date) + VALUES (?, ?, ?, ?, ?);` + + res, err := DB.Exec(query, comment.User, comment.Content, commentId, 0, currentTime) + + if err != nil { + return -1, fmt.Errorf("Failed to create comment: %v", err) + } + + lastId, err := res.LastInsertId() + if err != nil { + return -1, fmt.Errorf("Failed to ensure comment was created: %v", err) + } + + return lastId, nil +} + +// QueryDeleteComment soft deletes a comment +// +// Parameters: +// - id: The id of the comment to be deleted +// +// Returns: +// - int16: http status code +// - error: An error if the operation fails. +func QueryDeleteComment(id int) (int16, error) { + _, err := DB.Exec(`UPDATE PostComments SET user_id = -1 WHERE comment_id = ?`, id) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to update PostComments for deleted comment: %v", err) + } + + _, err = DB.Exec(`UPDATE ProjectComments SET user_id = -1 WHERE comment_id = ?`, id) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to update ProjectComments for deleted comment: %v", err) + } + + query := `UPDATE Comments SET user_id = -1, content = "This comment was deleted.", likes = 0 WHERE id = ?` + res, err := DB.Exec(query, id) + if err != nil { + return http.StatusBadRequest, fmt.Errorf("Failed to soft delete comment `%v`: %v", id, err) + } + + rowsAffected, err := res.RowsAffected() + if rowsAffected == 0 { + return http.StatusNotFound, fmt.Errorf("Comment not found or already marked as deleted") + } else if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Failed to fetch affected rows: %v", err) + } + + return http.StatusOK, nil +} + +// QueryUpdateCommentContent updates comment's content +// +// Parameters: +// - id: The id of the comment to be deleted +// - newContent: the updated content +// +// Returns: +// - int16: http status code +// - error: An error if the operation fails. +func QueryUpdateCommentContent(id int, newContent string) (int16, error) { + // get comment creation time to validate time diff + var creationDate string + query := `SELECT creation_date FROM Comments WHERE id = ?` + err := DB.QueryRow(query, id).Scan(&creationDate) + if err != nil { + if err == sql.ErrNoRows { + return 404, fmt.Errorf("Comment not found") + } + return 500, fmt.Errorf("Failed to fetch comment creation date: %v", err) + } + + createdAt, err := time.Parse("2006-01-02 15:04:05", creationDate) + if err != nil { + return 500, fmt.Errorf("Failed to parse creation date: %v", err) + } + + now := time.Now() + // now check if the comment is older than 2 minutes + if now.Sub(createdAt) > 2*time.Minute { + return 400, fmt.Errorf("Cannot update comment. More than 2 minutes have passed since posting.") + } + + query = `UPDATE Comments SET content = ? WHERE id = ?` + res, err := DB.Exec(query, newContent, id) + if err != nil { + return 500, fmt.Errorf("Failed to update comment content: %v", err) + } + + rowsAffected, err := res.RowsAffected() + if rowsAffected == 0 { + return 404, fmt.Errorf("Comment not found or no changes made") + } else if err != nil { + return 500, fmt.Errorf("Failed to fetch affected rows: %v", err) + } + + return 200, nil +} diff --git a/backend/api/internal/handlers/comment_routes.go b/backend/api/internal/handlers/comment_routes.go index 73e147b..9d2e211 100644 --- a/backend/api/internal/handlers/comment_routes.go +++ b/backend/api/internal/handlers/comment_routes.go @@ -1,3 +1,406 @@ package handlers -import () +import ( + "fmt" + "net/http" + "strconv" + + "backend/api/internal/database" + "backend/api/internal/types" + + "github.com/gin-gonic/gin" +) + +// GetPostById handles GET requests to retrieve project information by its ID. +// It expects the `post_id` parameter in the URL and does not require a request body. +// Returns: +// - 400 Bad Request if the ID is invalid. +// - 404 Not Found if the post does not exist. +// - 500 Internal Server Error if the database query fails. +// On success, responds with a 200 OK status and the post details in JSON format. +func GetCommentById(context *gin.Context) { + strId := context.Param("comment_id") + id, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse comment_id: %v", err)) + return + } + comment, err := database.QueryComment(id) + if err != nil { + RespondWithError(context, http.StatusInternalServerError, fmt.Sprintf("Failed to fetch comment: %v", err)) + return + } + + if comment == nil { + RespondWithError(context, http.StatusNotFound, fmt.Sprintf("Post with id '%v' not found", strId)) + return + } + + context.JSON(http.StatusOK, comment) +} + +// GetCommentsByUserId handles GET requests to retrieve comments information by its owning user. +// It expects the `user_id` parameter in the URL and does not require a request body. +// Returns: +// - 400 Bad Request if the ID is invalid. +// - 404 Not Found if the user does not exist. +// - 500 Internal Server Error if the database query fails. +// On success, responds with a 200 OK status and the comments” details in JSON format. +func GetCommentsByUserId(context *gin.Context) { + strId := context.Param("user_id") + id, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse user_id: %v", err)) + return + } + comments, httpcode, err := database.QueryCommentsByUserId(id) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to fetch comments: %v", err)) + return + } + + if comments == nil { + RespondWithError(context, http.StatusNotFound, fmt.Sprintf("Comments from user with id '%v' not found", strId)) + return + } + + context.JSON(http.StatusOK, comments) +} + +// GetCommentsByProjectId handles GET requests to retrieve comments information by its owning project. +// It expects the `project_id` parameter in the URL and does not require a request body. +// Returns: +// - 400 Bad Request if the ID is invalid. +// - 404 Not Found if the project does not exist. +// - 500 Internal Server Error if the database query fails. +// On success, responds with a 200 OK status and the comments details in JSON format. +func GetCommentsByProjectId(context *gin.Context) { + strId := context.Param("project_id") + id, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse project_id: %v", err)) + return + } + comments, httpcode, err := database.QueryCommentsByProjectId(id) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to fetch comments: %v", err)) + return + } + + if comments == nil { + RespondWithError(context, http.StatusNotFound, fmt.Sprintf("Comments from project with id '%v' not found", strId)) + return + } + + context.JSON(http.StatusOK, comments) +} + +// GetCommentsByPostId handles GET requests to retrieve comments information by its owning post. +// It expects the `post_id` parameter in the URL and does not require a request body. +// Returns: +// - 400 Bad Request if the ID is invalid. +// - 404 Not Found if the post does not exist. +// - 500 Internal Server Error if the database query fails. +// On success, responds with a 200 OK status and the comments details in JSON format. +func GetCommentsByPostId(context *gin.Context) { + strId := context.Param("post_id") + id, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse post_id: %v", err)) + return + } + comments, httpcode, err := database.QueryCommentsByPostId(id) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to fetch comments: %v", err)) + return + } + + if comments == nil { + RespondWithError(context, http.StatusNotFound, fmt.Sprintf("Comments from post with id '%v' not found", strId)) + return + } + + context.JSON(http.StatusOK, comments) +} + +// GetCommentsByCommentId handles GET requests to retrieve comments information by its owning comment. +// It expects the `comment_id` parameter in the URL and does not require a request body. +// Returns: +// - 400 Bad Request if the ID is invalid. +// - 404 Not Found if the comment does not exist. +// - 500 Internal Server Error if the database query fails. +// On success, responds with a 200 OK status and the comments details in JSON format. +func GetCommentsByCommentId(context *gin.Context) { + strId := context.Param("comment_id") + id, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse comment_id: %v", err)) + return + } + comments, httpcode, err := database.QueryCommentsByUserId(id) + if err != nil { + RespondWithError(context, httpcode, fmt.Sprintf("Failed to fetch comments: %v", err)) + return + } + + if comments == nil { + RespondWithError(context, http.StatusNotFound, fmt.Sprintf("Comments from comment with id '%v' not found", strId)) + return + } + + context.JSON(http.StatusOK, comments) +} + +// CreateCommentOnPost handles POST requests to create a new comment on a post +// It expects a JSON payload that can be bound to a `types.Comment` object. +// Validates the provided owner's ID, verifies the post, and ensures the user exists. +// Returns: +// - 400 Bad Request if the JSON payload is invalid or the user/post cannot be verified. +// - 500 Internal Server Error if there is a database error. +// On success, responds with a 201 Created status and the new comment ID in JSON format. +func CreateCommentOnPost(context *gin.Context) { + var newComment types.Comment + err := context.BindJSON(&newComment) + + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to bind to JSON: %v", err)) + return + } + + strId := context.Param("post_id") + postId, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse post id: %v", err)) + return + } + + // Verify the owner + username, err := database.GetUsernameById(newComment.User) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify comment ownership: %v", err)) + return + } + + if username == "" { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify comment ownership. User could not be found")) + return + } + + // Verify the post + post, err := database.QueryPost(postId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify post: %v", err)) + return + } + + if post == nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify post. Post could not be found")) + return + } + + // Create the comment + id, err := database.QueryCreateCommentOnPost(newComment, postId) + if err != nil { + RespondWithError(context, http.StatusInternalServerError, fmt.Sprintf("Failed to create comment on post: %v", err)) + return + } + + context.JSON(http.StatusCreated, gin.H{"message": fmt.Sprintf("Comment created successfully with id '%v'", id)}) +} + +// CreateCommentOnProject handles POST requests to create a new comment on a project +// It expects a JSON payload that can be bound to a `types.Comment` object. +// Validates the provided owner's ID, verifies the project, and ensures the user exists. +// Returns: +// - 400 Bad Request if the JSON payload is invalid or the user/project cannot be verified. +// - 500 Internal Server Error if there is a database error. +// On success, responds with a 201 Created status and the new comment ID in JSON format. +func CreateCommentOnProject(context *gin.Context) { + var newComment types.Comment + err := context.BindJSON(&newComment) + + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to bind to JSON: %v", err)) + return + } + + strId := context.Param("post_id") + projId, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse post id: %v", err)) + return + } + + // Verify the owner + username, err := database.GetUsernameById(newComment.User) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify comment ownership: %v", err)) + return + } + + if username == "" { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify comment ownership. User could not be found")) + return + } + + // Verify the project + project, err := database.QueryProject(projId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify project: %v", err)) + return + } + + if project == nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify project. Project could not be found")) + return + } + + // Create the comment + id, err := database.QueryCreateCommentOnProject(newComment, projId) + if err != nil { + RespondWithError(context, http.StatusInternalServerError, fmt.Sprintf("Failed to create comment on project: %v", err)) + return + } + + context.JSON(http.StatusCreated, gin.H{"message": fmt.Sprintf("Comment created successfully with id '%v'", id)}) +} + +// CreateCommentOnComment handles POST requests to create a new reply (comment) to another comment +// It expects a JSON payload that can be bound to a `types.Comment` object. +// Validates the provided owner's ID, verifies the parent comment, and ensures the user exists. +// Returns: +// - 400 Bad Request if the JSON payload is invalid or the user/parent comment cannot be verified. +// - 500 Internal Server Error if there is a database error. +// On success, responds with a 201 Created status and the new reply (comment) ID in JSON format. +func CreateCommentOnComment(context *gin.Context) { + var newComment types.Comment + err := context.BindJSON(&newComment) + + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to bind to JSON: %v", err)) + return + } + + strId := context.Param("post_id") + commId, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse post id: %v", err)) + return + } + + // Verify the owner + username, err := database.GetUsernameById(newComment.User) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify comment ownership: %v", err)) + return + } + + if username == "" { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify comment ownership. User could not be found")) + return + } + + // Verify the parent comment + parentComment, err := database.QueryComment(commId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify parent comment: %v", err)) + return + } + + if parentComment == nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to verify parent comment. Comment could not be found")) + return + } + + // Create the reply (comment) + id, err := database.QueryCreateCommentOnComment(newComment, commId) + if err != nil { + RespondWithError(context, http.StatusInternalServerError, fmt.Sprintf("Failed to create reply to comment: %v", err)) + return + } + + context.JSON(http.StatusCreated, gin.H{"message": fmt.Sprintf("Reply created successfully with id '%v'", id)}) +} + +// DeletePost handles DELETE requests to delete a post. +// It expects the `post_id` parameter in the URL. +// Returns: +// - 400 Bad Request if the post_id is invalid. +// - 404 Not Found if no post is found with the given id. +// - 500 Internal Server Error if a database query fails. +// On success, responds with a 200 OK status and a message confirming the post deletion. +func DeleteComment(context *gin.Context) { + strId := context.Param("comment_id") + id, err := strconv.Atoi(strId) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse comment id: %v", err)) + return + } + + code, err := database.QueryDeleteComment(id) + if err != nil { + var httpCode int + switch code { + case 400: + httpCode = http.StatusBadRequest + case 404: + httpCode = http.StatusNotFound + default: + httpCode = http.StatusInternalServerError + } + RespondWithError(context, httpCode, fmt.Sprintf("Failed to delete comment: %v", err)) + return + } + context.JSON(http.StatusOK, gin.H{ + "message": fmt.Sprintf("Comment %v soft deleted.", id), + }) +} + +func UpdateCommentContent(context *gin.Context) { + id, err := strconv.Atoi(context.Param("comment_id")) + if err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse comment id: %v", err)) + return + } + + existingComment, err := database.QueryComment(id) + if err != nil { + RespondWithError(context, http.StatusInternalServerError, fmt.Sprintf("Failed to retrieve comment: %v", err)) + return + } + if existingComment == nil { + RespondWithError(context, http.StatusNotFound, fmt.Sprintf("Comment with id '%v' not found", id)) + return + } + + var requestData struct { + Content string `json:"content"` + } + + if err := context.BindJSON(&requestData); err != nil { + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse request body: %v", err)) + return + } + + if requestData.Content == "" { + RespondWithError(context, http.StatusBadRequest, "Content cannot be empty") + return + } + + httpcode, err := database.QueryUpdateCommentContent(id, requestData.Content) + if err != nil { + RespondWithError(context, int(httpcode), fmt.Sprintf("Error updating comment: %v", err)) + return + } + + updatedComment, err := database.QueryComment(id) + if err != nil { + RespondWithError(context, http.StatusInternalServerError, fmt.Sprintf("Error validating updated comment: %v", err)) + return + } + + context.JSON(http.StatusOK, gin.H{ + "message": "Comment updated successfully", + "comment": updatedComment, + }) +} diff --git a/backend/api/main.go b/backend/api/main.go index 95fdd95..d0d7726 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -70,11 +70,11 @@ func main() { router.GET("/posts/by-user/:user_id", handlers.GetPostsByUserId) router.GET("/posts/by-project/:project_id", handlers.GetPostsByProjectId) - router.POST("/comments/for-post/:post_id", handlers.CreateCommentForPost) - router.POST("/comments/for-project/:project_id", handlers.CreateCommentForProject) - router.POST("/comments/for-comment/:comment_id", handlers.CreateCommentForComment) + 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.UpdateCommentInfo) + router.PUT("/comments/:comment_id", handlers.UpdateCommentContent) router.DELETE("/comments/:comment_id", handlers.DeleteComment) router.GET("/comments/by-user/:user_id", handlers.GetCommentsByUserId) From f4a5772415c465375446cd987fceb0f3f7cb1921 Mon Sep 17 00:00:00 2001 From: Derek Corniello Date: Mon, 23 Dec 2024 11:57:48 -0500 Subject: [PATCH 8/8] misc updates, polished comments --- .../api/internal/database/comment_queries.go | 4 +-- .../internal/database/create_test_data.sql | 28 ++++++++++-------- backend/api/internal/database/dev.sqlite3 | Bin 102400 -> 102400 bytes .../api/internal/handlers/comment_routes.go | 17 +++++++---- backend/api/internal/types/types.go | 13 ++++---- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/backend/api/internal/database/comment_queries.go b/backend/api/internal/database/comment_queries.go index ac115fa..580b1d2 100644 --- a/backend/api/internal/database/comment_queries.go +++ b/backend/api/internal/database/comment_queries.go @@ -18,7 +18,7 @@ import ( // - *types.Comment: The comment details if found. // - error: An error if the query fails. Returns nil for both if no comment exists. func QueryComment(id int) (*types.Comment, error) { - query := `SELECT id, user_id, content, likes, creation_date, parent_comment_id FROM Posts WHERE id = ?;` + query := `SELECT id, user_id, content, likes, creation_date, parent_comment_id FROM Comments WHERE id = ?;` row := DB.QueryRow(query, id) var comment types.Comment @@ -437,7 +437,7 @@ func QueryUpdateCommentContent(id int, newContent string) (int16, error) { return 500, fmt.Errorf("Failed to fetch comment creation date: %v", err) } - createdAt, err := time.Parse("2006-01-02 15:04:05", creationDate) + createdAt, err := time.Parse("2006-01-02T15:04:05Z", creationDate) if err != nil { return 500, fmt.Errorf("Failed to parse creation date: %v", err) } diff --git a/backend/api/internal/database/create_test_data.sql b/backend/api/internal/database/create_test_data.sql index a4d96fa..3d5d9d7 100644 --- a/backend/api/internal/database/create_test_data.sql +++ b/backend/api/internal/database/create_test_data.sql @@ -19,13 +19,14 @@ INSERT INTO Posts (content, project_id, creation_date, user_id, likes) VALUES ('We''ve archived DocuHelper, but feel free to explore the code.', (SELECT id FROM Projects WHERE name = 'DocuHelper'), '2024-06-13 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2'), 25), ('Updated ML Research repo with new algorithms for data analysis.', (SELECT id FROM Projects WHERE name = 'ML Research'), '2024-11-13 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3'), 15); --- Comments on Projects -INSERT INTO Comments (content, parent_comment_id, likes, creation_date, user_id, likes) VALUES - ('This is a fantastic project! Can''t wait to contribute.', NULL, 5, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'dev_user1'), 0), - ('I love the concept, but I think the documentation could be improved.', NULL, 3, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2'), 0), - ('Great to see more open-source tools for API development!', NULL, 4, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'backend_guru4'), 0), - ('I agree, but the API specs seem a bit too complex for beginners.', (SELECT id FROM Comments WHERE content = 'Great to see more open-source tools for API development!'), 2, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3'), 0), - ('I hope this toolkit will integrate with other Go tools soon!', (SELECT id FROM Comments WHERE content = 'This is a fantastic project! Can''t wait to contribute.'), 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'ui_designer5'), 0); +-- Comments on Projects (Parent-child relationships with hardcoded parent_comment_id) +INSERT INTO Comments (content, parent_comment_id, likes, creation_date, user_id) VALUES + ('This is a fantastic project! Can''t wait to contribute.', NULL, 5, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'dev_user1')), + ('I love the concept, but I think the documentation could be improved.', NULL, 3, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2')), + ('Great to see more open-source tools for API development!', NULL, 4, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'backend_guru4')), + ('I agree, but the API specs seem a bit too complex for beginners.', 3, 2, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3')), -- Hardcoded parent_comment_id = 3 + ('I hope this toolkit will integrate with other Go tools soon!', 1, 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'ui_designer5')), -- Hardcoded parent_comment_id = 1 + ('I agree, the documentation is lacking in detail.', 2, 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3')); -- Hardcoded parent_comment_id = 2 -- ProjectComments relations (Mapping comments to projects) INSERT INTO ProjectComments (project_id, comment_id, user_id) VALUES @@ -33,15 +34,17 @@ INSERT INTO ProjectComments (project_id, comment_id, user_id) VALUES ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'I love the concept, but I think the documentation could be improved.'), (SELECT id FROM Users WHERE username = 'tech_writer2')), ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'Great to see more open-source tools for API development!'), (SELECT id FROM Users WHERE username = 'backend_guru4')), ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'I agree, but the API specs seem a bit too complex for beginners.'), (SELECT id FROM Users WHERE username = 'data_scientist3')), - ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'I hope this toolkit will integrate with other Go tools soon!'), (SELECT id FROM Users WHERE username = 'ui_designer5')); + ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'I hope this toolkit will integrate with other Go tools soon!'), (SELECT id FROM Users WHERE username = 'ui_designer5')), + ((SELECT id FROM Projects WHERE name = 'OpenAPI Toolkit'), (SELECT id FROM Comments WHERE content = 'I agree, the documentation is lacking in detail.'), (SELECT id FROM Users WHERE username = 'data_scientist3')); --- Comments on Posts +-- Comments on Posts (Parent-child relationships with hardcoded parent_comment_id) INSERT INTO Comments (content, parent_comment_id, likes, creation_date, user_id) VALUES ('Awesome update! I''ll try it out.', NULL, 2, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'backend_guru4')), ('Thanks for sharing! Will this feature be extended soon?', NULL, 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3')), ('Great work, looking forward to more updates!', NULL, 4, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'ui_designer5')), - ('Will this be compatible with earlier versions of OpenAPI?', (SELECT id FROM Comments WHERE content = 'Thanks for sharing! Will this feature be extended soon?'), 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2')), - ('I hope the next update addresses performance improvements.', (SELECT id FROM Comments WHERE content = 'Awesome update! I''ll try it out.'), 3, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3')); + ('Will this be compatible with earlier versions of OpenAPI?', 2, 1, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'tech_writer2')), -- Hardcoded parent_comment_id = 2 + ('I hope the next update addresses performance improvements.', 1, 3, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'data_scientist3')), -- Hardcoded parent_comment_id = 1 + ('Looking forward to testing it!', 3, 2, '2024-12-23 00:00:00', (SELECT id FROM Users WHERE username = 'dev_user1')); -- Hardcoded parent_comment_id = 3 -- PostComments relations (Mapping comments to posts) INSERT INTO PostComments (post_id, comment_id, user_id) VALUES @@ -49,4 +52,5 @@ INSERT INTO PostComments (post_id, comment_id, user_id) VALUES ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'Thanks for sharing! Will this feature be extended soon?'), (SELECT id FROM Users WHERE username = 'data_scientist3')), ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'Great work, looking forward to more updates!'), (SELECT id FROM Users WHERE username = 'ui_designer5')), ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'Will this be compatible with earlier versions of OpenAPI?'), (SELECT id FROM Users WHERE username = 'tech_writer2')), - ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'I hope the next update addresses performance improvements.'), (SELECT id FROM Users WHERE username = 'data_scientist3')); + ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'I hope the next update addresses performance improvements.'), (SELECT id FROM Users WHERE username = 'data_scientist3')), + ((SELECT id FROM Posts WHERE content = 'Excited to release the first version of OpenAPI Toolkit!'), (SELECT id FROM Comments WHERE content = 'Looking forward to testing it!'), (SELECT id FROM Users WHERE username = 'dev_user1')); diff --git a/backend/api/internal/database/dev.sqlite3 b/backend/api/internal/database/dev.sqlite3 index 4169563a9b8e60ee07b3c53811b97eb463811d25..ffe85d4e15e27344bf1fc0d3da6b03fa216e2f5d 100644 GIT binary patch delta 568 zcmaKp&ubJh6vvZECNtU1?3u9YxW3LA3xY`tuA7b94wAYpTRDIzwjG=z!&%gyUF?W_b|&S$Cwdj8?|jl zjZ(shQIavssAW-1Ip-x!V6cy11V``_zQHi@w?2XzZrB!M$#CPMuQ59aj^P)4htIGN zJIR>;4(3?S5m~`0lY&#S3J$jmj!T&4VC>L>v}XeUfkXHTDeP&d4>@a26n14DS9h0a zE?k@Hmus0(xZ_ohkPs6HUcx+_t~{&My+iMncgOR^TX9cJZ1<@cuL}n?ru8w4-5S2J z9IeP^uhR*;v0M#SWGifkaoDO7)lW~k*{!mR`k1RX2iMunl7pUL1%nxEnJTfaaup03 z*i|nE3)oTFV5WYJIjDnq4L4-4^e_x3WxO29R+z1oXcVOh%j~^TXF${{Y(5 Bj?Mr8 delta 443 zcmZvW!7D^T9LHzgo0<3KwQqk`QR46%lmlA3l9smQ!k&ae(H>YXcv_1ayZ-?VCkK0V zP*1O1-Q>W1)9!mxBvHaaq|EaiO*4IF`t|+Hw^(MyGTUj6r4d3+^878myxWnaY1Fkf zeKNn9#fDqWwp&pO*9bo06&~RhuH9JO1@tjWIL4GR5|F4&4X6^P1XL7)3Fq99VhTQj zFL;M%xCdXRufX*W+v=pYg5U#Q-~n!+A_wP?3blqBYPk|>bsB0ll}RnejD%t@sPzU< za0i~uUqYG%V?(x*;N%y&l*yN-1dTZ}WhEoxAUK9h^gX&C?Ki*8BQtCAh;vffi)fRE zj!GDb#p)QF;2PRS;;cH*n20BOI=d5{iKG>er+&9Gtc&{E2-d_*Z5%V<)CROED$^8+ zL2q1}CGu_}ao`OJ-)kTfs)ode*V4OYujl7-R>8KH)~p3PZw-!)So67!+>*VzoLech Nl7D_9ZpJ@#egJMZW#a$< diff --git a/backend/api/internal/handlers/comment_routes.go b/backend/api/internal/handlers/comment_routes.go index 9d2e211..7ba0565 100644 --- a/backend/api/internal/handlers/comment_routes.go +++ b/backend/api/internal/handlers/comment_routes.go @@ -224,10 +224,10 @@ func CreateCommentOnProject(context *gin.Context) { return } - strId := context.Param("post_id") + strId := context.Param("project_id") projId, err := strconv.Atoi(strId) if err != nil { - RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse post id: %v", err)) + RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse project id: %v", err)) return } @@ -281,7 +281,7 @@ func CreateCommentOnComment(context *gin.Context) { return } - strId := context.Param("post_id") + strId := context.Param("comment_id") commId, err := strconv.Atoi(strId) if err != nil { RespondWithError(context, http.StatusBadRequest, fmt.Sprintf("Failed to parse post id: %v", err)) @@ -322,8 +322,8 @@ func CreateCommentOnComment(context *gin.Context) { context.JSON(http.StatusCreated, gin.H{"message": fmt.Sprintf("Reply created successfully with id '%v'", id)}) } -// DeletePost handles DELETE requests to delete a post. -// It expects the `post_id` parameter in the URL. +// DeleteComment handles DELETE requests to delete a post. +// It expects the `comment_id` parameter in the URL. // Returns: // - 400 Bad Request if the post_id is invalid. // - 404 Not Found if no post is found with the given id. @@ -356,6 +356,13 @@ func DeleteComment(context *gin.Context) { }) } +// UpdateCommentContent handles DELETE requests to delete a post. +// It expects the `comment_id` parameter in the URL. +// Returns: +// - 400 Bad Request if the post_id is invalid. +// - 404 Not Found if no post is found with the given id. +// - 500 Internal Server Error if a database query fails. +// On success, responds with a 200 OK status and a message confirming the post deletion. func UpdateCommentContent(context *gin.Context) { id, err := strconv.Atoi(context.Param("comment_id")) if err != nil { diff --git a/backend/api/internal/types/types.go b/backend/api/internal/types/types.go index 2b3537c..4f1fcec 100644 --- a/backend/api/internal/types/types.go +++ b/backend/api/internal/types/types.go @@ -8,6 +8,7 @@ package types import ( + "database/sql" "time" ) @@ -41,12 +42,12 @@ type Post struct { } type Comment struct { - ID int64 `json:"id"` - User int64 `json:"user" binding:"required"` - Likes int64 `json:"likes"` - ParentComment int64 `json:"parent_comment" binding:"required"` - CreationDate time.Time `json:"created_on"` - Content string `json:"content" binding:"required"` + ID int64 `json:"id"` + User int64 `json:"user" binding:"required"` + Likes int64 `json:"likes"` + ParentComment sql.NullInt64 `json:"parent_comment" binding:"required"` + CreationDate time.Time `json:"created_on"` + Content string `json:"content" binding:"required"` } type ErrorResponse struct {