From 26ce0220107a7027f8bbe15c74dfc80faaa2b2cb Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 26 Aug 2024 21:01:45 +0300 Subject: [PATCH 1/3] aait.backend.g7.mohammed-bug-fixes-and-refactoring --- .../delivery/controllers/blog_controller.go | 139 ++++---------- .../delivery/controllers/user_controller.go | 178 +++++------------- .../blog_project/delivery/routers/router.go | 35 ++-- 3 files changed, 99 insertions(+), 253 deletions(-) diff --git a/backend/AAiT-backend-group-7/blog_project/delivery/controllers/blog_controller.go b/backend/AAiT-backend-group-7/blog_project/delivery/controllers/blog_controller.go index bb3f85a8f..892991317 100644 --- a/backend/AAiT-backend-group-7/blog_project/delivery/controllers/blog_controller.go +++ b/backend/AAiT-backend-group-7/blog_project/delivery/controllers/blog_controller.go @@ -7,33 +7,29 @@ import ( "github.com/gin-gonic/gin" ) -type blogController struct { +type BlogController struct { BlogUsecase domain.IBlogUsecase } func NewBlogController(blogUsecase domain.IBlogUsecase) domain.IBlogController { - return &blogController{BlogUsecase: blogUsecase} + return &BlogController{BlogUsecase: blogUsecase} } -func (bc *blogController) GetAllBlogs(c *gin.Context) { +func (bc *BlogController) GetAllBlogs(c *gin.Context) { sortOrder := c.DefaultQuery("sort", "DESC") // Default to "DESC" if not specified - page := c.DefaultQuery("page", "1") - limit := c.DefaultQuery("limit", "10") - - // Convert page and limit to integers - pageInt, err := strconv.Atoi(page) + page, err := strconv.Atoi(c.DefaultQuery("page", "1")) if err != nil { c.JSON(400, gin.H{"error": "Invalid page number"}) return } - limitInt, err := strconv.Atoi(limit) + limit, err := strconv.Atoi(c.DefaultQuery("limit", "10")) if err != nil { c.JSON(400, gin.H{"error": "Invalid limit number"}) return } - blogs, err := bc.BlogUsecase.GetAllBlogs(c, sortOrder, pageInt, limitInt) + blogs, err := bc.BlogUsecase.GetAllBlogs(c, sortOrder, page, limit) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -41,17 +37,14 @@ func (bc *blogController) GetAllBlogs(c *gin.Context) { c.JSON(200, blogs) } -func (bc *blogController) CreateBlog(c *gin.Context) { +func (bc *BlogController) CreateBlog(c *gin.Context) { var blog domain.Blog - err := c.BindJSON(&blog) - - if err != nil { + if err := c.BindJSON(&blog); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } newBlog, err := bc.BlogUsecase.CreateBlog(c, blog) - if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -59,186 +52,120 @@ func (bc *blogController) CreateBlog(c *gin.Context) { c.JSON(200, newBlog) } -func (bc *blogController) UpdateBlog(c *gin.Context) { - id := c.Param("id") - - idInt, err := strconv.Atoi(id) - +func (bc *BlogController) UpdateBlog(c *gin.Context) { + id, err := strconv.Atoi(c.Query("id")) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(400, gin.H{"error": "Invalid blog ID"}) return } var blog domain.Blog - err = c.BindJSON(&blog) - - if err != nil { + if err := c.BindJSON(&blog); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - updatedBlog, err := bc.BlogUsecase.UpdateBlog(c, idInt, blog) - + updatedBlog, err := bc.BlogUsecase.UpdateBlog(c, id, blog) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, updatedBlog) - } -func (bc *blogController) DeleteBlog(c *gin.Context) { - - id := c.Param("id") - - idInt, err := strconv.Atoi(id) - +func (bc *BlogController) DeleteBlog(c *gin.Context) { + id, err := strconv.Atoi(c.Query("id")) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(400, gin.H{"error": "Invalid blog ID"}) return } - err = bc.BlogUsecase.DeleteBlog(c, idInt) - - if err != nil { + if err := bc.BlogUsecase.DeleteBlog(c, id); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Blog deleted successfully"}) - } -func (bc *blogController) LikeBlog(c *gin.Context) { - blogID := c.Param("blog_id") - authorID := c.Param("author_id") - - blogIDInt, err := strconv.Atoi(blogID) - +func (bc *BlogController) LikeBlog(c *gin.Context) { + blogID, err := strconv.Atoi(c.Query("id")) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(400, gin.H{"error": "Invalid blog ID"}) return } - authorIDInt, err := strconv.Atoi(authorID) - - if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) - return - } - - likedBlog, err := bc.BlogUsecase.LikeBlog(c, blogIDInt, authorIDInt) - + likedBlog, err := bc.BlogUsecase.LikeBlog(c, blogID) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, likedBlog) - } -func (bc *blogController) DislikeBlog(c *gin.Context) { - blogID := c.Param("blog_id") - authorID := c.Param("author_id") - - blogIDInt, err := strconv.Atoi(blogID) - +func (bc *BlogController) DislikeBlog(c *gin.Context) { + blogID, err := strconv.Atoi(c.Query("id")) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) - return - } - - authorIDInt, err := strconv.Atoi(authorID) - - if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(400, gin.H{"error": "Invalid blog ID"}) return } - dislikedBlog, err := bc.BlogUsecase.DislikeBlog(c, blogIDInt, authorIDInt) - + dislikedBlog, err := bc.BlogUsecase.DislikeBlog(c, blogID) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, dislikedBlog) - } -func (bc *blogController) AddComment(c *gin.Context) { - - blogID := c.Param("blog_id") - authorID := c.Param("author_id") - - blogIDInt, err := strconv.Atoi(blogID) - - if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) - return - } - - authorIDInt, err := strconv.Atoi(authorID) - +func (bc *BlogController) AddComment(c *gin.Context) { + blogID, err := strconv.Atoi(c.Query("id")) if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) + c.JSON(400, gin.H{"error": "Invalid blog ID"}) return } var comment domain.Comment - err = c.BindJSON(&comment) - - comment.UserID = authorIDInt - - if err != nil { + if err := c.BindJSON(&comment); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - commentedBlog, err := bc.BlogUsecase.AddComent(c, blogIDInt, authorIDInt, comment.Content) - + commentedBlog, err := bc.BlogUsecase.AddComment(c, blogID, comment.Content) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, commentedBlog) - } -func (bc *blogController) Search(c *gin.Context) { +func (bc *BlogController) Search(c *gin.Context) { author := c.Query("author") tags := c.QueryArray("tags") title := c.Query("title") blogs, err := bc.BlogUsecase.Search(c, author, tags, title) - if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, blogs) - } type BlogContent struct { Content string `json:"content"` } -func (bc *blogController) AiRecommendation(c *gin.Context) { +func (bc *BlogController) AiRecommendation(c *gin.Context) { var content BlogContent - - err := c.ShouldBindJSON(&content) - // print("content is", content) - if err != nil { - print("error is here") + if err := c.ShouldBindJSON(&content); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } recommendation, err := bc.BlogUsecase.AiRecommendation(c, content.Content) - if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, recommendation) - } diff --git a/backend/AAiT-backend-group-7/blog_project/delivery/controllers/user_controller.go b/backend/AAiT-backend-group-7/blog_project/delivery/controllers/user_controller.go index b7683c1fa..9da24f44d 100644 --- a/backend/AAiT-backend-group-7/blog_project/delivery/controllers/user_controller.go +++ b/backend/AAiT-backend-group-7/blog_project/delivery/controllers/user_controller.go @@ -10,98 +10,69 @@ import ( "github.com/gin-gonic/gin" ) -type userController struct { - UserUsecase domain.IUserUsecase +type UserController struct { + userUsecase domain.IUserUsecase } func NewUserController(userUsecase domain.IUserUsecase) domain.IUserController { - return &userController{UserUsecase: userUsecase} + return &UserController{userUsecase: userUsecase} } -func (uc *userController) GetAllUsers(c *gin.Context) { - users, err := uc.UserUsecase.GetAllUsers(c) +func (uc *UserController) GetAllUsers(c *gin.Context) { + users, err := uc.userUsecase.GetAllUsers(c) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - - // Prepend the URL to the ProfilePic path - for i := range users { - if users[i].ProfilePic != "" { - users[i].ProfilePic = fmt.Sprintf("http://localhost:8080/%s", users[i].ProfilePic) - } - } - c.JSON(200, users) } -func (uc *userController) GetUserByID(c *gin.Context) { - id := c.Param("id") - idInt, err := strconv.Atoi(id) +func (uc *UserController) GetUserByID(c *gin.Context) { + id, err := strconv.Atoi(c.Query("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - user, err := uc.UserUsecase.GetUserByID(c, idInt) + user, err := uc.userUsecase.GetUserByID(c, id) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - - // Prepend the URL to the ProfilePic path - if user.ProfilePic != "" { - user.ProfilePic = fmt.Sprintf("http://localhost:8080/%s", user.ProfilePic) - } - c.JSON(200, user) } -func (uc *userController) CreateUser(c *gin.Context) { +func (uc *UserController) CreateUser(c *gin.Context) { var user domain.User - - // Parse form data - err := c.Request.ParseMultipartForm(10 << 20) // Limit upload size to 10MB - if err != nil { + if err := c.Request.ParseMultipartForm(10 << 20); err != nil { c.JSON(400, gin.H{"error": "Could not parse form data"}) return } - // Retrieve JSON fields user.Username = c.PostForm("username") user.Password = c.PostForm("password") user.Email = c.PostForm("email") - user.Role = c.PostForm("role") user.Bio = c.PostForm("bio") user.Phone = c.PostForm("phone") - // Handle profile picture upload (optional) file, handler, err := c.Request.FormFile("profile_pic") - if err == nil { // Only proceed if the file was uploaded + if err == nil { defer file.Close() - - // Define the path where the file will be stored filePath := fmt.Sprintf("./uploads/%s", handler.Filename) - - // Save the file to disk out, err := os.Create(filePath) if err != nil { - c.JSON(500, gin.H{"error": "Unable to create the file for writing: " + err.Error()}) + c.JSON(500, gin.H{"error": "Unable to create file: " + err.Error()}) return } defer out.Close() - - _, err = io.Copy(out, file) - if err != nil { + if _, err = io.Copy(out, file); err != nil { c.JSON(500, gin.H{"error": "Unable to save the file"}) return } - - // Set the profile picture path user.ProfilePic = "/uploads/" + handler.Filename } - newUser, err := uc.UserUsecase.CreateUser(c, user) + newUser, err := uc.userUsecase.CreateUser(c, user) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -109,59 +80,43 @@ func (uc *userController) CreateUser(c *gin.Context) { c.JSON(200, newUser) } -func (uc *userController) UpdateUser(c *gin.Context) { - id := c.Param("id") - - idInt, err := strconv.Atoi(id) +func (uc *UserController) UpdateUser(c *gin.Context) { + id, err := strconv.Atoi(c.Query("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } var user domain.User - - // Parse form data - err = c.Request.ParseMultipartForm(10 << 20) // Limit upload size to 10MB - if err != nil { + if err := c.Request.ParseMultipartForm(10 << 20); err != nil { c.JSON(400, gin.H{"error": "Could not parse form data"}) return } - // Retrieve JSON fields user.Username = c.PostForm("username") user.Password = c.PostForm("password") user.Email = c.PostForm("email") - user.Role = c.PostForm("role") user.Bio = c.PostForm("bio") user.Phone = c.PostForm("phone") - // Handle profile picture upload (optional) file, handler, err := c.Request.FormFile("profile_pic") - if err == nil { // Only proceed if the file was uploaded + if err == nil { defer file.Close() - - // Define the path where the file will be stored filePath := fmt.Sprintf("./uploads/%s", handler.Filename) - - // Save the file to disk out, err := os.Create(filePath) if err != nil { - c.JSON(500, gin.H{"error": "Unable to create the file for writing: " + err.Error()}) + c.JSON(500, gin.H{"error": "Unable to create file: " + err.Error()}) return } defer out.Close() - - _, err = io.Copy(out, file) - if err != nil { + if _, err = io.Copy(out, file); err != nil { c.JSON(500, gin.H{"error": "Unable to save the file"}) return } - - // Set the profile picture path user.ProfilePic = "/uploads/" + handler.Filename } - newUser, err := uc.UserUsecase.UpdateUser(c, idInt, user) + newUser, err := uc.userUsecase.UpdateUser(c, id, user) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -169,149 +124,116 @@ func (uc *userController) UpdateUser(c *gin.Context) { c.JSON(200, newUser) } -func (uc *userController) DeleteUser(c *gin.Context) { - id := c.Param("id") - - idInt, err := strconv.Atoi(id) +func (uc *UserController) DeleteUser(c *gin.Context) { + id, err := strconv.Atoi(c.Query("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - err = uc.UserUsecase.DeleteUser(c, idInt) - if err != nil { + if err := uc.userUsecase.DeleteUser(c, id); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "User deleted successfully"}) } -func (uc *userController) AddBlog(c *gin.Context) { - userID := c.Param("userID") - - userIDInt, err := strconv.Atoi(userID) - if err != nil { +func (uc *UserController) Login(c *gin.Context) { + var user domain.User + if err := c.BindJSON(&user); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - var Blog domain.Blog - err = c.BindJSON(&Blog) + accessToken, refreshToken, err := uc.userUsecase.Login(c, user.Username, user.Password) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - newUser, err := uc.UserUsecase.AddBlog(c, userIDInt, Blog) + user, err = uc.userUsecase.GetUserByUsername(c, user.Username) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - c.JSON(200, newUser) + c.JSON(200, gin.H{"access_token": accessToken, "refresh_token": refreshToken, "user": user}) } -func (uc *userController) Login(c *gin.Context) { - var user domain.User - err := c.BindJSON(&user) - if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) - return - } - - accessToken, refreshToken, err := uc.UserUsecase.Login(c, user.Username, user.Password) - if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) - return - } - c.JSON(200, gin.H{"access token": accessToken, "refresh token": refreshToken}) -} - -func (uc *userController) Logout(c *gin.Context) { +func (uc *UserController) Logout(c *gin.Context) { var token struct { Token string `json:"token"` } - err := c.ShouldBindJSON(&token) - if err != nil { + if err := c.ShouldBindJSON(&token); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - err = uc.UserUsecase.Logout(c, token.Token) - if err != nil { + if err := uc.userUsecase.Logout(c, token.Token); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Logged out successfully"}) - } -func (uc *userController) ForgetPassword(c *gin.Context) { - email := c.Param("email") - err := uc.UserUsecase.ForgetPassword(c, email) - if err != nil { +func (uc *UserController) ForgetPassword(c *gin.Context) { + email := c.Query("email") + if err := uc.userUsecase.ForgetPassword(c, email); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Password reset link sent to email"}) } -func (uc *userController) ResetPassword(c *gin.Context) { - - username := c.Param("username") - password := c.Param("password") - err := uc.UserUsecase.ResetPassword(c, username, password) - if err != nil { +func (uc *UserController) ResetPassword(c *gin.Context) { + username := c.Query("username") + password := c.Query("password") + if err := uc.userUsecase.ResetPassword(c, username, password); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Password reset successfully"}) } -func (uc *userController) PromoteUser(c *gin.Context) { - id := c.Param("id") - idInt, err := strconv.Atoi(id) +func (uc *UserController) PromoteUser(c *gin.Context) { + id, err := strconv.Atoi(c.Query("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - user, err := uc.UserUsecase.PromoteUser(c, idInt) + user, err := uc.userUsecase.PromoteUser(c, id) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - c.JSON(200, gin.H{"message": "User promoted successfully", "user": user}) } -func (uc *userController) DemoteUser(c *gin.Context) { - id := c.Param("id") - idInt, err := strconv.Atoi(id) +func (uc *UserController) DemoteUser(c *gin.Context) { + id, err := strconv.Atoi(c.Query("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - user, err := uc.UserUsecase.DemoteUser(c, idInt) + user, err := uc.userUsecase.DemoteUser(c, id) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - c.JSON(200, gin.H{"message": "User demoted successfully", "user": user}) } -func (uc *userController) RefreshToken(c *gin.Context) { +func (uc *UserController) RefreshToken(c *gin.Context) { var refreshToken struct { RefreshToken string `json:"refresh_token"` } - err := c.ShouldBindJSON(&refreshToken) - - if err != nil { + if err := c.ShouldBindJSON(&refreshToken); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } - newToken, err := uc.UserUsecase.RefreshToken(c, refreshToken.RefreshToken) + + newToken, err := uc.userUsecase.RefreshToken(c, refreshToken.RefreshToken) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return diff --git a/backend/AAiT-backend-group-7/blog_project/delivery/routers/router.go b/backend/AAiT-backend-group-7/blog_project/delivery/routers/router.go index e61926d42..b3d48f158 100644 --- a/backend/AAiT-backend-group-7/blog_project/delivery/routers/router.go +++ b/backend/AAiT-backend-group-7/blog_project/delivery/routers/router.go @@ -2,38 +2,36 @@ package routers import ( "blog_project/domain" - "os" - "blog_project/infrastructure" + "os" "github.com/gin-gonic/gin" ) func SetupRouter(blogController domain.IBlogController, userController domain.IUserController) *gin.Engine { - r := gin.Default() + // Blog routes blogs := r.Group("/blogs") - blogs.Use(infrastructure.JwtAuthMiddleware(os.Getenv("jwt_secret"))) { - // http://localhost:8080/blogs?sort=DESC&page=1&limit=10 blogs.GET("/", blogController.GetAllBlogs) blogs.POST("/", blogController.CreateBlog) - blogs.PUT("/:id", blogController.UpdateBlog) - blogs.DELETE("/:id", blogController.DeleteBlog) + blogs.PUT("/", blogController.UpdateBlog) + blogs.DELETE("/", blogController.DeleteBlog) - blogs.POST("/:blog_id/:author_id/comment", blogController.AddComment) - blogs.POST("/:blog_id/:author_id/like", blogController.LikeBlog) - blogs.POST("/:blog_id/:author_id/dislike", blogController.DislikeBlog) + blogs.POST("/comment", blogController.AddComment) + blogs.POST("/like", blogController.LikeBlog) + blogs.POST("/dislike", blogController.DislikeBlog) blogs.POST("/search", blogController.Search) - blogs.POST("/GenerateContent", blogController.AiRecommendation) + blogs.POST("/generate-content", blogController.AiRecommendation) } + // User routes users := r.Group("/users") users.POST("/", userController.CreateUser) users.POST("/login", userController.Login) - users.POST("/forget-password/:email", userController.ForgetPassword) + users.POST("/forget-password/", userController.ForgetPassword) users.POST("/reset-password/:username/:password", userController.ResetPassword) users.POST("/logout", userController.Logout) users.POST("/refresh-token", userController.RefreshToken) @@ -41,14 +39,14 @@ func SetupRouter(blogController domain.IBlogController, userController domain.IU users.Use(infrastructure.JwtAuthMiddleware(os.Getenv("jwt_secret"))) { users.GET("/", infrastructure.AdminMiddleware(), userController.GetAllUsers) - users.GET("/:id", infrastructure.AdminMiddleware(), userController.GetUserByID) - users.PUT("/:id", userController.UpdateUser) - users.DELETE("/:id", userController.DeleteUser) - // users.POST("/:userID/blog", userController.AddBlog) - users.POST("/promote/:id", infrastructure.AdminMiddleware(), userController.PromoteUser) - users.POST("/demote/:id", infrastructure.AdminMiddleware(), userController.DemoteUser) + users.GET("/user", infrastructure.AdminMiddleware(), userController.GetUserByID) + users.PUT("/", userController.UpdateUser) + users.DELETE("/", userController.DeleteUser) + users.POST("/promote", infrastructure.AdminMiddleware(), userController.PromoteUser) + users.POST("/demote", infrastructure.AdminMiddleware(), userController.DemoteUser) } + // Upload routes uploads := r.Group("/uploads") uploads.Use(infrastructure.JwtAuthMiddleware(os.Getenv("jwt_secret"))) { @@ -56,5 +54,4 @@ func SetupRouter(blogController domain.IBlogController, userController domain.IU } return r - } From 39b8d3888b3340e53a1311c0d6c0ba18802c9718 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 26 Aug 2024 21:02:27 +0300 Subject: [PATCH 2/3] aait.backend.g7.mohammed-bug-fixes-and-refactoring --- .../blog_project/domain/blog.go | 20 +-- .../blog_project/domain/user.go | 1 - .../repositories/blog_repository.go | 52 ++++-- .../repositories/user_repository.go | 160 +++++++----------- .../blog_project/usecases/blog_usecases.go | 100 +++++++---- .../blog_project/usecases/user_usecases.go | 159 ++++++++--------- 6 files changed, 253 insertions(+), 239 deletions(-) diff --git a/backend/AAiT-backend-group-7/blog_project/domain/blog.go b/backend/AAiT-backend-group-7/blog_project/domain/blog.go index f78402ba5..dff55c340 100644 --- a/backend/AAiT-backend-group-7/blog_project/domain/blog.go +++ b/backend/AAiT-backend-group-7/blog_project/domain/blog.go @@ -22,21 +22,21 @@ type Blog struct { type Comment struct { ID int `json:"id"` - UserID int `json:"user_id"` + User string `json:"user"` Content string `json:"content"` Date time.Time `json:"date"` } type Like struct { - ID int `json:"id"` - UserID int `json:"user_id"` - Date time.Time `json:"date"` + ID int `json:"id"` + User string `json:"user"` + Date time.Time `json:"date"` } type Dislike struct { - ID int `json:"id"` - UserID int `json:"user_id"` - Date time.Time `json:"date"` + ID int `json:"id"` + User string `json:"user"` + Date time.Time `json:"date"` } type IBlogRepository interface { @@ -58,9 +58,9 @@ type IBlogUsecase interface { CreateBlog(ctx context.Context, blog Blog) (Blog, error) UpdateBlog(ctx context.Context, id int, blog Blog) (Blog, error) DeleteBlog(ctx context.Context, id int) error - AddComent(ctx context.Context, blogID int, authorID int, content string) (Blog, error) - LikeBlog(ctx context.Context, blogID int, authorID int) (Blog, error) - DislikeBlog(ctx context.Context, blogID int, authorID int) (Blog, error) + AddComment(ctx context.Context, blogID int, content string) (Blog, error) + LikeBlog(ctx context.Context, blogID int) (Blog, error) + DislikeBlog(ctx context.Context, blogID int) (Blog, error) Search(ctx context.Context, author string, tags []string, title string) ([]Blog, error) AiRecommendation(ctx context.Context, content string) (string, error) } diff --git a/backend/AAiT-backend-group-7/blog_project/domain/user.go b/backend/AAiT-backend-group-7/blog_project/domain/user.go index 26611bb52..6949e7b5a 100644 --- a/backend/AAiT-backend-group-7/blog_project/domain/user.go +++ b/backend/AAiT-backend-group-7/blog_project/domain/user.go @@ -56,7 +56,6 @@ type IUserController interface { CreateUser(c *gin.Context) UpdateUser(c *gin.Context) DeleteUser(c *gin.Context) - AddBlog(c *gin.Context) Login(c *gin.Context) Logout(c *gin.Context) ForgetPassword(c *gin.Context) diff --git a/backend/AAiT-backend-group-7/blog_project/repositories/blog_repository.go b/backend/AAiT-backend-group-7/blog_project/repositories/blog_repository.go index 835bdaec8..6cddef8fa 100644 --- a/backend/AAiT-backend-group-7/blog_project/repositories/blog_repository.go +++ b/backend/AAiT-backend-group-7/blog_project/repositories/blog_repository.go @@ -15,19 +15,19 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) -type blogRepository struct { +type BlogRepository struct { collection *mongo.Collection cache domain.Cache } func NewBlogRepository(collection *mongo.Collection, cache domain.Cache) domain.IBlogRepository { - return &blogRepository{ + return &BlogRepository{ collection: collection, cache: cache, } } -func (blogRepo *blogRepository) GetAllBlogs(ctx context.Context) ([]domain.Blog, error) { +func (blogRepo *BlogRepository) GetAllBlogs(ctx context.Context) ([]domain.Blog, error) { cacheKey := "blogs:all" var blogs []domain.Blog @@ -64,7 +64,7 @@ func (blogRepo *blogRepository) GetAllBlogs(ctx context.Context) ([]domain.Blog, return blogs, nil } -func (blogRepo *blogRepository) GetBlogsByPage(ctx context.Context, offset, limit int) ([]domain.Blog, error) { +func (blogRepo *BlogRepository) GetBlogsByPage(ctx context.Context, offset, limit int) ([]domain.Blog, error) { cacheKey := fmt.Sprintf("blogs:page:%d:%d", offset, limit) var blogs []domain.Blog @@ -102,7 +102,7 @@ func (blogRepo *blogRepository) GetBlogsByPage(ctx context.Context, offset, limi return blogs, nil } -func (blogRepo *blogRepository) GetBlogByID(ctx context.Context, id int) (domain.Blog, error) { +func (blogRepo *BlogRepository) GetBlogByID(ctx context.Context, id int) (domain.Blog, error) { cacheKey := fmt.Sprintf("blog:%d", id) var blog domain.Blog @@ -126,7 +126,7 @@ func (blogRepo *blogRepository) GetBlogByID(ctx context.Context, id int) (domain return blog, nil } -func (blogRepo *blogRepository) CreateBlog(ctx context.Context, blog domain.Blog) (domain.Blog, error) { +func (blogRepo *BlogRepository) CreateBlog(ctx context.Context, blog domain.Blog) (domain.Blog, error) { blog.Date = time.Now() // Set the current time for the blog's creation date _, err := blogRepo.collection.InsertOne(ctx, blog) if err != nil { @@ -145,7 +145,7 @@ func (blogRepo *blogRepository) CreateBlog(ctx context.Context, blog domain.Blog return blog, nil } -func (blogRepo *blogRepository) UpdateBlog(ctx context.Context, id int, blog domain.Blog) (domain.Blog, error) { +func (blogRepo *BlogRepository) UpdateBlog(ctx context.Context, id int, blog domain.Blog) (domain.Blog, error) { var updatedBlog domain.Blog // Update in DB @@ -176,7 +176,7 @@ func (blogRepo *blogRepository) UpdateBlog(ctx context.Context, id int, blog dom return updatedBlog, nil } -func (blogRepo *blogRepository) DeleteBlog(ctx context.Context, id int) error { +func (blogRepo *BlogRepository) DeleteBlog(ctx context.Context, id int) error { result, err := blogRepo.collection.DeleteOne(ctx, bson.M{"id": id}) if err != nil { return err @@ -198,7 +198,7 @@ func (blogRepo *blogRepository) DeleteBlog(ctx context.Context, id int) error { return nil } -func (blogRepo *blogRepository) SearchByTitle(ctx context.Context, title string) ([]domain.Blog, error) { +func (blogRepo *BlogRepository) SearchByTitle(ctx context.Context, title string) ([]domain.Blog, error) { cacheKey := fmt.Sprintf("blogs:search:title:%s", title) var blogs []domain.Blog @@ -236,7 +236,7 @@ func (blogRepo *blogRepository) SearchByTitle(ctx context.Context, title string) return blogs, nil } -func (blogRepo *blogRepository) SearchByTags(ctx context.Context, tags []string) ([]domain.Blog, error) { +func (blogRepo *BlogRepository) SearchByTags(ctx context.Context, tags []string) ([]domain.Blog, error) { cacheKey := fmt.Sprintf("blogs:search:tags:%v", tags) var blogs []domain.Blog @@ -274,7 +274,7 @@ func (blogRepo *blogRepository) SearchByTags(ctx context.Context, tags []string) return blogs, nil } -func (blogRepo *blogRepository) SearchByAuthor(ctx context.Context, author string) ([]domain.Blog, error) { +func (blogRepo *BlogRepository) SearchByAuthor(ctx context.Context, author string) ([]domain.Blog, error) { cacheKey := fmt.Sprintf("blogs:search:author:%s", author) var blogs []domain.Blog @@ -312,7 +312,7 @@ func (blogRepo *blogRepository) SearchByAuthor(ctx context.Context, author strin } // Helper function to invalidate pagination and search caches -func (blogRepo *blogRepository) invalidatePaginationAndSearchCaches(ctx context.Context) { +func (blogRepo *BlogRepository) invalidatePaginationAndSearchCaches(ctx context.Context) { // Invalidate all pagination caches blogRepo.cache.DelByPattern(ctx, "blogs:page:*") @@ -320,8 +320,10 @@ func (blogRepo *blogRepository) invalidatePaginationAndSearchCaches(ctx context. blogRepo.cache.DelByPattern(ctx, "blogs:search:*") } -func (blogRepo *blogRepository) UpdateAuthorName(ctx context.Context, oldName, newName string) error { +func (blogRepo *BlogRepository) UpdateAuthorName(ctx context.Context, oldName, newName string) error { println("Updating author name from", oldName, "to", newName) + + // Update the author's name in the blog posts filter := bson.M{"author": oldName} update := bson.M{"$set": bson.M{"author": newName}} _, err := blogRepo.collection.UpdateMany(ctx, filter, update) @@ -329,5 +331,29 @@ func (blogRepo *blogRepository) UpdateAuthorName(ctx context.Context, oldName, n return err } + // Update the author's name in the comments inside blog posts + commentFilter := bson.M{"comments.user": oldName} + commentUpdate := bson.M{"$set": bson.M{"comments.$[].user": newName}} + _, err = blogRepo.collection.UpdateMany(ctx, commentFilter, commentUpdate) + if err != nil { + return err + } + + // Update the author's name in the likes inside blog posts + likeFilter := bson.M{"likes.user": oldName} + likeUpdate := bson.M{"$set": bson.M{"likes.$[].user": newName}} + _, err = blogRepo.collection.UpdateMany(ctx, likeFilter, likeUpdate) + if err != nil { + return err + } + + // Update the author's name in the dislikes inside blog posts + dislikeFilter := bson.M{"dislikes.user": oldName} + dislikeUpdate := bson.M{"$set": bson.M{"dislikes.$[].user": newName}} + _, err = blogRepo.collection.UpdateMany(ctx, dislikeFilter, dislikeUpdate) + if err != nil { + return err + } + return nil } diff --git a/backend/AAiT-backend-group-7/blog_project/repositories/user_repository.go b/backend/AAiT-backend-group-7/blog_project/repositories/user_repository.go index fb8b67397..396244a64 100644 --- a/backend/AAiT-backend-group-7/blog_project/repositories/user_repository.go +++ b/backend/AAiT-backend-group-7/blog_project/repositories/user_repository.go @@ -12,27 +12,27 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) -type userRepository struct { +type UserRepository struct { collection *mongo.Collection cache domain.Cache } func NewUserRepository(collection *mongo.Collection, cache domain.Cache) domain.IUserRepository { - return &userRepository{ + return &UserRepository{ collection: collection, cache: cache, } } -func (userRepo *userRepository) GetAllUsers(ctx context.Context) ([]domain.User, error) { +func (repo *UserRepository) GetAllUsers(ctx context.Context) ([]domain.User, error) { cacheKey := "users:all" var users []domain.User - err := userRepo.cache.Get(ctx, cacheKey, &users) - if err == nil { + + if err := repo.cache.Get(ctx, cacheKey, &users); err == nil { return users, nil } - cursor, err := userRepo.collection.Find(ctx, bson.M{}) + cursor, err := repo.collection.Find(ctx, bson.M{}) if err != nil { return nil, err } @@ -47,53 +47,49 @@ func (userRepo *userRepository) GetAllUsers(ctx context.Context) ([]domain.User, } if len(users) > 0 { - userRepo.cache.Set(ctx, cacheKey, users, 1*time.Hour) + repo.cache.Set(ctx, cacheKey, users, 1*time.Hour) } return users, nil } -func (userRepo *userRepository) GetUserByID(ctx context.Context, id int) (domain.User, error) { +func (repo *UserRepository) GetUserByID(ctx context.Context, id int) (domain.User, error) { var user domain.User cacheKey := fmt.Sprintf("user:%d", id) - err := userRepo.cache.Get(ctx, cacheKey, &user) - if err == nil { + if err := repo.cache.Get(ctx, cacheKey, &user); err == nil { return user, nil } - result := userRepo.collection.FindOne(ctx, bson.M{"id": id}) + result := repo.collection.FindOne(ctx, bson.M{"id": id}) if err := result.Decode(&user); err != nil { - if err == mongo.ErrNoDocuments { + if errors.Is(err, mongo.ErrNoDocuments) { return domain.User{}, errors.New("user not found") } return domain.User{}, err } - userRepo.cache.Set(ctx, cacheKey, user, 1*time.Hour) + repo.cache.Set(ctx, cacheKey, user, 1*time.Hour) return user, nil } -func (userRepo *userRepository) CreateUser(ctx context.Context, user domain.User) (domain.User, error) { - users, _ := userRepo.GetAllUsers(ctx) +func (repo *UserRepository) CreateUser(ctx context.Context, user domain.User) (domain.User, error) { + users, _ := repo.GetAllUsers(ctx) if len(users) == 0 { user.Role = "admin" } - _, err := userRepo.collection.InsertOne(ctx, user) - if err != nil { + if _, err := repo.collection.InsertOne(ctx, user); err != nil { return domain.User{}, err } - // Invalidate the cache for all users - userRepo.cache.Del(ctx, "users:all") - + repo.cache.Del(ctx, "users:all") return user, nil } -func (userRepo *userRepository) UpdateUser(ctx context.Context, id int, user domain.User) (domain.User, error) { +func (repo *UserRepository) UpdateUser(ctx context.Context, id int, user domain.User) (domain.User, error) { var updatedUser domain.User - result := userRepo.collection.FindOneAndUpdate( + result := repo.collection.FindOneAndUpdate( ctx, bson.M{"id": id}, bson.M{"$set": user}, @@ -101,34 +97,18 @@ func (userRepo *userRepository) UpdateUser(ctx context.Context, id int, user dom ) if err := result.Decode(&updatedUser); err != nil { - if err == mongo.ErrNoDocuments { + if errors.Is(err, mongo.ErrNoDocuments) { return domain.User{}, errors.New("user not found") } return domain.User{}, err } - // Invalidate the cache for the updated user and related cache entries - cacheKey := fmt.Sprintf("user:%d", id) - userRepo.cache.Del(ctx, cacheKey) - - // Invalidate cache entries by username and email if those fields were updated - if user.Username != "" { - cacheKeyByUsername := fmt.Sprintf("user:username:%s", user.Username) - userRepo.cache.Del(ctx, cacheKeyByUsername) - } - if user.Email != "" { - cacheKeyByEmail := fmt.Sprintf("user:email:%s", user.Email) - userRepo.cache.Del(ctx, cacheKeyByEmail) - } - - // Invalidate the cache for all users - userRepo.cache.Del(ctx, "users:all") - + repo.invalidateUserCache(ctx, id, user) return updatedUser, nil } -func (userRepo *userRepository) DeleteUser(ctx context.Context, id int) error { - result, err := userRepo.collection.DeleteOne(ctx, bson.M{"id": id}) +func (repo *UserRepository) DeleteUser(ctx context.Context, id int) error { + result, err := repo.collection.DeleteOne(ctx, bson.M{"id": id}) if err != nil { return err } @@ -137,103 +117,77 @@ func (userRepo *userRepository) DeleteUser(ctx context.Context, id int) error { return errors.New("user not found") } - // Invalidate the cache for the deleted user and related cache entries - cacheKey := fmt.Sprintf("user:%d", id) - userRepo.cache.Del(ctx, cacheKey) - - // Also invalidate cache by username and email - var user domain.User - err = userRepo.collection.FindOne(ctx, bson.M{"id": id}).Decode(&user) - if err == nil { - cacheKeyByUsername := fmt.Sprintf("user:username:%s", user.Username) - userRepo.cache.Del(ctx, cacheKeyByUsername) - - cacheKeyByEmail := fmt.Sprintf("user:email:%s", user.Email) - userRepo.cache.Del(ctx, cacheKeyByEmail) - } - - // Invalidate the cache for all users - userRepo.cache.Del(ctx, "users:all") - + repo.invalidateUserCache(ctx, id, domain.User{}) return nil } -func (userRepo *userRepository) SearchByUsername(ctx context.Context, username string) (domain.User, error) { +func (repo *UserRepository) SearchByUsername(ctx context.Context, username string) (domain.User, error) { cacheKey := fmt.Sprintf("user:username:%s", username) var user domain.User - err := userRepo.cache.Get(ctx, cacheKey, &user) - if err == nil { + + if err := repo.cache.Get(ctx, cacheKey, &user); err == nil { return user, nil } - err = userRepo.collection.FindOne(ctx, bson.M{"username": username}).Decode(&user) - if err != nil { - if err == mongo.ErrNoDocuments { + if err := repo.collection.FindOne(ctx, bson.M{"username": username}).Decode(&user); err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { return domain.User{}, errors.New("user not found") } return domain.User{}, err } - userRepo.cache.Set(ctx, cacheKey, user, 1*time.Hour) + repo.cache.Set(ctx, cacheKey, user, 1*time.Hour) return user, nil } -func (userRepo *userRepository) SearchByEmail(ctx context.Context, email string) (domain.User, error) { +func (repo *UserRepository) SearchByEmail(ctx context.Context, email string) (domain.User, error) { cacheKey := fmt.Sprintf("user:email:%s", email) var user domain.User - err := userRepo.cache.Get(ctx, cacheKey, &user) - if err == nil { + + if err := repo.cache.Get(ctx, cacheKey, &user); err == nil { return user, nil } - err = userRepo.collection.FindOne(ctx, bson.M{"email": email}).Decode(&user) - if err != nil { - if err == mongo.ErrNoDocuments { + if err := repo.collection.FindOne(ctx, bson.M{"email": email}).Decode(&user); err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { return domain.User{}, errors.New("user not found") } return domain.User{}, err } - userRepo.cache.Set(ctx, cacheKey, user, 1*time.Hour) + repo.cache.Set(ctx, cacheKey, user, 1*time.Hour) return user, nil } -func (userRepo *userRepository) AddBlog(ctx context.Context, userID int, blog domain.Blog) (domain.User, error) { +func (repo *UserRepository) AddBlog(ctx context.Context, userID int, blog domain.Blog) (domain.User, error) { var user domain.User - err := userRepo.collection.FindOne(ctx, bson.M{"id": userID}).Decode(&user) - if err != nil { - if err == mongo.ErrNoDocuments { + if err := repo.collection.FindOne(ctx, bson.M{"id": userID}).Decode(&user); err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { return domain.User{}, errors.New("user not found") } return domain.User{}, err } user.Blogs = append(user.Blogs, blog.ID) - _, err = userRepo.collection.UpdateOne(ctx, bson.M{"id": userID}, bson.M{"$set": bson.M{"blogs": user.Blogs}}) - if err != nil { + if _, err := repo.collection.UpdateOne(ctx, bson.M{"id": userID}, bson.M{"$set": bson.M{"blogs": user.Blogs}}); err != nil { return domain.User{}, err } - // Invalidate the cache for this user and related cache keys - cacheKey := fmt.Sprintf("user:%d", userID) - userRepo.cache.Del(ctx, cacheKey) - - // Invalidate the cache for all users if relevant - userRepo.cache.Del(ctx, "users:all") - + repo.invalidateUserCache(ctx, userID, user) return user, nil } -func (userRepo *userRepository) StoreRefreshToken(ctx context.Context, userID int, refreshToken string) error { - _, err := userRepo.collection.UpdateOne(ctx, bson.M{"id": userID}, bson.M{"$set": bson.M{"refresh_token": refreshToken}}) + +func (repo *UserRepository) StoreRefreshToken(ctx context.Context, userID int, refreshToken string) error { + _, err := repo.collection.UpdateOne(ctx, bson.M{"id": userID}, bson.M{"$set": bson.M{"refresh_token": refreshToken}}) return err } -func (userRepo *userRepository) ValidateRefreshToken(ctx context.Context, userID int, refreshToken string) (bool, error) { +func (repo *UserRepository) ValidateRefreshToken(ctx context.Context, userID int, refreshToken string) (bool, error) { var user domain.User - err := userRepo.collection.FindOne(ctx, bson.M{"id": userID, "refresh_token": refreshToken}).Decode(&user) + err := repo.collection.FindOne(ctx, bson.M{"id": userID, "refresh_token": refreshToken}).Decode(&user) if err != nil { - if err == mongo.ErrNoDocuments { + if errors.Is(err, mongo.ErrNoDocuments) { return false, nil } return false, err @@ -242,19 +196,16 @@ func (userRepo *userRepository) ValidateRefreshToken(ctx context.Context, userID return true, nil } -func (userRepo *userRepository) GetRefreshToken(ctx context.Context, userID int) (string, error) { +func (repo *UserRepository) GetRefreshToken(ctx context.Context, userID int) (string, error) { var user map[string]interface{} - // Find the user document with the given ID and decode it into a map - err := userRepo.collection.FindOne(ctx, bson.M{"id": userID}).Decode(&user) - if err != nil { - if err == mongo.ErrNoDocuments { + if err := repo.collection.FindOne(ctx, bson.M{"id": userID}).Decode(&user); err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { return "", errors.New("user not found") } return "", err } - // Extract the refresh token from the user document refreshToken, ok := user["refresh_token"].(string) if !ok { return "", errors.New("refresh token not found or invalid") @@ -262,3 +213,16 @@ func (userRepo *userRepository) GetRefreshToken(ctx context.Context, userID int) return refreshToken, nil } + +func (repo *UserRepository) invalidateUserCache(ctx context.Context, userID int, user domain.User) { + repo.cache.Del(ctx, fmt.Sprintf("user:%d", userID)) + + if user.Username != "" { + repo.cache.Del(ctx, fmt.Sprintf("user:username:%s", user.Username)) + } + if user.Email != "" { + repo.cache.Del(ctx, fmt.Sprintf("user:email:%s", user.Email)) + } + + repo.cache.Del(ctx, "users:all") +} diff --git a/backend/AAiT-backend-group-7/blog_project/usecases/blog_usecases.go b/backend/AAiT-backend-group-7/blog_project/usecases/blog_usecases.go index 741967854..e89a9a26e 100644 --- a/backend/AAiT-backend-group-7/blog_project/usecases/blog_usecases.go +++ b/backend/AAiT-backend-group-7/blog_project/usecases/blog_usecases.go @@ -6,6 +6,8 @@ import ( "errors" "sort" "time" + + "github.com/dgrijalva/jwt-go" ) type BlogUsecases struct { @@ -14,11 +16,11 @@ type BlogUsecases struct { UserUsecase domain.IUserUsecase } -func NewBlogUsecase(aiService domain.AiService, blogrepo domain.IBlogRepository, userusecase domain.IUserUsecase) domain.IBlogUsecase { +func NewBlogUsecase(aiService domain.AiService, blogRepo domain.IBlogRepository, userUsecase domain.IUserUsecase) domain.IBlogUsecase { return &BlogUsecases{ AiService: aiService, - BlogRepo: blogrepo, - UserUsecase: userusecase, + BlogRepo: blogRepo, + UserUsecase: userUsecase, } } @@ -42,15 +44,12 @@ func (u *BlogUsecases) GetAllBlogs(ctx context.Context, sortOrder string, page, blogWithPopularityList[i] = blogWithPopularity{Blog: blog, Popularity: popularity} } - if sortOrder == "ASC" { - sort.Slice(blogWithPopularityList, func(i, j int) bool { + sort.Slice(blogWithPopularityList, func(i, j int) bool { + if sortOrder == "ASC" { return blogWithPopularityList[i].Popularity < blogWithPopularityList[j].Popularity - }) - } else { - sort.Slice(blogWithPopularityList, func(i, j int) bool { - return blogWithPopularityList[i].Popularity > blogWithPopularityList[j].Popularity - }) - } + } + return blogWithPopularityList[i].Popularity > blogWithPopularityList[j].Popularity + }) sortedBlogs := make([]domain.Blog, len(blogs)) for i, bw := range blogWithPopularityList { @@ -65,16 +64,14 @@ func (u *BlogUsecases) GetBlogByID(ctx context.Context, id int) (domain.Blog, er } func (u *BlogUsecases) CreateBlog(ctx context.Context, blog domain.Blog) (domain.Blog, error) { - blog.ID = generateUniqueID() // Generate a new unique ID + blog.ID = generateUniqueID() user, err := u.UserUsecase.GetUserByUsername(ctx, blog.Author) if err != nil { return domain.Blog{}, err } - authorID := user.ID - - _, err = u.UserUsecase.AddBlog(ctx, authorID, blog) + _, err = u.UserUsecase.AddBlog(ctx, user.ID, blog) if err != nil { return domain.Blog{}, err } @@ -83,23 +80,30 @@ func (u *BlogUsecases) CreateBlog(ctx context.Context, blog domain.Blog) (domain } func (u *BlogUsecases) UpdateBlog(ctx context.Context, id int, updatedBlog domain.Blog) (domain.Blog, error) { + claims, ok := ctx.Value("user").(jwt.MapClaims) + if !ok { + return domain.Blog{}, errors.New("failed to get user claims from context") + } + + username := claims["username"].(string) existingBlog, err := u.BlogRepo.GetBlogByID(ctx, id) if err != nil { return domain.Blog{}, err } + if username != existingBlog.Author { + return domain.Blog{}, errors.New("you are not authorized to update this blog") + } + if updatedBlog.Title != "" { existingBlog.Title = updatedBlog.Title } - if updatedBlog.Content != "" { existingBlog.Content = updatedBlog.Content } - if !updatedBlog.Date.IsZero() { existingBlog.Date = updatedBlog.Date } - if len(updatedBlog.Tags) > 0 { existingBlog.Tags = updatedBlog.Tags } @@ -108,6 +112,14 @@ func (u *BlogUsecases) UpdateBlog(ctx context.Context, id int, updatedBlog domai } func (u *BlogUsecases) DeleteBlog(ctx context.Context, id int) error { + claims, ok := ctx.Value("user").(jwt.MapClaims) + if !ok { + return errors.New("failed to get user claims from context") + } + + username := claims["username"].(string) + userRole := claims["role"].(string) + blog, err := u.BlogRepo.GetBlogByID(ctx, id) if err != nil { return err @@ -118,18 +130,21 @@ func (u *BlogUsecases) DeleteBlog(ctx context.Context, id int) error { return err } + if userRole != "admin" && blog.Author != username { + return errors.New("you are not authorized to delete this blog") + } + u.UserUsecase.DeleteBlog(ctx, user.ID, id) return u.BlogRepo.DeleteBlog(ctx, id) } func (u *BlogUsecases) Search(ctx context.Context, author string, tags []string, title string) ([]domain.Blog, error) { + blogMap := make(map[int]int) var results []domain.Blog var tempResults []domain.Blog var err error - blogMap := make(map[int]int) - if author != "" { tempResults, err = u.BlogRepo.SearchByAuthor(ctx, author) if err != nil { @@ -184,22 +199,29 @@ func (u *BlogUsecases) Search(ctx context.Context, author string, tags []string, return results, nil } -func (u *BlogUsecases) LikeBlog(ctx context.Context, blogID int, authorID int) (domain.Blog, error) { +func (u *BlogUsecases) LikeBlog(ctx context.Context, blogID int) (domain.Blog, error) { blog, err := u.BlogRepo.GetBlogByID(ctx, blogID) if err != nil { return domain.Blog{}, err } + claims, ok := ctx.Value("user").(jwt.MapClaims) + if !ok { + return domain.Blog{}, errors.New("failed to get user claims from context") + } + + user := claims["username"].(string) + for _, like := range blog.Likes { - if like.UserID == authorID { + if like.User == user { return domain.Blog{}, errors.New("user already liked this blog") } } newLike := domain.Like{ - ID: len(blog.Likes) + 1, - UserID: authorID, - Date: time.Now(), + ID: len(blog.Likes) + 1, + User: user, + Date: time.Now(), } blog.Likes = append(blog.Likes, newLike) @@ -211,22 +233,29 @@ func (u *BlogUsecases) LikeBlog(ctx context.Context, blogID int, authorID int) ( return blog, nil } -func (u *BlogUsecases) DislikeBlog(ctx context.Context, blogID int, authorID int) (domain.Blog, error) { +func (u *BlogUsecases) DislikeBlog(ctx context.Context, blogID int) (domain.Blog, error) { blog, err := u.BlogRepo.GetBlogByID(ctx, blogID) if err != nil { return domain.Blog{}, err } + claims, ok := ctx.Value("user").(jwt.MapClaims) + if !ok { + return domain.Blog{}, errors.New("failed to get user claims from context") + } + + user := claims["username"].(string) + for _, dislike := range blog.Dislikes { - if dislike.UserID == authorID { + if dislike.User == user { return domain.Blog{}, errors.New("user already disliked this blog") } } newDislike := domain.Dislike{ - ID: len(blog.Dislikes) + 1, - UserID: authorID, - Date: time.Now(), + ID: len(blog.Dislikes) + 1, + User: user, + Date: time.Now(), } blog.Dislikes = append(blog.Dislikes, newDislike) @@ -238,15 +267,22 @@ func (u *BlogUsecases) DislikeBlog(ctx context.Context, blogID int, authorID int return blog, nil } -func (u *BlogUsecases) AddComent(ctx context.Context, blogID int, authorID int, content string) (domain.Blog, error) { +func (u *BlogUsecases) AddComment(ctx context.Context, blogID int, content string) (domain.Blog, error) { blog, err := u.BlogRepo.GetBlogByID(ctx, blogID) if err != nil { return domain.Blog{}, err } + claims, ok := ctx.Value("user").(jwt.MapClaims) + if !ok { + return domain.Blog{}, errors.New("failed to get user claims from context") + } + + user := claims["username"].(string) + newComment := domain.Comment{ ID: len(blog.Comments) + 1, - UserID: authorID, + User: user, Content: content, Date: time.Now(), } diff --git a/backend/AAiT-backend-group-7/blog_project/usecases/user_usecases.go b/backend/AAiT-backend-group-7/blog_project/usecases/user_usecases.go index f975f9e8b..6b4ca1ae2 100644 --- a/backend/AAiT-backend-group-7/blog_project/usecases/user_usecases.go +++ b/backend/AAiT-backend-group-7/blog_project/usecases/user_usecases.go @@ -10,6 +10,8 @@ import ( "regexp" "sync/atomic" "time" + + "github.com/dgrijalva/jwt-go" ) type UserUsecase struct { @@ -19,12 +21,17 @@ type UserUsecase struct { TokenRepo domain.ITokenRepository } -func NewUserUsecase(userRepo domain.IUserRepository, blogRepo domain.IBlogRepository, emailService *infrastructure.EmailService, tokenRepo domain.ITokenRepository) domain.IUserUsecase { +func NewUserUsecase( + userRepo domain.IUserRepository, + blogRepo domain.IBlogRepository, + emailService *infrastructure.EmailService, + tokenRepo domain.ITokenRepository, +) domain.IUserUsecase { return &UserUsecase{ emailService: emailService, UserRepo: userRepo, - TokenRepo: tokenRepo, BlogRepo: blogRepo, + TokenRepo: tokenRepo, } } @@ -35,66 +42,47 @@ func (u *UserUsecase) GetAllUsers(ctx context.Context) ([]domain.User, error) { func (u *UserUsecase) GetUserByID(ctx context.Context, id int) (domain.User, error) { user, err := u.UserRepo.GetUserByID(ctx, id) if err != nil { - return domain.User{}, errors.New(err.Error()) + return domain.User{}, err } - return user, nil } func (u *UserUsecase) GetUserByUsername(ctx context.Context, username string) (domain.User, error) { user, err := u.UserRepo.SearchByUsername(ctx, username) if err != nil { - return domain.User{}, errors.New(err.Error()) + return domain.User{}, err } - return user, nil } func (u *UserUsecase) CreateUser(ctx context.Context, user domain.User) (domain.User, error) { - existingUser, _ := u.UserRepo.SearchByEmail(ctx, user.Email) - // if err != nil { - // return domain.User{}, errors.New(err.Error()) - // } - if existingUser.ID != 0 { + if existingUser, _ := u.UserRepo.SearchByEmail(ctx, user.Email); existingUser.ID != 0 { return domain.User{}, errors.New("email already in use") } - existingUser, _ = u.UserRepo.SearchByUsername(ctx, user.Username) - // if err != nil { - // return domain.User{}, errors.New(err.Error()) - // } - if existingUser.ID != 0 { + if existingUser, _ := u.UserRepo.SearchByUsername(ctx, user.Username); existingUser.ID != 0 { return domain.User{}, errors.New("username already in use") } if !isValidEmail(user.Email) { return domain.User{}, errors.New("invalid email") - } if !isValidPassword(user.Password) { - return domain.User{}, errors.New("invalid password, must contain at least one uppercase letter, one lowercase letter, one number, one special character, and minimum length of 8") + return domain.User{}, errors.New("invalid password, must contain at least one uppercase letter, one lowercase letter, one number, one special character, and a minimum length of 8") } hashedPassword, err := infrastructure.HashPassword(user.Password) if err != nil { - return domain.User{}, errors.New(err.Error()) - } - - if user.Username == "" { - return domain.User{}, errors.New("username is required") + return domain.User{}, err } user.Password = hashedPassword - user.ID = generateUniqueID() - users, err := u.GetAllUsers(ctx) - if err != nil { - return domain.User{}, errors.New(err.Error()) - } - - if len(users) == 0 { + if users, err := u.GetAllUsers(ctx); err != nil { + return domain.User{}, err + } else if len(users) == 0 { user.Role = "admin" } else { user.Role = "user" @@ -104,9 +92,21 @@ func (u *UserUsecase) CreateUser(ctx context.Context, user domain.User) (domain. } func (u *UserUsecase) UpdateUser(ctx context.Context, id int, user domain.User) (domain.User, error) { + claims, ok := ctx.Value("user").(jwt.MapClaims) + if !ok { + return domain.User{}, errors.New("failed to get user claims from context") + } + + userID := int(claims["id"].(float64)) + userRole := claims["role"].(string) + + if userRole != "admin" && userID != id { + return domain.User{}, errors.New("you are not authorized to update this user") + } + existingUser, err := u.UserRepo.GetUserByID(ctx, id) if err != nil { - return domain.User{}, errors.New(err.Error()) + return domain.User{}, err } if user.Email != "" { @@ -118,12 +118,12 @@ func (u *UserUsecase) UpdateUser(ctx context.Context, id int, user domain.User) if user.Password != "" { if !isValidPassword(user.Password) { - return domain.User{}, errors.New("invalid password, must contain at least one uppercase letter, one lowercase letter, one number, one special character, and minimum length of 8") + return domain.User{}, errors.New("invalid password, must contain at least one uppercase letter, one lowercase letter, one number, one special character, and a minimum length of 8") } hashedPassword, err := infrastructure.HashPassword(user.Password) if err != nil { - return domain.User{}, errors.New(err.Error()) + return domain.User{}, err } existingUser.Password = hashedPassword } @@ -145,26 +145,41 @@ func (u *UserUsecase) UpdateUser(ctx context.Context, id int, user domain.User) existingUser.ProfilePic = user.ProfilePic } + if user.Role != "" { + existingUser.Role = user.Role + } + return u.UserRepo.UpdateUser(ctx, id, existingUser) } func (u *UserUsecase) DeleteUser(ctx context.Context, id int) error { + claims, ok := ctx.Value("user").(jwt.MapClaims) + if !ok { + return errors.New("failed to get user claims from context") + } + + userID := int(claims["id"].(float64)) + userRole := claims["role"].(string) + + if userRole != "admin" && userID != id { + return errors.New("you are not authorized to delete this user") + } + return u.UserRepo.DeleteUser(ctx, id) } func (u *UserUsecase) AddBlog(ctx context.Context, userID int, blog domain.Blog) (domain.User, error) { - return u.UserRepo.AddBlog(ctx, userID, blog) } -func (u *UserUsecase) DeleteBlog(ctx context.Context, userID int, blogID int) (domain.User, error) { +func (u *UserUsecase) DeleteBlog(ctx context.Context, userID, blogID int) (domain.User, error) { user, err := u.UserRepo.GetUserByID(ctx, userID) if err != nil { - return domain.User{}, errors.New(err.Error()) + return domain.User{}, err } - for i, blog := range user.Blogs { - if blog == blogID { + for i, b := range user.Blogs { + if b == blogID { user.Blogs = append(user.Blogs[:i], user.Blogs[i+1:]...) break } @@ -179,24 +194,21 @@ func (u *UserUsecase) Login(ctx context.Context, username, password string) (str return "", "", errors.New("invalid credentials") } - err = infrastructure.ComparePassword(user.Password, password) - if err != nil { + if err := infrastructure.ComparePassword(user.Password, password); err != nil { return "", "", errors.New("invalid credentials") } - token, err := infrastructure.GenerateJWTAccessToken(&user, os.Getenv("jwt_secret"), 1) + token, err := infrastructure.GenerateJWTAccessToken(&user, os.Getenv("JWT_SECRET"), 1) if err != nil { return "", "", err } - refreshToken, err := infrastructure.GenerateJWTRefreshToken(&user, os.Getenv("jwt_secret"), 5) - + refreshToken, err := infrastructure.GenerateJWTRefreshToken(&user, os.Getenv("JWT_SECRET"), 5) if err != nil { return "", "", err } - err = u.UserRepo.StoreRefreshToken(ctx, user.ID, refreshToken) - if err != nil { + if err := u.UserRepo.StoreRefreshToken(ctx, user.ID, refreshToken); err != nil { return "", "", err } @@ -204,37 +216,36 @@ func (u *UserUsecase) Login(ctx context.Context, username, password string) (str } func (u *UserUsecase) RefreshToken(ctx context.Context, refreshToken string) (string, error) { - validatedToken, err := infrastructure.IsAuthorized(refreshToken, os.Getenv("jwt_secret")) + validatedToken, err := infrastructure.IsAuthorized(refreshToken, os.Getenv("JWT_SECRET")) if err != nil { return "", errors.New("invalid token") } - // Convert the id from float64 to int userID, ok := validatedToken["id"].(float64) if !ok { return "", errors.New("invalid token ID type") } - // Convert float64 to int user, err := u.UserRepo.GetUserByID(ctx, int(userID)) if err != nil { return "", errors.New("user not found") } - newToken, _ := infrastructure.GenerateJWTAccessToken(&user, os.Getenv("jwt_secret"), 1) + newToken, err := infrastructure.GenerateJWTAccessToken(&user, os.Getenv("JWT_SECRET"), 1) + if err != nil { + return "", err + } return newToken, nil } -// RequestPasswordReset handles the logic for initiating a password reset func (u *UserUsecase) ForgetPassword(ctx context.Context, email string) error { user, err := u.UserRepo.SearchByEmail(ctx, email) if err != nil { return errors.New("user not found") } - resetToken, err := infrastructure.GenerateJWTRefreshToken(&user, os.Getenv("jwt_secret"), 1) - + resetToken, err := infrastructure.GenerateJWTRefreshToken(&user, os.Getenv("JWT_SECRET"), 1) if err != nil { return err } @@ -246,10 +257,8 @@ func (u *UserUsecase) ForgetPassword(ctx context.Context, email string) error { return nil } -// ResetPassword handles the logic for resetting the password func (u *UserUsecase) ResetPassword(ctx context.Context, token, newPassword string) error { - claims, err := infrastructure.IsAuthorized(token, os.Getenv("jwt_secret")) - + claims, err := infrastructure.IsAuthorized(token, os.Getenv("JWT_SECRET")) if err != nil { return errors.New("invalid token") } @@ -280,40 +289,33 @@ func (u *UserUsecase) ResetPassword(ctx context.Context, token, newPassword stri func (u *UserUsecase) PromoteUser(ctx context.Context, userID int) (domain.User, error) { user, err := u.UserRepo.GetUserByID(ctx, userID) - if err != nil { - return domain.User{}, nil + return domain.User{}, err } user.Role = "admin" - u.UpdateUser(ctx, user.ID, user) - - return user, nil + return u.UpdateUser(ctx, user.ID, user) } func (u *UserUsecase) DemoteUser(ctx context.Context, userID int) (domain.User, error) { user, err := u.UserRepo.GetUserByID(ctx, userID) - if err != nil { - return domain.User{}, nil + return domain.User{}, err } user.Role = "user" - u.UpdateUser(ctx, user.ID, user) - - return user, nil + return u.UpdateUser(ctx, user.ID, user) } func (u *UserUsecase) Logout(ctx context.Context, token string) error { - decodedToken, err := infrastructure.IsAuthorized(token, os.Getenv("jwt_secret")) + decodedToken, err := infrastructure.IsAuthorized(token, os.Getenv("JWT_SECRET")) if err != nil { return fmt.Errorf("invalid token: %v", err) } - err = u.TokenRepo.BlacklistToken(ctx, token) - if err != nil { + if err := u.TokenRepo.BlacklistToken(ctx, token); err != nil { return err } @@ -324,24 +326,15 @@ func (u *UserUsecase) Logout(ctx context.Context, token string) error { return err } - err = u.TokenRepo.BlacklistToken(ctx, refreshToken) - if err != nil { - return err - } - - return nil - + return u.TokenRepo.BlacklistToken(ctx, refreshToken) } -// Email validation function func isValidEmail(email string) bool { - // Regex pattern for valid email format const emailRegex = `^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$` re := regexp.MustCompile(emailRegex) return re.MatchString(email) } -// Password strength validation function func isValidPassword(password string) bool { if len(password) < 8 { return false @@ -358,15 +351,11 @@ func isValidPassword(password string) bool { var counter int32 func generateUniqueID() int { - // Use a larger portion of the timestamp - timestamp := int(time.Now().UnixNano() / 1e6 % 1e6) // Last 6 digits - - // Combine with counter + timestamp := int(time.Now().UnixNano() / 1e6 % 1e6) uniqueID := timestamp*1000 + int(atomic.AddInt32(&counter, 1)%1000) - // Ensure uniqueID fits within a 32-bit integer - if uniqueID > 2147483647 { // Max int32 value - uniqueID = uniqueID % 1000000 + if uniqueID > 2147483647 { + uniqueID %= 1000000 } return uniqueID From 99ab1a1ce98cc64defb34c9dd721792d2ee0c872 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Tue, 27 Aug 2024 19:45:42 +0300 Subject: [PATCH 3/3] aait.backend.g7.mohammed-bug-fixes-and-refactoring --- .../delivery/controllers/blog_controller.go | 8 +- .../delivery/controllers/user_controller.go | 14 +- .../blog_project/delivery/routers/router.go | 20 +- .../blog_project/docs/api_documentation.md | 661 ++++++------------ .../blog_project/usecases/blog_usecases.go | 12 +- 5 files changed, 250 insertions(+), 465 deletions(-) diff --git a/backend/AAiT-backend-group-7/blog_project/delivery/controllers/blog_controller.go b/backend/AAiT-backend-group-7/blog_project/delivery/controllers/blog_controller.go index 892991317..9f961051e 100644 --- a/backend/AAiT-backend-group-7/blog_project/delivery/controllers/blog_controller.go +++ b/backend/AAiT-backend-group-7/blog_project/delivery/controllers/blog_controller.go @@ -53,7 +53,7 @@ func (bc *BlogController) CreateBlog(c *gin.Context) { } func (bc *BlogController) UpdateBlog(c *gin.Context) { - id, err := strconv.Atoi(c.Query("id")) + id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": "Invalid blog ID"}) return @@ -74,7 +74,7 @@ func (bc *BlogController) UpdateBlog(c *gin.Context) { } func (bc *BlogController) DeleteBlog(c *gin.Context) { - id, err := strconv.Atoi(c.Query("id")) + id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": "Invalid blog ID"}) return @@ -103,7 +103,7 @@ func (bc *BlogController) LikeBlog(c *gin.Context) { } func (bc *BlogController) DislikeBlog(c *gin.Context) { - blogID, err := strconv.Atoi(c.Query("id")) + blogID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": "Invalid blog ID"}) return @@ -118,7 +118,7 @@ func (bc *BlogController) DislikeBlog(c *gin.Context) { } func (bc *BlogController) AddComment(c *gin.Context) { - blogID, err := strconv.Atoi(c.Query("id")) + blogID, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": "Invalid blog ID"}) return diff --git a/backend/AAiT-backend-group-7/blog_project/delivery/controllers/user_controller.go b/backend/AAiT-backend-group-7/blog_project/delivery/controllers/user_controller.go index 9da24f44d..db03e7bc8 100644 --- a/backend/AAiT-backend-group-7/blog_project/delivery/controllers/user_controller.go +++ b/backend/AAiT-backend-group-7/blog_project/delivery/controllers/user_controller.go @@ -28,7 +28,7 @@ func (uc *UserController) GetAllUsers(c *gin.Context) { } func (uc *UserController) GetUserByID(c *gin.Context) { - id, err := strconv.Atoi(c.Query("id")) + id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -81,7 +81,7 @@ func (uc *UserController) CreateUser(c *gin.Context) { } func (uc *UserController) UpdateUser(c *gin.Context) { - id, err := strconv.Atoi(c.Query("id")) + id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -125,7 +125,7 @@ func (uc *UserController) UpdateUser(c *gin.Context) { } func (uc *UserController) DeleteUser(c *gin.Context) { - id, err := strconv.Atoi(c.Query("id")) + id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -185,8 +185,8 @@ func (uc *UserController) ForgetPassword(c *gin.Context) { } func (uc *UserController) ResetPassword(c *gin.Context) { - username := c.Query("username") - password := c.Query("password") + username := c.Param("username") + password := c.Param("password") if err := uc.userUsecase.ResetPassword(c, username, password); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -195,7 +195,7 @@ func (uc *UserController) ResetPassword(c *gin.Context) { } func (uc *UserController) PromoteUser(c *gin.Context) { - id, err := strconv.Atoi(c.Query("id")) + id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return @@ -210,7 +210,7 @@ func (uc *UserController) PromoteUser(c *gin.Context) { } func (uc *UserController) DemoteUser(c *gin.Context) { - id, err := strconv.Atoi(c.Query("id")) + id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(400, gin.H{"error": err.Error()}) return diff --git a/backend/AAiT-backend-group-7/blog_project/delivery/routers/router.go b/backend/AAiT-backend-group-7/blog_project/delivery/routers/router.go index b3d48f158..743cf1c2e 100644 --- a/backend/AAiT-backend-group-7/blog_project/delivery/routers/router.go +++ b/backend/AAiT-backend-group-7/blog_project/delivery/routers/router.go @@ -17,12 +17,12 @@ func SetupRouter(blogController domain.IBlogController, userController domain.IU { blogs.GET("/", blogController.GetAllBlogs) blogs.POST("/", blogController.CreateBlog) - blogs.PUT("/", blogController.UpdateBlog) - blogs.DELETE("/", blogController.DeleteBlog) + blogs.PUT("/:id", blogController.UpdateBlog) + blogs.DELETE("/:id", blogController.DeleteBlog) - blogs.POST("/comment", blogController.AddComment) - blogs.POST("/like", blogController.LikeBlog) - blogs.POST("/dislike", blogController.DislikeBlog) + blogs.POST("/comment/:id", blogController.AddComment) + blogs.POST("/like/:id", blogController.LikeBlog) + blogs.POST("/dislike/:id", blogController.DislikeBlog) blogs.POST("/search", blogController.Search) blogs.POST("/generate-content", blogController.AiRecommendation) } @@ -39,11 +39,11 @@ func SetupRouter(blogController domain.IBlogController, userController domain.IU users.Use(infrastructure.JwtAuthMiddleware(os.Getenv("jwt_secret"))) { users.GET("/", infrastructure.AdminMiddleware(), userController.GetAllUsers) - users.GET("/user", infrastructure.AdminMiddleware(), userController.GetUserByID) - users.PUT("/", userController.UpdateUser) - users.DELETE("/", userController.DeleteUser) - users.POST("/promote", infrastructure.AdminMiddleware(), userController.PromoteUser) - users.POST("/demote", infrastructure.AdminMiddleware(), userController.DemoteUser) + users.GET("/:id", infrastructure.AdminMiddleware(), userController.GetUserByID) + users.PUT("/:id", userController.UpdateUser) + users.DELETE("/:id", userController.DeleteUser) + users.POST("/promote/:id", infrastructure.AdminMiddleware(), userController.PromoteUser) + users.POST("/demote/:id", infrastructure.AdminMiddleware(), userController.DemoteUser) } // Upload routes diff --git a/backend/AAiT-backend-group-7/blog_project/docs/api_documentation.md b/backend/AAiT-backend-group-7/blog_project/docs/api_documentation.md index 0b9ec1bcb..18b126df2 100644 --- a/backend/AAiT-backend-group-7/blog_project/docs/api_documentation.md +++ b/backend/AAiT-backend-group-7/blog_project/docs/api_documentation.md @@ -1,441 +1,222 @@ -# Blog Platform API Documentation +### Blog Application API + +This project is a blog platform API built with Go using the Gin web framework. It allows users to create, manage, and interact with blog posts. The platform supports user authentication, blog management, and interactions like commenting, liking, and disliking posts. Additionally, the system integrates AI-based content recommendations. + +### Running the Project + +To run the project, follow these steps: + +1. Start MongoDB: + ```bash + mongod + ``` +2. (Optional) Start Redis if your setup includes caching or token management with Redis: + ```bash + redis-server + ``` +3. Navigate to the `delivery` directory: + ```bash + cd delivery + ``` +4. Run the application: + ```bash + go run . + ``` + +### API Documentation + +#### **Users API** + +**Base Path:** `/users` + +1. **Get All Users** + - **Endpoint:** `GET /users` + - **Description:** Retrieves a list of all users. + - **Response:** JSON array of user objects. + +2. **Get User by ID** + - **Endpoint:** `GET /users/:id` + - **Description:** Retrieves details of a specific user by ID. + - **Parameters:** + - `id` (path): User ID. + - **Response:** JSON object of the user. + +3. **Create User** + - **Endpoint:** `POST /users` + - **Description:** Creates a new user. + - **Request Type:** `multipart/form-data` + - **Form Data Fields:** + - `username`: (string) Required. + - `password`: (string) Required. + - `email`: (string) Required. + - `bio`: (string) Optional. + - `phone`: (string) Optional. + - `profile_pic`: (file) Optional. + - **Response:** JSON object of the newly created user. + +4. **Update User** + - **Endpoint:** `PUT /users/:id` + - **Description:** Updates the details of an existing user. + - **Parameters:** + - `id` (path): User ID. + - **Request Type:** `multipart/form-data` + - **Form Data Fields:** + - `username`: (string) Optional. + - `password`: (string) Optional. + - `email`: (string) Optional. + - `bio`: (string) Optional. + - `phone`: (string) Optional. + - `profile_pic`: (file) Optional. + - **Response:** JSON object of the updated user. + +5. **Delete User** + - **Endpoint:** `DELETE /users/:id` + - **Description:** Deletes a user by ID. + - **Parameters:** + - `id` (path): User ID. + - **Response:** JSON message confirming deletion. + +6. **Login** + - **Endpoint:** `POST /users/login` + - **Description:** Authenticates a user and returns an access token and a refresh token. + - **Request Type:** `application/json` + - **Request Body Fields:** + - `username`: (string) Required. + - `password`: (string) Required. + - **Response:** JSON object containing `access_token`, `refresh_token`, and user details. + +7. **Logout** + - **Endpoint:** `POST /users/logout` + - **Description:** Logs out a user by invalidating the provided token. + - **Request Type:** `application/json` + - **Request Body Fields:** + - `token`: (string) Required. + - **Response:** JSON message confirming logout. + +8. **Forget Password** + - **Endpoint:** `GET /users/forget-password` + - **Description:** Sends a password reset link to the user's email. + - **Query Parameters:** + - `email`: (string) Required. + - **Response:** JSON message confirming the email was sent. + +9. **Reset Password** + - **Endpoint:** `PUT /users/reset-password/:username/:password` + - **Description:** Resets the user's password. + - **Parameters:** + - `username` (path): User's username. + - `password` (path): New password. + - **Response:** JSON message confirming the password reset. + +10. **Promote User** + - **Endpoint:** `PUT /users/promote/:id` + - **Description:** Promotes a user to a higher role. + - **Parameters:** + - `id` (path): User ID. + - **Response:** JSON message confirming promotion. + +11. **Demote User** + - **Endpoint:** `PUT /users/demote/:id` + - **Description:** Demotes a user to a lower role. + - **Parameters:** + - `id` (path): User ID. + - **Response:** JSON message confirming demotion. + +12. **Refresh Token** + - **Endpoint:** `POST /users/refresh-token` + - **Description:** Refreshes the access token using a valid refresh token. + - **Request Type:** `application/json` + - **Request Body Fields:** + - `refresh_token`: (string) Required. + - **Response:** JSON object with the new access token. + +#### **Blogs API** + +**Base Path:** `/blogs` + +1. **Get All Blogs** + - **Endpoint:** `GET /blogs` + - **Description:** Retrieves a paginated list of all blogs. + - **Query Parameters:** + - `page`: (int) Optional. Default is 1. + - `limit`: (int) Optional. Default is 10. + - `sortOrder`: (string) Optional. Can be `asc` or `desc`. + - **Response:** JSON array of blog objects. + +2. **Get Blog by ID** + - **Endpoint:** `GET /blogs/:id` + - **Description:** Retrieves details of a specific blog by ID. + - **Parameters:** + - `id` (path): Blog ID. + - **Response:** JSON object of the blog. + +3. **Create Blog** + - **Endpoint:** `POST /blogs` + - **Description:** Creates a new blog. + - **Request Type:** `application/json` + - **Request Body Fields:** + - `title`: (string) Optional. + - `content`: (string) Optional. + - `tags`: (array of strings) Optional. + - **Response:** JSON object of the newly created blog. + +4. **Update Blog** + - **Endpoint:** `PUT /blogs/:id` + - **Description:** Updates an existing blog. + - **Parameters:** + - `id` (path): Blog ID. + - **Request Type:** `application/json` + - **Request Body Fields:** + - `title`: (string) Optional. + - `content`: (string) Optional. + - `tags`: (array of strings) Optional. + - **Response:** JSON object of the updated blog. + +5. **Delete Blog** + - **Endpoint:** `DELETE /blogs/:id` + - **Description:** Deletes a blog by ID. + - **Parameters:** + - `id` (path): Blog ID. + - **Response:** JSON message confirming deletion. + +6. **Add Comment** + - **Endpoint:** `POST /blogs/comments/:id` + - **Description:** Adds a comment to a blog. + - **Parameters:** + - `id` (path): Blog ID. + - **Request Type:** `application/json` + - **Request Body Fields:** + - `content`: (string) Required. + - **Response:** JSON object of the blog with the new comment. + +7. **Like Blog** + - **Endpoint:** `POST /blogs/like/:id` + - **Description:** Likes a blog. + - **Parameters:** + - `id` (path): Blog ID. + - **Response:** JSON object of the blog with the updated likes. + +8. **Dislike Blog** + - **Endpoint:** `POST /blogs/dislike/:id` + - **Description:** Dislikes a blog. + - **Parameters:** + - `id` (path): Blog ID. + - **Response:** JSON object of the blog with the updated dislikes. + +9. **Search Blogs** + - **Endpoint:** `GET /blogs/search` + - **Description:** Searches blogs by title, tags, or author. + - **Query Parameters:** + - `title`: (string) Optional. + - `tags`: (array of strings) Optional. + - `author`: (string) Optional. + - **Response:** JSON array of blogs matching the search criteria. + +10. **AI Recommendation** + - **Endpoint:** `POST /blogs/recommendation` + - **Description:** Generates AI-based content recommendations based on the given content. + - **Request Type:** `application/json` + - **Request Body Fields:** + - `content`: (string) Required. + - **Response:** JSON object with the AI-generated recommendation. -This documentation outlines the API endpoints for managing blogs and users on the Blog Platform. The platform supports various operations such as creating, retrieving, updating, and deleting blogs, as well as advanced functionalities like commenting, liking, disliking, and AI-generated content recommendations. - -## Base URL - -``` -http://localhost:8080 -``` - -## Authentication - -The API uses JWT for authentication. Most endpoints require a valid JWT token included in the `Authorization` header of the request. - ---- - -## Blogs - -### 1. Get All Blogs - -**Endpoint:** -`GET /blogs` - -**Query Parameters:** - -- `sort` (optional): Defines the sort order of the blogs. Default is `DESC` for descending order. Acceptable values: `ASC` (ascending), `DESC` (descending). -- `page` (optional): Specifies the page number for pagination. Default is `1`. -- `limit` (optional): Determines the number of blogs returned per page. Default is `10`. - -**Description:** -Retrieves a list of blogs with options for sorting and pagination. The response includes metadata such as total pages and total number of blogs. - -**Response:** - -- `200 OK`: Successfully retrieved the list of blogs. -- `400 Bad Request`: Invalid query parameters provided. - ---- - -### 2. Create a Blog - -**Endpoint:** -`POST /blogs` - -**Description:** -Creates a new blog entry. The user must be authenticated to perform this action. - -**Request Body:** - -- `title`: The title of the blog (required). -- `content`: The content of the blog (required). -- `author_id`: The unique ID of the author creating the blog (required). - -**Response:** - -- `200 OK`: The blog was successfully created. -- `400 Bad Request`: The request body contains invalid or missing fields. - ---- - -### 3. Update a Blog - -**Endpoint:** -`PUT /blogs/:id` - -**Path Parameters:** - -- `id`: The unique ID of the blog to be updated. - -**Description:** -Updates an existing blog. Only the fields provided in the request body will be updated. The user must be the author of the blog or an admin. - -**Request Body:** - -- Fields to be updated, such as `title` and `content`. - -**Response:** - -- `200 OK`: The blog was successfully updated. -- `400 Bad Request`: Invalid ID or request body provided. - ---- - -### 4. Delete a Blog - -**Endpoint:** -`DELETE /blogs/:id` - -**Path Parameters:** - -- `id`: The unique ID of the blog to be deleted. - -**Description:** -Deletes an existing blog. The user must be the author of the blog or have admin privileges. - -**Response:** - -- `200 OK`: The blog was successfully deleted. -- `400 Bad Request`: Invalid ID provided. - ---- - -### 5. Add a Comment to a Blog - -**Endpoint:** -`POST /blogs/:blog_id/:author_id/comment` - -**Path Parameters:** - -- `blog_id`: The unique ID of the blog where the comment will be added. -- `author_id`: The unique ID of the user making the comment. - -**Request Body:** - -- `content`: The content of the comment (required). - -**Description:** -Adds a comment to a specified blog. The user must be authenticated to perform this action. - -**Response:** - -- `200 OK`: The comment was successfully added to the blog. -- `400 Bad Request`: Invalid request body or parameters provided. - ---- - -### 6. Like a Blog - -**Endpoint:** -`POST /blogs/:blog_id/:author_id/like` - -**Path Parameters:** - -- `blog_id`: The unique ID of the blog to be liked. -- `author_id`: The unique ID of the user liking the blog. - -**Description:** -Registers a like for the specified blog. The system ensures that the user has not already liked the blog to prevent duplicate likes. - -**Response:** - -- `200 OK`: The like was successfully registered. -- `400 Bad Request`: Invalid parameters provided. - ---- - -### 7. Dislike a Blog - -**Endpoint:** -`POST /blogs/:blog_id/:author_id/dislike` - -**Path Parameters:** - -- `blog_id`: The unique ID of the blog to be disliked. -- `author_id`: The unique ID of the user disliking the blog. - -**Description:** -Registers a dislike for the specified blog. The system ensures that the user has not already disliked the blog to prevent duplicate dislikes. - -**Response:** - -- `200 OK`: The dislike was successfully registered. -- `400 Bad Request`: Invalid parameters provided. - ---- - -### 8. Search Blogs - -**Endpoint:** -`POST /blogs/search` - -**Query Parameters:** - -- `author` (optional): Filters blogs by author name. -- `tags` (optional): Filters blogs by tags. Multiple tags can be provided. -- `title` (optional): Filters blogs by title. - -**Description:** -Searches for blogs based on the provided criteria, such as author, tags, or title. The results are filtered and returned to the user. - -**Response:** - -- `200 OK`: Successfully retrieved the list of blogs that match the search criteria. -- `400 Bad Request`: Invalid query parameters provided. - ---- - -### 9. AI Blog Content Recommendation - -**Endpoint:** -`POST /blogs/GenerateContent` - -**Request Body:** - -- `content`: The text content that the AI will analyze to provide recommendations (required). - -**Description:** -Generates content recommendations or suggestions based on the provided text using AI capabilities. This feature assists users in enhancing or expanding their blog content. - -**Response:** - -- `200 OK`: Successfully generated AI content recommendations. -- `400 Bad Request`: Invalid request body provided. - ---- - -## Users - -### 1. Create User - -**Endpoint:** -`POST /users/` - -**Description:** -Creates a new user account on the platform. - -**Request Body:** - -- `username` (string): The desired username for the new user (required). -- `password` (string): The password for the new user (required). -- `email` (string): The email address of the new user (required). -- `role` (string): The role of the new user (e.g., `user`, `admin`) (required). -- `bio` (string): A brief biography for the new user (optional). -- `phone` (string): The phone number of the new user (optional). -- `profile_pic` (file): The profile picture of the new user (optional). - -**Response:** - -- `200 OK`: Successfully created the user. Returns the user object. -- `400 Bad Request`: The request payload is invalid or there was an error uploading the profile picture. - ---- - -### 2. User Login - -**Endpoint:** -`POST /users/login` - -**Description:** -Authenticates a user and returns access and refresh tokens. - -**Request Body:** - -- `username` (string): The username of the user (required). -- `password` (string): The password of the user (required). - -**Response:** - -- `200 OK`: Successfully authenticated. Returns the access and refresh tokens. -- `400 Bad Request`: The username or password provided is incorrect. - ---- - -### 3. Forgot Password - -**Endpoint:** -`POST /users/forget-password/:email` - -**Description:** -Sends a password reset link to the specified email address. - -**Path Parameters:** - -- `email` (string): The email address associated with the user's account (required). - -**Response:** - -- `200 OK`: The password reset link has been sent to the specified email. -- `400 Bad Request`: There was an error processing the request, such as an invalid email address. - ---- - -### 4. Reset Password - -**Endpoint:** -`POST /users/reset-password/:username/:password` - -**Description:** -Resets the password for a user identified by their username. - -**Path Parameters:** - -- `username` (string): The username of the user (required). -- `password` (string): The new password to be set for the user (required). - -**Response:** - -- `200 OK`: The password has been successfully reset. -- `400 Bad Request`: There was an error processing the request, such as an invalid username or password. - ---- - -### 5. Logout - -**Endpoint:** -`POST /users/logout` - -**Description:** -Logs out the user by invalidating the provided access token. - -**Request Body:** - -- `token` (string): The access token to be invalidated (required). - -**Response:** - -- `200 OK`: Successfully logged out. -- `400 Bad Request`: There was an error processing the request, such as an invalid token. - ---- - -### 6. Get Users - -**Endpoint:** -`GET /users/` - -**Description:** -Retrieves a list of all users on the platform. - -**Response:** - -- `200 OK`: Successfully retrieved the list of users. -- `400 Bad Request`: There was an error processing the request. - ---- - -### 7. Get User - -**Endpoint:** -`GET /users/:id` - -**Description:** -Retrieves details for a specific user identified by their ID. - -**Path Parameters:** - -- `id` (int): The unique ID of the user (required). - -**Response:** - -- `200 OK`: Successfully retrieved the user object. -- `400 Bad Request`: There was an error processing the request, such as an invalid user ID. - ---- - -### 8. Update User - -**Endpoint:** -`PUT /users/:id` - -**Description:** -Updates details for a specific user identified by their ID. - -**Path Parameters:** - -- `id` (int): The unique ID of the user to be updated (required). - -**Request Body:** - -- JSON object representing the fields to update, such as `username`, `email`, `bio`, etc. - -**Response:** - -- `200 OK`: Successfully updated the user object. -- `400 Bad Request`: There was an error processing the request, such as an invalid ID or invalid fields. - ---- - -### 9. Delete User - -**Endpoint:** -`DELETE /users/:id` - -**Description:** -Deletes a user account identified by their ID. - -**Path Parameters:** - -- `id` (int): The unique ID of the user to be deleted (required). - -**Response:** - -- `200 OK`: Successfully deleted the user. -- `400 Bad Request`: There was an error processing the request, such as an invalid user ID. - ---- - -### 10. Promote User - -**Endpoint:** -`POST /users/promote/:id` - -**Description:** -Promotes a user to a higher role, such as from `user` to `admin`. - -**Path Parameters:** - -- `id` (int): The unique ID of the user to be promoted (required). - -**Response:** - -- `200 OK`: Successfully promoted the user. -- `400 Bad Request`: There was an error processing the request, such as an invalid user ID. - ---- - -### 11. Demote User - -**Endpoint:** -`POST /users/demote/:id` - -**Description:** -Demotes a user to a lower role. - -**Path Parameters:** - -- `id` (int): The unique ID of the user to be demoted (required). - -**Response:** - -- `200 OK`: Successfully demoted the user. -- `400 Bad Request`: There was an error processing the request, such as an invalid user ID. - ---- - -### 12. Refresh Token - -**Endpoint:** -`POST /users/refresh-token` - -**Description:** -Generates a new access token using a refresh token. - -**Request Body:** - -- `refresh_token` (string): The refresh token used to generate a new access token (required). - -**Response:** - -- `200 OK`: Successfully generated a new access token. -- `400 Bad Request`: There was an error processing the request, such as an invalid or expired refresh token. diff --git a/backend/AAiT-backend-group-7/blog_project/usecases/blog_usecases.go b/backend/AAiT-backend-group-7/blog_project/usecases/blog_usecases.go index e89a9a26e..b557e55a8 100644 --- a/backend/AAiT-backend-group-7/blog_project/usecases/blog_usecases.go +++ b/backend/AAiT-backend-group-7/blog_project/usecases/blog_usecases.go @@ -66,12 +66,16 @@ func (u *BlogUsecases) GetBlogByID(ctx context.Context, id int) (domain.Blog, er func (u *BlogUsecases) CreateBlog(ctx context.Context, blog domain.Blog) (domain.Blog, error) { blog.ID = generateUniqueID() - user, err := u.UserUsecase.GetUserByUsername(ctx, blog.Author) - if err != nil { - return domain.Blog{}, err + claims, ok := ctx.Value("user").(jwt.MapClaims) + if !ok { + return domain.Blog{}, errors.New("failed to get user claims from context") } - _, err = u.UserUsecase.AddBlog(ctx, user.ID, blog) + username := claims["username"].(string) + userID := int(claims["id"].(float64)) + blog.Author = username + + _, err := u.UserUsecase.AddBlog(ctx, userID, blog) if err != nil { return domain.Blog{}, err }