From ab4b53fd40636a7360a82d84a08923e0bab9ebbb Mon Sep 17 00:00:00 2001 From: KamilM1205 Date: Mon, 22 Sep 2025 22:12:49 +0400 Subject: [PATCH] changes... --- cmd/main.go | 21 +- docs/docs.go | 2 +- docs/swagger.json | 2 +- docs/swagger.yaml | 190 +++++++++++++++- internal/application/errors/db_error.go | 15 ++ .../application/errors/not_found_error.go | 15 ++ internal/application/services/post_service.go | 5 + internal/application/services/user_service.go | 4 +- internal/infrastructure/db/repo/post_repo.go | 11 +- internal/infrastructure/db/repo/user_repo.go | 21 +- .../api/controllers/post_controller.go | 16 +- .../api/controllers/user_controller.go | 210 +++++++++++++++++- .../response_from_post_find_by_id_result.go | 2 +- .../response_from_post_getall_result.go | 2 +- .../response_from_post_result.go | 2 +- .../response_from_user_find_by_id_result.go | 13 ++ .../response_from_user_find_by_name_result.go | 13 ++ .../response_from_user_get_all_result.go | 16 ++ .../api/mapper/response_from_user_result.go | 13 ++ .../api/requests/create_user_request.go | 6 + ...t_post_response.go => put_post_request.go} | 0 .../api/requests/put_user_request.go | 6 + .../interfaces/api/responses/user_response.go | 8 + internal/interfaces/route.go | 12 + internal/utils/password_crypt.go | 21 ++ internal/utils/password_validator.go | 25 +++ 26 files changed, 600 insertions(+), 51 deletions(-) create mode 100644 internal/application/errors/db_error.go create mode 100644 internal/application/errors/not_found_error.go rename internal/interfaces/api/{dto => mapper}/response_from_post_find_by_id_result.go (96%) rename internal/interfaces/api/{dto => mapper}/response_from_post_getall_result.go (97%) rename internal/interfaces/api/{dto => mapper}/response_from_post_result.go (96%) create mode 100644 internal/interfaces/api/mapper/response_from_user_find_by_id_result.go create mode 100644 internal/interfaces/api/mapper/response_from_user_find_by_name_result.go create mode 100644 internal/interfaces/api/mapper/response_from_user_get_all_result.go create mode 100644 internal/interfaces/api/mapper/response_from_user_result.go create mode 100644 internal/interfaces/api/requests/create_user_request.go rename internal/interfaces/api/requests/{put_post_response.go => put_post_request.go} (100%) create mode 100644 internal/interfaces/api/requests/put_user_request.go create mode 100644 internal/interfaces/api/responses/user_response.go create mode 100644 internal/utils/password_crypt.go create mode 100644 internal/utils/password_validator.go diff --git a/cmd/main.go b/cmd/main.go index ff4e652..7f7c950 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -20,9 +20,12 @@ import ( "58team_blog/internal/infrastructure/db" "58team_blog/internal/infrastructure/db/repo" "58team_blog/internal/interfaces" + "58team_blog/internal/utils" "log" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" ) @@ -30,6 +33,11 @@ import ( func main() { router := gin.Default() + // Register custom validators + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("password", utils.PasswordValidator) + } + // Swagger setup docs.SwaggerInfo.Title = "58team blog" docs.SwaggerInfo.Description = "This is blog 58team" @@ -38,14 +46,6 @@ func main() { docs.SwaggerInfo.BasePath = "/api/v1/" docs.SwaggerInfo.Schemes = []string{"http", "https"} - // router.GET("/swagger/*any", func(c *gin.Context) { - // path := c.Param("any") - // if strings.HasPrefix(path, "/doc.json") { - // c.File("docs/swagger.json") - // } else { - // - // } - // }) router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("http://localhost:8080/swag/doc/doc.json"))) router.StaticFile("/swag/doc/doc.json", "docs/swagger.json") @@ -61,10 +61,15 @@ func main() { if err != nil { log.Fatal("Database error: ", err) } + postRepository := repo.CreatePostRepository(d) + userRepository := repo.CreateUserRepository(d) postService := services.CreatePostService(&postRepository) + userService := services.CreateUserService(&userRepository) + interfaces.BindPostAdmin(&postService, g) + interfaces.BindUser(&userService, g) router.Run(":8080") } diff --git a/docs/docs.go b/docs/docs.go index bd37c88..d929fd3 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5,7 +5,7 @@ package docs import "github.com/swaggo/swag/v2" const docTemplate = `{ - "schemes": {{ marshal .Schemes }},"swagger":"2.0","info":{"description":"{{escape .Description}}","title":"{{.Title}}","termsOfService":"http://swagger.io/terms/","contact":{"name":"API Support","url":"http://www.swagger.io/support","email":"support@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"{{.Version}}"},"host":"{{.Host}}","basePath":"{{.BasePath}}","paths":{"/images/{path}":{"get":{"description":"Creates new user in system","produces":["application/json"],"summary":"Create new user","parameters":[{"type":"string","description":"Path to image","name":"path","in":"query","required":true}],"responses":{"200":{"description":"OK"}}}},"/post":{"get":{"description":"Return first 5 posts","produces":["application/json"],"tags":["post"],"summary":"Get all posts","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"post":{"description":"Create new post in blog","consumes":["application/json"],"produces":["application/json"],"tags":["post"],"summary":"Create new post","parameters":[{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.CreatePostRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/offset/{offset}":{"get":{"description":"return 5 posts after first offset posts","produces":["application/json"],"tags":["post"],"summary":"Get posts after offset","parameters":[{"type":"integer","description":"Offset of posts","name":"offset","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/{id}":{"get":{"description":"get post by id","produces":["application/json"],"tags":["post"],"summary":"Get post by id","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.PostResponse"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"put":{"description":"update post content","produces":["application/json"],"tags":["post"],"summary":"Update post content","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true},{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.PutPostRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"delete":{"description":"Delete post by id","produces":["application/json"],"tags":["post"],"summary":"Delete post","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}}},"definitions":{"requests.CreatePostRequest":{"type":"object","required":["content","description","title","userId"],"properties":{"content":{"type":"string","minLength":36},"description":{"type":"string","maxLength":255,"minLength":8},"title":{"type":"string","maxLength":255,"minLength":8},"userId":{"type":"string"}}},"requests.PutPostRequest":{"type":"object","properties":{"content":{"type":"string"},"description":{"type":"string"},"title":{"type":"string"}}},"responses.ErrorResponse":{"type":"object","properties":{"error_code":{"type":"integer"},"message":{"type":"string"}}},"responses.GetListPostResponseItem":{"type":"object","properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}}},"responses.PostResponse":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}}},"securityDefinitions":{"BasicAuth":{"type":"basic"}}}` + "schemes": {{ marshal .Schemes }},"swagger":"2.0","info":{"description":"{{escape .Description}}","title":"{{.Title}}","termsOfService":"http://swagger.io/terms/","contact":{"name":"API Support","url":"http://www.swagger.io/support","email":"support@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"{{.Version}}"},"host":"{{.Host}}","basePath":"{{.BasePath}}","paths":{"/images/{path}":{"get":{"description":"get image by path","produces":["image/png","image/jpeg"],"summary":"Get an image by path","parameters":[{"type":"string","description":"Path to image","name":"path","in":"query","required":true}],"responses":{"200":{"description":"OK"}}}},"/post":{"get":{"description":"Return first 5 posts","produces":["application/json"],"tags":["post"],"summary":"Get all posts","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"post":{"description":"Create new post in blog","consumes":["application/json"],"produces":["application/json"],"tags":["post"],"summary":"Create new post","parameters":[{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.CreatePostRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/offset/{offset}":{"get":{"description":"return 5 posts after first offset posts","produces":["application/json"],"tags":["post"],"summary":"Get posts after offset","parameters":[{"type":"integer","description":"Offset of posts","name":"offset","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/{id}":{"get":{"description":"get post by id","produces":["application/json"],"tags":["post"],"summary":"Get post by id","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.PostResponse"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"put":{"description":"update post content","produces":["application/json"],"tags":["post"],"summary":"Update post content","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true},{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.PutPostRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"delete":{"description":"Delete post by id","produces":["application/json"],"tags":["post"],"summary":"Delete post","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/user/":{"get":{"description":"Return all registered users","produces":["application/json"],"tags":["user"],"summary":"Get all users","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.UserResponse"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"post":{"description":"Creates new user in system","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Create new user","parameters":[{"description":"User data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.CreateUserRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/responses.UserResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/user/name/{name}":{"get":{"description":"Find user by username","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Find user by username","parameters":[{"type":"string","description":"User name","name":"name","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.UserResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/user/{id}":{"get":{"description":"Find user by id","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Find user by id","parameters":[{"type":"string","description":"user id","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.UserResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"put":{"description":"Change the user's name and password","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Change user","parameters":[{"type":"string","description":"User id","name":"id","in":"path","required":true},{"description":"User data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.PutUserRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.UserResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"delete":{"description":"Delete user","produces":["application/json"],"tags":["user"],"summary":"Delete user","parameters":[{"type":"string","description":"User id","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}}},"definitions":{"requests.CreatePostRequest":{"type":"object","required":["content","description","title","userId"],"properties":{"content":{"type":"string","minLength":36},"description":{"type":"string","maxLength":255,"minLength":8},"title":{"type":"string","maxLength":255,"minLength":8},"userId":{"type":"string"}}},"requests.CreateUserRequest":{"type":"object","required":["password","username"],"properties":{"password":{"type":"string","maxLength":32,"minLength":6},"username":{"type":"string","maxLength":32,"minLength":3}}},"requests.PutPostRequest":{"type":"object","properties":{"content":{"type":"string"},"description":{"type":"string"},"title":{"type":"string"}}},"requests.PutUserRequest":{"type":"object","required":["password","username"],"properties":{"password":{"type":"string","maxLength":32,"minLength":6},"username":{"type":"string","maxLength":32,"minLength":3}}},"responses.ErrorResponse":{"type":"object","properties":{"error_code":{"type":"integer"},"message":{"type":"string"}}},"responses.GetListPostResponseItem":{"type":"object","properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}}},"responses.PostResponse":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}},"responses.UserResponse":{"type":"object","properties":{"id":{"type":"string"},"username":{"type":"string"}}}},"securityDefinitions":{"BasicAuth":{"type":"basic"}}}` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ diff --git a/docs/swagger.json b/docs/swagger.json index ae46508..eba4d3a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"description":"58team blog's backend","title":"58team blog backend","termsOfService":"http://swagger.io/terms/","contact":{"name":"API Support","url":"http://www.swagger.io/support","email":"support@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0"},"host":"localhost:8080","basePath":"/api/v1","paths":{"/images/{path}":{"get":{"description":"Creates new user in system","produces":["application/json"],"summary":"Create new user","parameters":[{"type":"string","description":"Path to image","name":"path","in":"query","required":true}],"responses":{"200":{"description":"OK"}}}},"/post":{"get":{"description":"Return first 5 posts","produces":["application/json"],"tags":["post"],"summary":"Get all posts","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"post":{"description":"Create new post in blog","consumes":["application/json"],"produces":["application/json"],"tags":["post"],"summary":"Create new post","parameters":[{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.CreatePostRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/offset/{offset}":{"get":{"description":"return 5 posts after first offset posts","produces":["application/json"],"tags":["post"],"summary":"Get posts after offset","parameters":[{"type":"integer","description":"Offset of posts","name":"offset","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/{id}":{"get":{"description":"get post by id","produces":["application/json"],"tags":["post"],"summary":"Get post by id","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.PostResponse"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"put":{"description":"update post content","produces":["application/json"],"tags":["post"],"summary":"Update post content","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true},{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.PutPostRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"delete":{"description":"Delete post by id","produces":["application/json"],"tags":["post"],"summary":"Delete post","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}}},"definitions":{"requests.CreatePostRequest":{"type":"object","required":["content","description","title","userId"],"properties":{"content":{"type":"string","minLength":36},"description":{"type":"string","maxLength":255,"minLength":8},"title":{"type":"string","maxLength":255,"minLength":8},"userId":{"type":"string"}}},"requests.PutPostRequest":{"type":"object","properties":{"content":{"type":"string"},"description":{"type":"string"},"title":{"type":"string"}}},"responses.ErrorResponse":{"type":"object","properties":{"error_code":{"type":"integer"},"message":{"type":"string"}}},"responses.GetListPostResponseItem":{"type":"object","properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}}},"responses.PostResponse":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}}},"securityDefinitions":{"BasicAuth":{"type":"basic"}}} \ No newline at end of file +{"swagger":"2.0","info":{"description":"58team blog's backend","title":"58team blog backend","termsOfService":"http://swagger.io/terms/","contact":{"name":"API Support","url":"http://www.swagger.io/support","email":"support@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0"},"host":"localhost:8080","basePath":"/api/v1","paths":{"/images/{path}":{"get":{"description":"get image by path","produces":["image/png","image/jpeg"],"summary":"Get an image by path","parameters":[{"type":"string","description":"Path to image","name":"path","in":"query","required":true}],"responses":{"200":{"description":"OK"}}}},"/post":{"get":{"description":"Return first 5 posts","produces":["application/json"],"tags":["post"],"summary":"Get all posts","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"post":{"description":"Create new post in blog","consumes":["application/json"],"produces":["application/json"],"tags":["post"],"summary":"Create new post","parameters":[{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.CreatePostRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/offset/{offset}":{"get":{"description":"return 5 posts after first offset posts","produces":["application/json"],"tags":["post"],"summary":"Get posts after offset","parameters":[{"type":"integer","description":"Offset of posts","name":"offset","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/{id}":{"get":{"description":"get post by id","produces":["application/json"],"tags":["post"],"summary":"Get post by id","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.PostResponse"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"put":{"description":"update post content","produces":["application/json"],"tags":["post"],"summary":"Update post content","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true},{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.PutPostRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"delete":{"description":"Delete post by id","produces":["application/json"],"tags":["post"],"summary":"Delete post","parameters":[{"type":"string","description":"Id of post","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/user/":{"get":{"description":"Return all registered users","produces":["application/json"],"tags":["user"],"summary":"Get all users","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.UserResponse"}}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"post":{"description":"Creates new user in system","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Create new user","parameters":[{"description":"User data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.CreateUserRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/responses.UserResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/user/name/{name}":{"get":{"description":"Find user by username","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Find user by username","parameters":[{"type":"string","description":"User name","name":"name","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.UserResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/user/{id}":{"get":{"description":"Find user by id","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Find user by id","parameters":[{"type":"string","description":"user id","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.UserResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"put":{"description":"Change the user's name and password","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Change user","parameters":[{"type":"string","description":"User id","name":"id","in":"path","required":true},{"description":"User data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.PutUserRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.UserResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"delete":{"description":"Delete user","produces":["application/json"],"tags":["user"],"summary":"Delete user","parameters":[{"type":"string","description":"User id","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}}},"definitions":{"requests.CreatePostRequest":{"type":"object","required":["content","description","title","userId"],"properties":{"content":{"type":"string","minLength":36},"description":{"type":"string","maxLength":255,"minLength":8},"title":{"type":"string","maxLength":255,"minLength":8},"userId":{"type":"string"}}},"requests.CreateUserRequest":{"type":"object","required":["password","username"],"properties":{"password":{"type":"string","maxLength":32,"minLength":6},"username":{"type":"string","maxLength":32,"minLength":3}}},"requests.PutPostRequest":{"type":"object","properties":{"content":{"type":"string"},"description":{"type":"string"},"title":{"type":"string"}}},"requests.PutUserRequest":{"type":"object","required":["password","username"],"properties":{"password":{"type":"string","maxLength":32,"minLength":6},"username":{"type":"string","maxLength":32,"minLength":3}}},"responses.ErrorResponse":{"type":"object","properties":{"error_code":{"type":"integer"},"message":{"type":"string"}}},"responses.GetListPostResponseItem":{"type":"object","properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}}},"responses.PostResponse":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}},"responses.UserResponse":{"type":"object","properties":{"id":{"type":"string"},"username":{"type":"string"}}}},"securityDefinitions":{"BasicAuth":{"type":"basic"}}} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 4449e5a..1654a79 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -21,6 +21,20 @@ definitions: - title - userId type: object + requests.CreateUserRequest: + properties: + password: + maxLength: 32 + minLength: 6 + type: string + username: + maxLength: 32 + minLength: 3 + type: string + required: + - password + - username + type: object requests.PutPostRequest: properties: content: @@ -30,6 +44,20 @@ definitions: title: type: string type: object + requests.PutUserRequest: + properties: + password: + maxLength: 32 + minLength: 6 + type: string + username: + maxLength: 32 + minLength: 3 + type: string + required: + - password + - username + type: object responses.ErrorResponse: properties: error_code: @@ -63,6 +91,13 @@ definitions: userId: type: string type: object + responses.UserResponse: + properties: + id: + type: string + username: + type: string + type: object host: localhost:8080 info: contact: @@ -79,7 +114,7 @@ info: paths: /images/{path}: get: - description: Creates new user in system + description: get image by path parameters: - description: Path to image in: query @@ -87,11 +122,12 @@ paths: required: true type: string produces: - - application/json + - image/png + - image/jpeg responses: "200": description: OK - summary: Create new user + summary: Get an image by path /post: get: description: Return first 5 posts @@ -125,8 +161,8 @@ paths: produces: - application/json responses: - "200": - description: OK + "201": + description: Created schema: $ref: '#/definitions/responses.PostResponse' "400": @@ -238,6 +274,150 @@ paths: summary: Get posts after offset tags: - post + /user/: + get: + description: Return all registered users + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/responses.UserResponse' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Get all users + tags: + - user + post: + consumes: + - application/json + description: Creates new user in system + parameters: + - description: User data + in: body + name: request + required: true + schema: + $ref: '#/definitions/requests.CreateUserRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/responses.UserResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Create new user + tags: + - user + /user/{id}: + delete: + description: Delete user + parameters: + - description: User id + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Delete user + tags: + - user + get: + consumes: + - application/json + description: Find user by id + parameters: + - description: user id + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.UserResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Find user by id + tags: + - user + put: + consumes: + - application/json + description: Change the user's name and password + parameters: + - description: User id + in: path + name: id + required: true + type: string + - description: User data + in: body + name: request + required: true + schema: + $ref: '#/definitions/requests.PutUserRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.UserResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Change user + tags: + - user + /user/name/{name}: + get: + consumes: + - application/json + description: Find user by username + parameters: + - description: User name + in: path + name: name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.UserResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Find user by username + tags: + - user securityDefinitions: BasicAuth: type: basic diff --git a/internal/application/errors/db_error.go b/internal/application/errors/db_error.go new file mode 100644 index 0000000..79b476b --- /dev/null +++ b/internal/application/errors/db_error.go @@ -0,0 +1,15 @@ +package errors + +type DBError struct { + msg string +} + +func NewDBError(msg string) DBError { + return DBError{ + msg: msg, + } +} + +func (e *DBError) Error() string { + return e.msg +} diff --git a/internal/application/errors/not_found_error.go b/internal/application/errors/not_found_error.go new file mode 100644 index 0000000..ac60c54 --- /dev/null +++ b/internal/application/errors/not_found_error.go @@ -0,0 +1,15 @@ +package errors + +type NotFoundError struct { + msg string +} + +func NewNotFoundError(msg string) *NotFoundError { + return &NotFoundError{ + msg: msg, + } +} + +func (e *NotFoundError) Error() string { + return e.msg +} diff --git a/internal/application/services/post_service.go b/internal/application/services/post_service.go index f1633a5..efe11c6 100644 --- a/internal/application/services/post_service.go +++ b/internal/application/services/post_service.go @@ -3,6 +3,7 @@ package services import ( "58team_blog/internal/application/commands" "58team_blog/internal/application/common" + ie "58team_blog/internal/application/errors" "58team_blog/internal/application/mapper" "58team_blog/internal/application/queries" "58team_blog/internal/domain/entities" @@ -44,6 +45,10 @@ func (s *PostService) FindById(query queries.PostFindByIdQuery) (*queries.PostFi return nil, err } + if post == nil { + return nil, ie.NewNotFoundError("Post") + } + if err := post.Validate(); err != nil { return nil, err } diff --git a/internal/application/services/user_service.go b/internal/application/services/user_service.go index b5ccfea..7f61762 100644 --- a/internal/application/services/user_service.go +++ b/internal/application/services/user_service.go @@ -15,7 +15,7 @@ type UserService struct { repo repository.UsersRepository } -func NewUserService(repo repository.UsersRepository) UserService { +func CreateUserService(repo repository.UsersRepository) UserService { return UserService{ repo: repo, } @@ -26,7 +26,7 @@ func (s *UserService) Create(cmd commands.CreateUserCommand) (*common.UserResult { user, err := s.repo.FindByName(cmd.Username) if err != nil { - return nil, err + return nil, fmt.Errorf("User.create findByName error: %s", err) } if user != nil { diff --git a/internal/infrastructure/db/repo/post_repo.go b/internal/infrastructure/db/repo/post_repo.go index a58f1dc..6f99819 100644 --- a/internal/infrastructure/db/repo/post_repo.go +++ b/internal/infrastructure/db/repo/post_repo.go @@ -3,6 +3,7 @@ package repo import ( "58team_blog/internal/domain/entities" "58team_blog/internal/infrastructure/db" + "database/sql" "strconv" "github.com/google/uuid" @@ -28,7 +29,7 @@ func (r *PostRepository) Create(entity *entities.Post) (*entities.Post, error) { } func (r *PostRepository) FindById(id uuid.UUID) (*entities.Post, error) { - var entity *entities.Post + var entity entities.Post query := "SELECT * FROM " + entities.PostTable + " WHERE id=?" query, args, err := sqlx.In(query, id) @@ -37,9 +38,13 @@ func (r *PostRepository) FindById(id uuid.UUID) (*entities.Post, error) { } query = r.conn.Conn.Rebind(query) - err = r.conn.Conn.Get(entity, query, args) + err = r.conn.Conn.Get(&entity, query, args...) - return entity, err + if err == sql.ErrNoRows { + return nil, nil + } + + return &entity, err } func (r *PostRepository) FindAllByUserName(userName string) ([]*entities.Post, error) { diff --git a/internal/infrastructure/db/repo/user_repo.go b/internal/infrastructure/db/repo/user_repo.go index d421407..074998f 100644 --- a/internal/infrastructure/db/repo/user_repo.go +++ b/internal/infrastructure/db/repo/user_repo.go @@ -3,6 +3,7 @@ package repo import ( "58team_blog/internal/domain/entities" "58team_blog/internal/infrastructure/db" + "database/sql" "github.com/google/uuid" "github.com/jmoiron/sqlx" @@ -39,31 +40,29 @@ func (r *UserRepository) FindById(id uuid.UUID) (*entities.User, error) { } query = r.conn.Conn.Rebind(query) - err = r.conn.Conn.Get(entity, query, arg...) + err = r.conn.Conn.Select(entity, query, arg...) return entity, err } func (r *UserRepository) FindByName(username string) (*entities.User, error) { - var entity *entities.User + var entity entities.User - query := "SELECT * FROM " + entities.UserTable + " WHERE username=?" - query, arg, err := sqlx.In(query, username) - if err != nil { - return nil, err + query := "SELECT * FROM " + entities.UserTable + " WHERE username=$1" + + err := r.conn.Conn.Get(&entity, query, username) + if err == sql.ErrNoRows { + return nil, nil } - query = r.conn.Conn.Rebind(query) - err = r.conn.Conn.Get(entity, query, arg...) - - return entity, err + return &entity, nil } func (r *UserRepository) GetAll() ([]*entities.User, error) { var entity_list []*entities.User query := "SELECT * FROM " + entities.UserTable - err := r.conn.Conn.Select(entity_list, query) + err := r.conn.Conn.Select(&entity_list, query) if err != nil { return nil, err } diff --git a/internal/interfaces/api/controllers/post_controller.go b/internal/interfaces/api/controllers/post_controller.go index 685fc45..7944b0e 100644 --- a/internal/interfaces/api/controllers/post_controller.go +++ b/internal/interfaces/api/controllers/post_controller.go @@ -4,7 +4,7 @@ import ( "58team_blog/internal/application/commands" "58team_blog/internal/application/queries" "58team_blog/internal/application/services" - "58team_blog/internal/interfaces/api/dto" + "58team_blog/internal/interfaces/api/mapper" "58team_blog/internal/interfaces/api/requests" "58team_blog/internal/interfaces/api/responses" "log" @@ -33,7 +33,7 @@ func CreatePostController(service *services.PostService) PostController { // @Accept json // @Produce json // @Param request body requests.CreatePostRequest true "Post data" -// @Success 200 {object} responses.PostResponse +// @Success 201 {object} responses.PostResponse // @Failure 400 {object} responses.ErrorResponse // @Failure 500 {object} responses.ErrorResponse // @Router /post [post] @@ -70,9 +70,9 @@ func (r *PostController) Post(c *gin.Context) { return } - response := dto.ResponseFromPostResult(res) + response := mapper.ResponseFromPostResult(res) - c.JSON(http.StatusOK, response) + c.JSON(http.StatusCreated, response) } // GetAllPost godoc @@ -93,7 +93,7 @@ func (r *PostController) GetAll(c *gin.Context) { return } - res := dto.ResponseFromPostGetAllResult(result) + res := mapper.ResponseFromPostGetAllResult(result) c.JSON(http.StatusOK, res) } @@ -126,7 +126,7 @@ func (r *PostController) GetAllWithOffset(c *gin.Context) { return } - res := dto.ResponseFromPostGetAllResult(result) + res := mapper.ResponseFromPostGetAllResult(result) c.JSON(http.StatusOK, res) } @@ -164,7 +164,7 @@ func (r *PostController) GetById(c *gin.Context) { return } - result := dto.ResponseFormPostFindByIdResult(posts) + result := mapper.ResponseFormPostFindByIdResult(posts) c.JSON(http.StatusOK, result) } @@ -216,7 +216,7 @@ func (r *PostController) Put(c *gin.Context) { return } - response := dto.ResponseFromPostResult(post) + response := mapper.ResponseFromPostResult(post) c.JSON(http.StatusOK, response) } diff --git a/internal/interfaces/api/controllers/user_controller.go b/internal/interfaces/api/controllers/user_controller.go index 6591806..761eb2f 100644 --- a/internal/interfaces/api/controllers/user_controller.go +++ b/internal/interfaces/api/controllers/user_controller.go @@ -1,9 +1,18 @@ package controllers import ( + "58team_blog/internal/application/commands" + "58team_blog/internal/application/queries" "58team_blog/internal/application/services" + "58team_blog/internal/interfaces/api/mapper" + "58team_blog/internal/interfaces/api/requests" + "58team_blog/internal/interfaces/api/responses" + "58team_blog/internal/utils" + "log" + "net/http" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) type UserController struct { @@ -18,33 +27,216 @@ func CreateUserController(service *services.UserService) UserController { // @Summary Create new user // @Description Creates new user in system -// @Param path query string true "Path to image" +// @Tags user +// @Accept json // @Produce json -// @Success 200 -// @Router /images/{path} [get] +// @Param request body requests.CreateUserRequest true "User data" +// @Success 201 {object} responses.UserResponse +// @Failure 400 {object} responses.ErrorResponse +// @Router /user/ [post] func (r *UserController) Post(c *gin.Context) { - // TODO: return image - panic("Not implemented") + var request requests.CreateUserRequest + if err := c.BindJSON(&request); err != nil { + log.Println("User invalid request: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Bad request") + c.JSON(resp.ErrorCode, resp) + return + } + + encrypted_password, err := utils.EncryptPassword(request.Password) + if err != nil { + log.Println("User encrupt password error: ", err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.JSON(resp.ErrorCode, resp) + return + } + + cmd := commands.CreateUserCommand{ + Username: request.Username, + Password: encrypted_password, + } + + user, err := r.service.Create(cmd) + if err != nil { + log.Println("User service error: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Bad request") + c.JSON(resp.ErrorCode, resp) + return + } + + response := mapper.ResponseFromUserResult(user) + + c.JSON(http.StatusCreated, response) } +// @Summary Find user by id +// @Description Find user by id +// @Tags user +// @Accept json +// @Produce json +// @Param id path string true "user id" +// @Success 200 {object} responses.UserResponse +// @Failure 400 {object} responses.ErrorResponse +// @Router /user/{id} [get] func (r *UserController) FindById(c *gin.Context) { + id_path := c.Param("id") + id, err := uuid.Parse(id_path) + if err != nil { + log.Println("User id error: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Bad request") + c.JSON(resp.ErrorCode, resp) + return + } + + query := queries.UserFindByIdQuery{ + Id: id, + } + + user, err := r.service.FindById(query) + if err != nil { + log.Println("User service error: ", err) + resp := responses.CreateErrorResponse(http.StatusNotFound, "Not found") + c.JSON(resp.ErrorCode, resp) + return + } + + response := mapper.ResponseFromUserFindByIdResult(user) + c.JSON(http.StatusOK, response) } +// @Summary Find user by username +// @Description Find user by username +// @Tags user +// @Accept json +// @Produce json +// @Param name path string true "User name" +// @Success 200 {object} responses.UserResponse +// @Failure 400 {object} responses.ErrorResponse +// @Router /user/name/{name} [get] func (r *UserController) FindByName(c *gin.Context) { + name := c.Param("name") + query := queries.UserFindByNameQuery{ + Name: name, + } + + user, err := r.service.FindByName(query) + if err != nil { + log.Println("User service error: ", err) + resp := responses.CreateErrorResponse(http.StatusNotFound, "Not found") + c.JSON(resp.ErrorCode, resp) + return + } + + response := mapper.ResponseFromUserFindByNameResult(user) + c.JSON(http.StatusOK, response) } +// @Summary Get all users +// @Description Return all registered users +// @Tags user +// @Produce json +// @Success 200 {object} responses.UserResponseList +// @Failure 400 {object} responses.ErrorResponse +// @Router /user/ [get] func (r *UserController) GetAll(c *gin.Context) { - // TODO: return image - panic("Not implemented") + users, err := r.service.GetAll() + if err != nil { + log.Println("User service error: ", err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.JSON(resp.ErrorCode, resp) + return + } + + responses := mapper.ResponseFromUserGetAllResult(users) + c.JSON(http.StatusOK, responses) } +// @Summary Change user +// @Description Change the user's name and password +// @Tags user +// @Accept json +// @Produce json +// @Param id path string true "User id" +// @Param request body requests.PutUserRequest true "User data" +// @Success 200 {object} responses.UserResponse +// @Failure 400 {object} responses.ErrorResponse +// @Router /user/{id} [put] func (r *UserController) Put(c *gin.Context) { + var request requests.PutUserRequest + id_path := c.Param("id") + id, err := uuid.Parse(id_path) + if err != nil { + log.Println("User id error: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Bad request") + c.JSON(resp.ErrorCode, resp) + return + } + + if err := c.BindJSON(&request); err != nil { + log.Println("User request error: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Bad request") + c.JSON(resp.ErrorCode, resp) + return + } + + password, err := utils.EncryptPassword(request.Password) + if err != nil { + log.Println("User password encrypt error: ", err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.JSON(resp.ErrorCode, resp) + return + } + + cmd := commands.UpdateUserCommand{ + Id: id, + Username: request.Username, + Password: password, + } + + user, err := r.service.Update(cmd) + if err != nil { + log.Println("User service error: ", err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.JSON(resp.ErrorCode, resp) + return + } + + response := mapper.ResponseFromUserResult(user) + c.JSON(http.StatusOK, response) } +// @Summary Delete user +// @Description Delete user +// @Tags user +// @Produce json +// @Param id path string true "User id" +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse +// @Router /user/{id} [delete] func (r *UserController) Delete(c *gin.Context) { - // TODO: return image - panic("Not implemented") + id_path := c.Param("id") + + id, err := uuid.Parse(id_path) + if err != nil { + log.Println("User id error: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Bad request") + c.JSON(resp.ErrorCode, resp) + return + } + + cmd := commands.DeleteUserCommand{ + Id: id, + } + + if err := r.service.Delete(cmd); err != nil { + log.Println("User service error: ", err) + resp := responses.CreateErrorResponse(http.StatusNotFound, "User not found") + c.JSON(resp.ErrorCode, resp) + return + } + + c.Status(http.StatusOK) } diff --git a/internal/interfaces/api/dto/response_from_post_find_by_id_result.go b/internal/interfaces/api/mapper/response_from_post_find_by_id_result.go similarity index 96% rename from internal/interfaces/api/dto/response_from_post_find_by_id_result.go rename to internal/interfaces/api/mapper/response_from_post_find_by_id_result.go index 9c03af8..86fb1bb 100644 --- a/internal/interfaces/api/dto/response_from_post_find_by_id_result.go +++ b/internal/interfaces/api/mapper/response_from_post_find_by_id_result.go @@ -1,4 +1,4 @@ -package dto +package mapper import ( "58team_blog/internal/application/queries" diff --git a/internal/interfaces/api/dto/response_from_post_getall_result.go b/internal/interfaces/api/mapper/response_from_post_getall_result.go similarity index 97% rename from internal/interfaces/api/dto/response_from_post_getall_result.go rename to internal/interfaces/api/mapper/response_from_post_getall_result.go index 0eb6bb2..faf0371 100644 --- a/internal/interfaces/api/dto/response_from_post_getall_result.go +++ b/internal/interfaces/api/mapper/response_from_post_getall_result.go @@ -1,4 +1,4 @@ -package dto +package mapper import ( "58team_blog/internal/application/common" diff --git a/internal/interfaces/api/dto/response_from_post_result.go b/internal/interfaces/api/mapper/response_from_post_result.go similarity index 96% rename from internal/interfaces/api/dto/response_from_post_result.go rename to internal/interfaces/api/mapper/response_from_post_result.go index 7702fcc..86ba7c6 100644 --- a/internal/interfaces/api/dto/response_from_post_result.go +++ b/internal/interfaces/api/mapper/response_from_post_result.go @@ -1,4 +1,4 @@ -package dto +package mapper import ( "58team_blog/internal/application/common" diff --git a/internal/interfaces/api/mapper/response_from_user_find_by_id_result.go b/internal/interfaces/api/mapper/response_from_user_find_by_id_result.go new file mode 100644 index 0000000..b5d90a1 --- /dev/null +++ b/internal/interfaces/api/mapper/response_from_user_find_by_id_result.go @@ -0,0 +1,13 @@ +package mapper + +import ( + "58team_blog/internal/application/queries" + "58team_blog/internal/interfaces/api/responses" +) + +func ResponseFromUserFindByIdResult(user *queries.UserFindByIdResult) responses.UserResponse { + return responses.UserResponse{ + Id: user.Result.Id.String(), + UserName: user.Result.UserName, + } +} diff --git a/internal/interfaces/api/mapper/response_from_user_find_by_name_result.go b/internal/interfaces/api/mapper/response_from_user_find_by_name_result.go new file mode 100644 index 0000000..eff4642 --- /dev/null +++ b/internal/interfaces/api/mapper/response_from_user_find_by_name_result.go @@ -0,0 +1,13 @@ +package mapper + +import ( + "58team_blog/internal/application/queries" + "58team_blog/internal/interfaces/api/responses" +) + +func ResponseFromUserFindByNameResult(result *queries.UserFindByNameResult) responses.UserResponse { + return responses.UserResponse{ + Id: result.Result.Id.String(), + UserName: result.Result.UserName, + } +} diff --git a/internal/interfaces/api/mapper/response_from_user_get_all_result.go b/internal/interfaces/api/mapper/response_from_user_get_all_result.go new file mode 100644 index 0000000..944f5c5 --- /dev/null +++ b/internal/interfaces/api/mapper/response_from_user_get_all_result.go @@ -0,0 +1,16 @@ +package mapper + +import ( + "58team_blog/internal/application/queries" + "58team_blog/internal/interfaces/api/responses" +) + +func ResponseFromUserGetAllResult(result *queries.UserGetAllResult) responses.UserResponseList { + var list responses.UserResponseList + + for _, i := range result.Result.Result { + list = append(list, ResponseFromUserResult(i)) + } + + return list +} diff --git a/internal/interfaces/api/mapper/response_from_user_result.go b/internal/interfaces/api/mapper/response_from_user_result.go new file mode 100644 index 0000000..0a2518a --- /dev/null +++ b/internal/interfaces/api/mapper/response_from_user_result.go @@ -0,0 +1,13 @@ +package mapper + +import ( + "58team_blog/internal/application/common" + "58team_blog/internal/interfaces/api/responses" +) + +func ResponseFromUserResult(result *common.UserResult) responses.UserResponse { + return responses.UserResponse{ + Id: result.Id.String(), + UserName: result.UserName, + } +} diff --git a/internal/interfaces/api/requests/create_user_request.go b/internal/interfaces/api/requests/create_user_request.go new file mode 100644 index 0000000..99e7b86 --- /dev/null +++ b/internal/interfaces/api/requests/create_user_request.go @@ -0,0 +1,6 @@ +package requests + +type CreateUserRequest struct { + Username string `json:"username" validate:"required,min=3,max=32"` + Password string `json:"password" validate:"required,min=6,max=32,password"` +} diff --git a/internal/interfaces/api/requests/put_post_response.go b/internal/interfaces/api/requests/put_post_request.go similarity index 100% rename from internal/interfaces/api/requests/put_post_response.go rename to internal/interfaces/api/requests/put_post_request.go diff --git a/internal/interfaces/api/requests/put_user_request.go b/internal/interfaces/api/requests/put_user_request.go new file mode 100644 index 0000000..04d94bb --- /dev/null +++ b/internal/interfaces/api/requests/put_user_request.go @@ -0,0 +1,6 @@ +package requests + +type PutUserRequest struct { + Username string `json:"username" validate:"required,min=3,max=32"` + Password string `json:"password" validate:"required,min=6,max=32,password"` +} diff --git a/internal/interfaces/api/responses/user_response.go b/internal/interfaces/api/responses/user_response.go new file mode 100644 index 0000000..58e3ee2 --- /dev/null +++ b/internal/interfaces/api/responses/user_response.go @@ -0,0 +1,8 @@ +package responses + +type UserResponse struct { + Id string `json:"id"` + UserName string `json:"username"` +} + +type UserResponseList []UserResponse diff --git a/internal/interfaces/route.go b/internal/interfaces/route.go index c42cf76..1dbafa1 100644 --- a/internal/interfaces/route.go +++ b/internal/interfaces/route.go @@ -18,3 +18,15 @@ func BindPostAdmin(service *services.PostService, group *gin.RouterGroup) { g.PUT("/:id", post.Put) g.DELETE("/:id", post.Delete) } + +func BindUser(service *services.UserService, group *gin.RouterGroup) { + user := controllers.CreateUserController(service) + + g := group.Group("/user/") + g.POST("/", user.Post) + g.GET("/", user.GetAll) + g.GET("/:id", user.FindById) + g.GET("/name/:name", user.FindByName) + g.PUT("/:id", user.Put) + g.DELETE("/:id", user.Delete) +} diff --git a/internal/utils/password_crypt.go b/internal/utils/password_crypt.go new file mode 100644 index 0000000..4f63565 --- /dev/null +++ b/internal/utils/password_crypt.go @@ -0,0 +1,21 @@ +package utils + +import ( + "fmt" + + "golang.org/x/crypto/bcrypt" +) + +func EncryptPassword(pass string) (string, error) { + var salted string + salt := "58_team:%s:1205secret" + + salted = fmt.Sprintf(salt, pass) + + hashed, err := bcrypt.GenerateFromPassword([]byte(salted), 12) + if err != nil { + return "", err + } + + return string(hashed), nil +} diff --git a/internal/utils/password_validator.go b/internal/utils/password_validator.go new file mode 100644 index 0000000..688e21b --- /dev/null +++ b/internal/utils/password_validator.go @@ -0,0 +1,25 @@ +package utils + +import ( + "unicode" + + "github.com/go-playground/validator/v10" +) + +func PasswordValidator(fl validator.FieldLevel) bool { + password := fl.Field().Interface().(string) + + var hasUpper, hasNumber, hasLower = false, false, false + + for _, c := range password { + if unicode.IsUpper(c) { + hasUpper = true + } else if unicode.IsLower(c) { + hasLower = true + } else if unicode.IsDigit(c) { + hasNumber = true + } + } + + return hasUpper && hasNumber && hasLower +}