diff --git a/cmd/main.go b/cmd/main.go index 7f7c950..50d49a6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -23,6 +23,8 @@ import ( "58team_blog/internal/utils" "log" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/validator/v10" @@ -30,9 +32,14 @@ import ( ginSwagger "github.com/swaggo/gin-swagger" ) +const secret = "58secret" + func main() { router := gin.Default() + // Setup cookie container + router.Use(sessions.Sessions("session", cookie.NewStore([]byte(secret)))) + // Register custom validators if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("password", utils.PasswordValidator) @@ -64,12 +71,15 @@ func main() { postRepository := repo.CreatePostRepository(d) userRepository := repo.CreateUserRepository(d) + imagesRepository := repo.CreateImagesRepository(d) postService := services.CreatePostService(&postRepository) userService := services.CreateUserService(&userRepository) + imagesService := services.CreateImagesService(&imagesRepository) - interfaces.BindPostAdmin(&postService, g) - interfaces.BindUser(&userService, g) + interfaces.BindPostAdmin(&postService, &userService, g) + interfaces.BindUser(config.AdminName, config.AdminPassword, &userService, g) + interfaces.BindImages(config.ImagesPath, &imagesService, g) router.Run(":8080") } diff --git a/config.yaml b/config.yaml index 3666efd..9a1cea9 100644 --- a/config.yaml +++ b/config.yaml @@ -4,7 +4,7 @@ db-password: 1205 db-host: localhost db-port: 5432 admin_name: muts -admin_pass: 1205 +admin_pass: Abc1205 images_path: ./images/ posts_path: ./posts/ diff --git a/docs/docs.go b/docs/docs.go index 4b2e9e3..f56b2b3 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":"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"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"500":{"description":"Internal Server Error","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"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}}}` + "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/":{"post":{"description":"Upload new image and returns uploaded image json object","produces":["application/json"],"tags":["images"],"summary":"Upload new image","parameters":[{"type":"file","description":"image file","name":"file","in":"formData","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.ImageResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/images/{path}":{"get":{"description":"get image by path","produces":["application/octet-stream","application/json"],"tags":["images"],"summary":"Get an image by path","parameters":[{"type":"string","description":"Path to image","name":"path","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"file"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"delete":{"description":"Delete image from server by given path","produces":["image/png","image/jpeg"],"tags":["images"],"summary":"Delete image by path","parameters":[{"type":"string","description":"Path to image","name":"filename","in":"path","required":true}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/login":{"post":{"description":"Login user into system","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Login","parameters":[{"description":"User login data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.LoginUserRequest"}}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/logout":{"get":{"description":"Creates new user in system","produces":["application/json"],"tags":["user"],"summary":"Create new user","responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/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"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"500":{"description":"Internal Server Error","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"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}}},"definitions":{"requests.CreatePostRequest":{"type":"object","required":["content","description","title","userId"],"properties":{"category":{"type":"string"},"content":{"type":"string","minLength":36},"description":{"type":"string","maxLength":255,"minLength":8},"tags":{"type":"array","items":{"type":"string"}},"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.LoginUserRequest":{"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":{"category":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"},"username":{"type":"string"}}},"responses.ImageResponse":{"type":"object","properties":{"id":{"type":"string"},"path":{"type":"string"}}},"responses.PostResponse":{"type":"object","properties":{"category":{"type":"string"},"content":{"type":"string"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"},"username":{"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 97a1a1b..30a04f4 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":"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"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"500":{"description":"Internal Server Error","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"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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 +{"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/":{"post":{"description":"Upload new image and returns uploaded image json object","produces":["application/json"],"tags":["images"],"summary":"Upload new image","parameters":[{"type":"file","description":"image file","name":"file","in":"formData","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.ImageResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/images/{path}":{"get":{"description":"get image by path","produces":["application/octet-stream","application/json"],"tags":["images"],"summary":"Get an image by path","parameters":[{"type":"string","description":"Path to image","name":"path","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"file"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}},"delete":{"description":"Delete image from server by given path","produces":["image/png","image/jpeg"],"tags":["images"],"summary":"Delete image by path","parameters":[{"type":"string","description":"Path to image","name":"filename","in":"path","required":true}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/login":{"post":{"description":"Login user into system","consumes":["application/json"],"produces":["application/json"],"tags":["user"],"summary":"Login","parameters":[{"description":"User login data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.LoginUserRequest"}}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/logout":{"get":{"description":"Creates new user in system","produces":["application/json"],"tags":["user"],"summary":"Create new user","responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/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"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"500":{"description":"Internal Server Error","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"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","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"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/responses.ErrorResponse"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}}},"definitions":{"requests.CreatePostRequest":{"type":"object","required":["content","description","title","userId"],"properties":{"category":{"type":"string"},"content":{"type":"string","minLength":36},"description":{"type":"string","maxLength":255,"minLength":8},"tags":{"type":"array","items":{"type":"string"}},"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.LoginUserRequest":{"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":{"category":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"},"username":{"type":"string"}}},"responses.ImageResponse":{"type":"object","properties":{"id":{"type":"string"},"path":{"type":"string"}}},"responses.PostResponse":{"type":"object","properties":{"category":{"type":"string"},"content":{"type":"string"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"},"username":{"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 2b49736..405e89e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2,6 +2,8 @@ basePath: /api/v1 definitions: requests.CreatePostRequest: properties: + category: + type: string content: minLength: 36 type: string @@ -9,6 +11,10 @@ definitions: maxLength: 255 minLength: 8 type: string + tags: + items: + type: string + type: array title: maxLength: 255 minLength: 8 @@ -35,6 +41,20 @@ definitions: - password - username type: object + requests.LoginUserRequest: + properties: + password: + maxLength: 32 + minLength: 6 + type: string + username: + maxLength: 32 + minLength: 3 + type: string + required: + - password + - username + type: object requests.PutPostRequest: properties: content: @@ -67,15 +87,36 @@ definitions: type: object responses.GetListPostResponseItem: properties: + category: + type: string description: type: string id: type: string + tags: + items: + type: string + type: array title: type: string + updatedAt: + type: string + userId: + type: string + username: + type: string + type: object + responses.ImageResponse: + properties: + id: + type: string + path: + type: string type: object responses.PostResponse: properties: + category: + type: string content: type: string createdAt: @@ -84,12 +125,18 @@ definitions: type: string id: type: string + tags: + items: + type: string + type: array title: type: string updatedAt: type: string userId: type: string + username: + type: string type: object responses.UserResponse: properties: @@ -112,13 +159,36 @@ info: title: 58team blog backend version: "1.0" paths: + /images/: + post: + description: Upload new image and returns uploaded image json object + parameters: + - description: image file + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.ImageResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Upload new image + tags: + - images /images/{path}: - get: - description: get image by path + delete: + description: Delete image from server by given path parameters: - description: Path to image - in: query - name: path + in: path + name: filename required: true type: string produces: @@ -127,7 +197,103 @@ paths: responses: "200": description: OK + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Delete image by path + tags: + - images + get: + description: get image by path + parameters: + - description: Path to image + in: path + name: path + required: true + type: string + produces: + - application/octet-stream + - application/json + responses: + "200": + description: OK + schema: + type: file + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' summary: Get an image by path + tags: + - images + /login: + post: + consumes: + - application/json + description: Login user into system + parameters: + - description: User login data + in: body + name: request + required: true + schema: + $ref: '#/definitions/requests.LoginUserRequest' + produces: + - application/json + responses: + "200": + description: OK + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Login + tags: + - user + /logout: + get: + description: Creates new user in system + produces: + - application/json + responses: + "200": + description: OK + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Create new user + tags: + - user /post: get: description: Return first 5 posts diff --git a/go.mod b/go.mod index 787c645..26f2ed0 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/creasty/defaults v1.8.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gin-contrib/sessions v1.0.4 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.10.1 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -28,6 +29,9 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/golang-migrate/migrate v3.5.4+incompatible // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.4.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 62676bc..8a8360b 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/appleboy/gin-jwt/v2 v2.10.3 h1:KNcPC+XPRNpuoBh+j+rgs5bQxN+SwG/0tHbIqpRoBGc= +github.com/appleboy/gin-jwt/v2 v2.10.3/go.mod h1:LDUaQ8mF2W6LyXIbd5wqlV2SFebuyYs4RDwqMNgpsp8= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w= @@ -24,6 +26,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U= +github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= @@ -60,11 +64,19 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -143,6 +155,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= diff --git a/images/1f3b3e94-7574-4e97-8cfb-35ab11744bca b/images/1f3b3e94-7574-4e97-8cfb-35ab11744bca new file mode 100644 index 0000000..f3f9fdc Binary files /dev/null and b/images/1f3b3e94-7574-4e97-8cfb-35ab11744bca differ diff --git a/images/79fed5e1-f1fa-40c1-9c5d-88aa257908a6 b/images/79fed5e1-f1fa-40c1-9c5d-88aa257908a6 new file mode 100644 index 0000000..f3f9fdc Binary files /dev/null and b/images/79fed5e1-f1fa-40c1-9c5d-88aa257908a6 differ diff --git a/internal/application/commands/create_post_command.go b/internal/application/commands/create_post_command.go index a9fb57b..6a9f5f2 100644 --- a/internal/application/commands/create_post_command.go +++ b/internal/application/commands/create_post_command.go @@ -7,4 +7,6 @@ type CreatePostCommand struct { Title string Description string Content string + Category string + Tags []string } diff --git a/internal/application/commands/create_posts_command.go b/internal/application/commands/create_posts_command.go deleted file mode 100644 index 12c26e4..0000000 --- a/internal/application/commands/create_posts_command.go +++ /dev/null @@ -1,8 +0,0 @@ -package commands - -import "github.com/google/uuid" - -type CreatePostsCommand struct { - PostId uuid.UUID - UserId uuid.UUID -} diff --git a/internal/application/commands/delete_posts_command.go b/internal/application/commands/delete_posts_command.go deleted file mode 100644 index 2247df0..0000000 --- a/internal/application/commands/delete_posts_command.go +++ /dev/null @@ -1,7 +0,0 @@ -package commands - -import "github.com/google/uuid" - -type DeletePostsCommand struct { - Id uuid.UUID -} diff --git a/internal/application/common/post_result.go b/internal/application/common/post_result.go index a31d9a2..6b6aea2 100644 --- a/internal/application/common/post_result.go +++ b/internal/application/common/post_result.go @@ -14,6 +14,8 @@ type PostResult struct { Content string CreatedAt time.Time UpdatedAt time.Time + Category string + Tags []string } type PostResultList struct { diff --git a/internal/application/common/posts_result.go b/internal/application/common/posts_result.go deleted file mode 100644 index 1a83dbb..0000000 --- a/internal/application/common/posts_result.go +++ /dev/null @@ -1,13 +0,0 @@ -package common - -import "github.com/google/uuid" - -type PostsResult struct { - Id uuid.UUID - UserId uuid.UUID - PostId uuid.UUID -} - -type PostsResultList struct { - Result []*PostsResult -} diff --git a/internal/application/errors/read_file_error.go b/internal/application/errors/read_file_error.go new file mode 100644 index 0000000..eb2685c --- /dev/null +++ b/internal/application/errors/read_file_error.go @@ -0,0 +1,15 @@ +package errors + +type ReadFileError struct { + msg string +} + +func NewReadFileError(msg string) *ReadFileError { + return &ReadFileError{ + msg: msg, + } +} + +func (e *ReadFileError) Error() string { + return "Read file error: " + e.msg +} diff --git a/internal/application/interfaces/posts_service.go b/internal/application/interfaces/posts_service.go deleted file mode 100644 index e720141..0000000 --- a/internal/application/interfaces/posts_service.go +++ /dev/null @@ -1,16 +0,0 @@ -package interfaces - -import ( - "58team_blog/internal/application/commands" - "58team_blog/internal/application/common" - "58team_blog/internal/application/queries" -) - -type PostsService interface { - Create(commands.CreatePostsCommand) (*common.PostsResult, error) - FindByUserId(queries.PostsFindByUserIdQuery) (*queries.PostsFindByUserIdResult, error) - FindByPostId(queries.PostsFindByPostIdQuery) (*queries.PostsFindByPostIdResult, error) - FindAllByUserId(queries.PostsFindByUserIdQuery) (*queries.PostsFindAllByUserIdResult, error) - GetAll() (queries.PostsGetAllResult, error) - Delete(commands.DeletePostsCommand) error -} diff --git a/internal/application/mapper/post_result.go b/internal/application/mapper/post_result.go index 2e2587f..e3d8dd6 100644 --- a/internal/application/mapper/post_result.go +++ b/internal/application/mapper/post_result.go @@ -15,6 +15,8 @@ func CreatePostResultFromEntity(entity *entities.Post) *common.PostResult { Content: entity.Content, CreatedAt: entity.CreatedAt, UpdatedAt: entity.UpdatedAt, + Category: entity.Category, + Tags: entity.Tags, } } diff --git a/internal/application/mapper/posts_result.go b/internal/application/mapper/posts_result.go deleted file mode 100644 index 35be7d9..0000000 --- a/internal/application/mapper/posts_result.go +++ /dev/null @@ -1,48 +0,0 @@ -package mapper - -import ( - "58team_blog/internal/application/common" - "58team_blog/internal/application/queries" - "58team_blog/internal/domain/entities" -) - -func CreatePostsResultFromEntity(entity *entities.Posts) *common.PostsResult { - return &common.PostsResult{ - Id: entity.Id, - UserId: entity.UserId, - PostId: entity.PostId, - } -} - -func CreatePostsResultListFromEntityList(entity_list []*entities.Posts) *common.PostsResultList { - var result common.PostsResultList - for _, e := range entity_list { - result.Result = append(result.Result, CreatePostsResultFromEntity(e)) - } - - return &result -} - -func CreatePostsFindByUserIdResultFromEntity(entity *entities.Posts) *queries.PostsFindByUserIdResult { - return &queries.PostsFindByUserIdResult{ - Result: CreatePostsResultFromEntity(entity), - } -} - -func CreatePostsFindByPostIdResultFromEntity(entity *entities.Posts) *queries.PostsFindByPostIdResult { - return &queries.PostsFindByPostIdResult{ - Result: CreatePostsResultFromEntity(entity), - } -} - -func CreatePostsFindAllByUserIdResultFromEntity(entity_list []*entities.Posts) *queries.PostsFindAllByUserIdResult { - return &queries.PostsFindAllByUserIdResult{ - Result: CreatePostsResultListFromEntityList(entity_list), - } -} - -func CreatePostsGetAllResultFromEntity(entity_list []*entities.Posts) *queries.PostsGetAllResult { - return &queries.PostsGetAllResult{ - Result: CreatePostsResultListFromEntityList(entity_list), - } -} diff --git a/internal/application/queries/posts_find_all_by_user_id.go b/internal/application/queries/posts_find_all_by_user_id.go deleted file mode 100644 index 5988fd1..0000000 --- a/internal/application/queries/posts_find_all_by_user_id.go +++ /dev/null @@ -1,15 +0,0 @@ -package queries - -import ( - "58team_blog/internal/application/common" - - "github.com/google/uuid" -) - -type PostsFindAllByUserIdQuery struct { - UserId uuid.UUID -} - -type PostsFindAllByUserIdResult struct { - Result *common.PostsResultList -} diff --git a/internal/application/queries/posts_find_by_post_id.go b/internal/application/queries/posts_find_by_post_id.go deleted file mode 100644 index 64cf98f..0000000 --- a/internal/application/queries/posts_find_by_post_id.go +++ /dev/null @@ -1,15 +0,0 @@ -package queries - -import ( - "58team_blog/internal/application/common" - - "github.com/google/uuid" -) - -type PostsFindByPostIdQuery struct { - PostId uuid.UUID -} - -type PostsFindByPostIdResult struct { - Result *common.PostsResult -} diff --git a/internal/application/queries/posts_find_by_user_id.go b/internal/application/queries/posts_find_by_user_id.go deleted file mode 100644 index 5ec14a1..0000000 --- a/internal/application/queries/posts_find_by_user_id.go +++ /dev/null @@ -1,15 +0,0 @@ -package queries - -import ( - "58team_blog/internal/application/common" - - "github.com/google/uuid" -) - -type PostsFindByUserIdQuery struct { - UserId uuid.UUID -} - -type PostsFindByUserIdResult struct { - Result *common.PostsResult -} diff --git a/internal/application/queries/posts_get_all.go b/internal/application/queries/posts_get_all.go deleted file mode 100644 index 6b7df62..0000000 --- a/internal/application/queries/posts_get_all.go +++ /dev/null @@ -1,7 +0,0 @@ -package queries - -import "58team_blog/internal/application/common" - -type PostsGetAllResult struct { - Result *common.PostsResultList -} diff --git a/internal/application/services/images_service.go b/internal/application/services/images_service.go index 62ba400..0aa9853 100644 --- a/internal/application/services/images_service.go +++ b/internal/application/services/images_service.go @@ -14,7 +14,7 @@ type ImagesService struct { repo repository.ImagesRepository } -func NewImagesService(repo repository.ImagesRepository) ImagesService { +func CreateImagesService(repo repository.ImagesRepository) ImagesService { return ImagesService{ repo: repo, } diff --git a/internal/application/services/post_service.go b/internal/application/services/post_service.go index 6d96880..94fc1f4 100644 --- a/internal/application/services/post_service.go +++ b/internal/application/services/post_service.go @@ -22,7 +22,7 @@ func CreatePostService(repo repository.PostRepository) PostService { } func (s *PostService) Create(cmd commands.CreatePostCommand) (*common.PostResult, error) { - entity, err := entities.CreatePost(cmd.UserId, cmd.Title, cmd.Description, cmd.Content) + entity, err := entities.CreatePost(cmd.UserId, cmd.Title, cmd.Description, cmd.Content, cmd.Category, cmd.Tags) if err != nil { return nil, errors.NewValidationError("Invalid input data " + err.Error()) } diff --git a/internal/application/services/posts_service.go b/internal/application/services/posts_service.go deleted file mode 100644 index 2cd33ee..0000000 --- a/internal/application/services/posts_service.go +++ /dev/null @@ -1,141 +0,0 @@ -package services - -import ( - "58team_blog/internal/application/commands" - "58team_blog/internal/application/common" - "58team_blog/internal/application/mapper" - "58team_blog/internal/application/queries" - "58team_blog/internal/domain/entities" - "58team_blog/internal/domain/repository" - "errors" - "fmt" -) - -type PostsService struct { - repo repository.PostsRepository -} - -func CreatePostsService(repo repository.PostsRepository) PostsService { - return PostsService{ - repo: repo, - } -} - -func (s *PostsService) Create(cmd commands.CreatePostsCommand) (*common.PostsResult, error) { - if user, err := s.repo.FindByPostId(cmd.PostId); user != nil { - if err != nil { - return nil, err - } - - return nil, errors.New("Posts already exists") - } - - entity, err := entities.CreatePosts(cmd.UserId, cmd.PostId) - if err != nil { - return nil, err - } - - if err := entity.Validate(); err != nil { - return nil, err - } - - result := mapper.CreatePostsResultFromEntity(&entity) - - return result, nil -} - -func (s *PostsService) FindByUserId(query queries.PostsFindByUserIdQuery) (*queries.PostsFindByUserIdResult, error) { - entity, err := s.repo.FindByUserId(query.UserId) - if err != nil { - return nil, err - } - - if entity == nil { - return nil, errors.New("Posts not found") - } - - if err := entity.Validate(); err != nil { - return nil, err - } - - result := mapper.CreatePostsFindByUserIdResultFromEntity(entity) - - return result, nil -} - -func (s *PostsService) FindByPostId(query queries.PostsFindByPostIdQuery) (*queries.PostsFindByPostIdResult, error) { - entity, err := s.repo.FindByPostId(query.PostId) - if err != nil { - return nil, err - } - - if entity == nil { - return nil, errors.New("Posts not found") - } - - if err := entity.Validate(); err != nil { - return nil, err - } - - result := mapper.CreatePostsFindByPostIdResultFromEntity(entity) - - return result, nil -} - -func (s *PostsService) FindAllByUserId(query queries.PostsFindByUserIdQuery) (*queries.PostsFindAllByUserIdResult, error) { - entities, err := s.repo.FindAllByUserId(query.UserId) - if err != nil { - return nil, err - } - - if entities == nil { - return nil, fmt.Errorf("No posts owned by user: %s", query.UserId.String()) - } - - for _, e := range entities { - if err := e.Validate(); err != nil { - return nil, err - } - } - - result := mapper.CreatePostsFindAllByUserIdResultFromEntity(entities) - - return result, nil -} - -func (s *PostsService) GetAll() (*queries.PostsGetAllResult, error) { - entities, err := s.repo.GetAll() - if err != nil { - return nil, err - } - - for _, e := range entities { - if err := e.Validate(); err != nil { - return nil, err - } - } - - result := mapper.CreatePostsGetAllResultFromEntity(entities) - return result, nil -} - -func (s *PostsService) Delete(cmd commands.DeletePostsCommand) error { - entity, err := s.repo.FindById(cmd.Id) - if err != nil { - return err - } - - if entity == nil { - return fmt.Errorf("Posts row not found: %s", cmd.Id) - } - - if err := entity.Validate(); err != nil { - return err - } - - if err := s.repo.Delete(cmd.Id); err != nil { - return err - } - - return nil -} diff --git a/internal/domain/entities/post.go b/internal/domain/entities/post.go index 5ac108b..918c284 100644 --- a/internal/domain/entities/post.go +++ b/internal/domain/entities/post.go @@ -5,21 +5,24 @@ import ( "time" "github.com/google/uuid" + "github.com/lib/pq" ) const PostTable = "post" type Post struct { - Id uuid.UUID `db:"id"` - UserId uuid.UUID `db:"userid"` - Title string `db:"title"` - Description string `db:"description"` - Content string `db:"content"` - CreatedAt time.Time `db:"createdat"` - UpdatedAt time.Time `db:"updatedat"` + Id uuid.UUID `db:"id"` + UserId uuid.UUID `db:"userid"` + Title string `db:"title"` + Description string `db:"description"` + Content string `db:"content"` + CreatedAt time.Time `db:"createdat"` + UpdatedAt time.Time `db:"updatedat"` + Category string `db:"category"` + Tags pq.StringArray `db:"tags"` // TODO: rewrite it to many2many } -func CreatePost(userId uuid.UUID, title string, description string, content string) (post Post, err error) { +func CreatePost(userId uuid.UUID, title string, description string, content string, category string, tags []string) (post Post, err error) { post = Post{ Id: uuid.New(), UserId: userId, @@ -28,6 +31,8 @@ func CreatePost(userId uuid.UUID, title string, description string, content stri Content: content, CreatedAt: time.Now(), UpdatedAt: time.Now(), + Category: category, + Tags: tags, } err = post.Validate() diff --git a/internal/domain/entities/posts.go b/internal/domain/entities/posts.go deleted file mode 100644 index 3c30c52..0000000 --- a/internal/domain/entities/posts.go +++ /dev/null @@ -1,43 +0,0 @@ -package entities - -import ( - "errors" - - "github.com/google/uuid" -) - -const PostsTable = "posts" - -type Posts struct { - Id uuid.UUID `db:"id"` - UserId uuid.UUID `db:"user_id"` - PostId uuid.UUID `db:"post_id"` -} - -func CreatePosts(userId uuid.UUID, postId uuid.UUID) (posts Posts, err error) { - posts = Posts{ - Id: uuid.New(), - UserId: userId, - PostId: postId, - } - - err = posts.Validate() - - return -} - -func (p *Posts) Validate() error { - if err := uuid.Validate(p.Id.String()); err != nil { - return errors.New("Invalid posts.id") - } - - if err := uuid.Validate(p.UserId.String()); err != nil { - return errors.New("Invalid posts.userId") - } - - if err := uuid.Validate(p.PostId.String()); err != nil { - return errors.New("Invalid posts.postId") - } - - return nil -} diff --git a/internal/domain/repository/posts_repo.go b/internal/domain/repository/posts_repo.go deleted file mode 100644 index 5dec120..0000000 --- a/internal/domain/repository/posts_repo.go +++ /dev/null @@ -1,17 +0,0 @@ -package repository - -import ( - "58team_blog/internal/domain/entities" - - "github.com/google/uuid" -) - -type PostsRepository interface { - Create(*entities.Posts) (*entities.Posts, error) - FindById(uuid.UUID) (*entities.Posts, error) - FindByPostId(uuid.UUID) (*entities.Posts, error) - FindByUserId(uuid.UUID) (*entities.Posts, error) - FindAllByUserId(uuid.UUID) ([]*entities.Posts, error) - GetAll() ([]*entities.Posts, error) - Delete(uuid.UUID) error -} diff --git a/internal/infrastructure/auth.go b/internal/infrastructure/auth.go new file mode 100644 index 0000000..89dcbf9 --- /dev/null +++ b/internal/infrastructure/auth.go @@ -0,0 +1,19 @@ +package infrastructure + +import ( + "net/http" + + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" +) + +func AuthRequired(c *gin.Context) { + session := sessions.Default(c) + + if user := session.Get("user"); user == nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } + + c.Next() +} diff --git a/internal/infrastructure/db/repo/post_repo.go b/internal/infrastructure/db/repo/post_repo.go index 6f99819..ffbf335 100644 --- a/internal/infrastructure/db/repo/post_repo.go +++ b/internal/infrastructure/db/repo/post_repo.go @@ -21,8 +21,8 @@ func CreatePostRepository(conn *db.Database) PostRepository { } func (r *PostRepository) Create(entity *entities.Post) (*entities.Post, error) { - query := "INSERT INTO " + entities.PostTable + " (id, userid, title, description, content, createdat, updatedat)" + - "VALUES (:id, :userid, :title, :description, :content, :createdat, :updatedat)" + query := "INSERT INTO " + entities.PostTable + " (id, userid, title, description, content, createdat, updatedat, category, tags)" + + "VALUES (:id, :userid, :title, :description, :content, :createdat, :updatedat, :category, :tags)" _, err := r.conn.Conn.NamedExec(query, entity) return entity, err @@ -49,9 +49,23 @@ func (r *PostRepository) FindById(id uuid.UUID) (*entities.Post, error) { func (r *PostRepository) FindAllByUserName(userName string) ([]*entities.Post, error) { var entity_list []*entities.Post + var id string + + user_query := "SELECT id FROM " + entities.UserTable + " WHERE username=?" + user_query, args, err := sqlx.In(user_query, userName) + if err != nil { + return nil, err + } + + user_query = r.conn.Conn.Rebind(user_query) + err = r.conn.Conn.Select(&id, user_query, args...) + if err != nil { + return nil, err + } + query := "SELECT * FROM " + entities.PostTable + " WHERE userid=?" - query, args, err := sqlx.In(query, userName) + query, args, err = sqlx.In(query, id) if err != nil { return nil, err } diff --git a/internal/infrastructure/db/repo/user_repo.go b/internal/infrastructure/db/repo/user_repo.go index 074998f..c1a968c 100644 --- a/internal/infrastructure/db/repo/user_repo.go +++ b/internal/infrastructure/db/repo/user_repo.go @@ -31,7 +31,7 @@ func (r *UserRepository) Create(entity *entities.User) (*entities.User, error) { } func (r *UserRepository) FindById(id uuid.UUID) (*entities.User, error) { - var entity *entities.User + var entity entities.User query := "SELECT * FROM " + entities.UserTable + " WHERE id=?" query, arg, err := sqlx.In(query, id) @@ -40,9 +40,9 @@ func (r *UserRepository) FindById(id uuid.UUID) (*entities.User, error) { } query = r.conn.Conn.Rebind(query) - err = r.conn.Conn.Select(entity, query, arg...) + err = r.conn.Conn.Get(&entity, query, arg...) - return entity, err + return &entity, err } func (r *UserRepository) FindByName(username string) (*entities.User, error) { diff --git a/internal/interfaces/api/controllers/images_controller.go b/internal/interfaces/api/controllers/images_controller.go index 8f7bc5f..801b51a 100644 --- a/internal/interfaces/api/controllers/images_controller.go +++ b/internal/interfaces/api/controllers/images_controller.go @@ -1,43 +1,155 @@ package controllers import ( + "58team_blog/internal/application/commands" + "58team_blog/internal/application/errors" "58team_blog/internal/application/services" + "58team_blog/internal/interfaces/api/mapper" + "58team_blog/internal/interfaces/api/responses" + "58team_blog/internal/utils" + "log" + "net/http" + "os" + "path/filepath" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) type ImagesController struct { - service *services.ImagesService + images_path string + service *services.ImagesService } -func CreateImagesController(service *services.ImagesService) ImagesController { +func CreateImagesController(images_path string, service *services.ImagesService) ImagesController { return ImagesController{ - service: service, + images_path: images_path, + service: service, } } -// get /images/{path} -// post /images -// delete /images/{id} +// @Summary Upload new image +// @Description Upload new image and returns uploaded image json object +// @Tags images +// @Produce json +// @Param file formData file true "image file" +// @Success 200 {object} responses.ImageResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /images/ [post] +func (r *ImagesController) PostImage(c *gin.Context) { + file, err := c.FormFile("file") + if err != nil { + resp := utils.HandleError(errors.NewNotFoundError("File not found")) + c.JSON(resp.ErrorCode, resp) + return + } + + uploadedFile, err := file.Open() + if err != nil { + resp := utils.HandleError(errors.NewReadFileError(err.Error())) + c.JSON(resp.ErrorCode, resp) + return + } + + // Read first 512 bytes for detect MIME-type + buffer := make([]byte, 512) + _, err = uploadedFile.Read(buffer) + if err != nil { + resp := utils.HandleError(errors.NewReadFileError(err.Error())) + c.JSON(resp.ErrorCode, resp) + return + } + + uploadedFile.Close() + + mimeType := http.DetectContentType(buffer) + + if !utils.IsImageMime(mimeType) { + resp := utils.HandleError(errors.NewValidationError("Unexpected file type. Expected: jpeg, png, gif, webp, bmp.")) + c.JSON(resp.ErrorCode, resp) + return + } + + cmd := commands.CreateImageCommand{ + Path: uuid.NewString(), + } + + image, err := r.service.Create(cmd) + if err != nil { + resp := utils.HandleError(errors.NewValidationError(err.Error())) + c.JSON(resp.ErrorCode, resp) + return + } + + c.SaveUploadedFile(file, r.images_path+"/"+image.Path) + + resp := mapper.ResponseFromImageResult(image) + + c.JSON(http.StatusOK, resp) +} // @Summary Get an image by path // @Description get image by path -// @Param path query string true "Path to image" +// @Tags images +// @Param path path string true "Path to image" +// @Produce octet-stream +// @Produce json +// @Success 200 {file} blob +// @Failure 400 {object} responses.ErrorResponse +// @Failure 404 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /images/{path} [get] +func (r *ImagesController) GetImage(c *gin.Context) { + filename := c.Param("path") + + filePath := filepath.Join(r.images_path, filename) + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + log.Println(err) + resp := responses.CreateErrorResponse(http.StatusNotFound, "Image not found") + c.JSON(resp.ErrorCode, resp) + return + } + + file, err := os.Open(filePath) + if err != nil { + log.Println(err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Cannot load image file from server") + c.JSON(resp.ErrorCode, resp) + return + } + + mimeData := make([]byte, 512) + if _, err := file.Read(mimeData); err != nil { + log.Println(err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Cannot load image from server") + c.JSON(resp.ErrorCode, resp) + return + } + + mimeType, err := utils.GetImageMimeType(mimeData) + if err != nil { + log.Println(err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, err.Error()) + c.JSON(resp.ErrorCode, resp) + return + } + + c.Header("Content-Type", mimeType) + c.File(filePath) +} + +// @Summary Delete image by path +// @Description Delete image from server by given path +// @Tags images +// @Param filename path string true "Path to image" // @Produce image/png // @Produce image/jpeg // @Success 200 -// @Router /images/{path} [get] -func (r *ImagesController) GetImage(c *gin.Context) { - // TODO: return image - panic("Not implemented") -} - -func (r *ImagesController) PostImage(c *gin.Context) { - // TODO: return image - panic("Not implemented") -} - +// @Failure 400 {object} responses.ErrorResponse +// @Failure 404 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /images/{path} [delete] func (r *ImagesController) DeleteImage(c *gin.Context) { - // TODO: return image panic("Not implemented") } diff --git a/internal/interfaces/api/controllers/post_controller.go b/internal/interfaces/api/controllers/post_controller.go index 742477b..069b3f9 100644 --- a/internal/interfaces/api/controllers/post_controller.go +++ b/internal/interfaces/api/controllers/post_controller.go @@ -17,12 +17,14 @@ import ( ) type PostController struct { - service *services.PostService + service *services.PostService + userService *services.UserService } -func CreatePostController(service *services.PostService) PostController { +func CreatePostController(service *services.PostService, userService *services.UserService) PostController { return PostController{ - service: service, + service: service, + userService: userService, } } @@ -61,6 +63,8 @@ func (r *PostController) Post(c *gin.Context) { Title: request.Title, Description: request.Description, Content: request.Content, + Category: request.Category, + Tags: request.Tags, } res, err := r.service.Create(cmd) @@ -70,7 +74,16 @@ func (r *PostController) Post(c *gin.Context) { return } + // Get username by userid + user, err := r.userService.FindById(queries.UserFindByIdQuery{Id: userId}) + if err != nil { + resp := utils.HandleError(err) + c.JSON(resp.ErrorCode, resp) + return + } + response := mapper.ResponseFromPostResult(res) + response.Username = user.Result.UserName c.JSON(http.StatusCreated, response) } @@ -94,6 +107,17 @@ func (r *PostController) GetAll(c *gin.Context) { res := mapper.ResponseFromPostGetAllResult(result) + for e, i := range res { + // Get username by userid + user, err := r.userService.FindById(queries.UserFindByIdQuery{Id: uuid.MustParse(i.UserId)}) + if err != nil { + resp := utils.HandleError(err) + c.JSON(resp.ErrorCode, resp) + return + } + res[e].Username = user.Result.UserName + } + c.JSON(http.StatusOK, res) } @@ -127,6 +151,17 @@ func (r *PostController) GetAllWithOffset(c *gin.Context) { res := mapper.ResponseFromPostGetAllResult(result) + for e, i := range res { + // Get username by userid + user, err := r.userService.FindById(queries.UserFindByIdQuery{Id: uuid.MustParse(i.UserId)}) + if err != nil { + resp := utils.HandleError(err) + c.JSON(resp.ErrorCode, resp) + return + } + res[e].Username = user.Result.UserName + } + c.JSON(http.StatusOK, res) } @@ -166,6 +201,14 @@ func (r *PostController) GetById(c *gin.Context) { result := mapper.ResponseFormPostFindByIdResult(posts) + user, err := r.userService.FindById(queries.UserFindByIdQuery{Id: uuid.MustParse(result.UserId)}) + if err != nil { + resp := utils.HandleError(err) + c.JSON(resp.ErrorCode, resp) + return + } + result.Username = user.Result.UserName + c.JSON(http.StatusOK, result) } diff --git a/internal/interfaces/api/controllers/user_controller.go b/internal/interfaces/api/controllers/user_controller.go index 790afbf..1db2a70 100644 --- a/internal/interfaces/api/controllers/user_controller.go +++ b/internal/interfaces/api/controllers/user_controller.go @@ -11,20 +11,117 @@ import ( "log" "net/http" + "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type UserController struct { - service *services.UserService + adminName string + adminPass string + service *services.UserService } -func CreateUserController(service *services.UserService) UserController { +func CreateUserController(service *services.UserService, adminName string, adminPass string) UserController { return UserController{ - service: service, + service: service, + adminName: adminName, + adminPass: adminPass, } } +// @Summary Login +// @Description Login user into system +// @Tags user +// @Accept json +// @Produce json +// @Param request body requests.LoginUserRequest true "User login data" +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse +// @Failure 401 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /login [post] +func (r *UserController) Login(c *gin.Context) { + session := sessions.Default(c) + + var request requests.LoginUserRequest + if err := c.BindJSON(&request); err != nil { + log.Println("User invalid request: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, err.Error()) + c.JSON(resp.ErrorCode, resp) + return + } + + // Check admin login + if request.Username == r.adminName && request.Password == r.adminPass { + session.Set("user", uuid.NewString()) + if err := session.Save(); err != nil { + log.Println("User save session error: ", err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.JSON(resp.ErrorCode, resp) + return + } + + c.Status(http.StatusOK) + return + } + + user, err := r.service.FindByName(queries.UserFindByNameQuery{Name: request.Username}) + if err != nil { + resp := utils.HandleError(err) + c.JSON(resp.ErrorCode, resp) + return + } + + pass, err := utils.EncryptPassword(request.Password) + if err != nil { + log.Println("User encrypt password error: ", err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.JSON(resp.ErrorCode, resp) + return + } + + if utils.CheckPassword(user.Result.Password, pass) { + log.Println("Pass ", user.Result.Password, " != ", pass) + resp := responses.CreateErrorResponse(http.StatusUnauthorized, "Authentication error") + c.JSON(resp.ErrorCode, resp) + return + } + + session.Set("user", user.Result.Id.String()) + if err := session.Save(); err != nil { + log.Println("User save session error: ", err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.JSON(resp.ErrorCode, resp) + return + } + + c.Status(http.StatusOK) +} + +// @Summary Create new user +// @Description Creates new user in system +// @Tags user +// @Produce json +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /logout [get] +func (r *UserController) Logout(c *gin.Context) { + session := sessions.Default(c) + user := session.Get("user") + if user == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid session token"}) + return + } + session.Delete("user") + if err := session.Save(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save session"}) + return + } + c.Status(http.StatusOK) +} + // @Summary Create new user // @Description Creates new user in system // @Tags user diff --git a/internal/interfaces/api/mapper/response_from_image_result.go b/internal/interfaces/api/mapper/response_from_image_result.go new file mode 100644 index 0000000..4c09b5c --- /dev/null +++ b/internal/interfaces/api/mapper/response_from_image_result.go @@ -0,0 +1,13 @@ +package mapper + +import ( + "58team_blog/internal/application/common" + "58team_blog/internal/interfaces/api/responses" +) + +func ResponseFromImageResult(result *common.ImageResult) responses.ImageResponse { + return responses.ImageResponse{ + Id: result.Id.String(), + Path: result.Path, + } +} diff --git a/internal/interfaces/api/mapper/response_from_post_find_by_id_result.go b/internal/interfaces/api/mapper/response_from_post_find_by_id_result.go index 86fb1bb..7e67115 100644 --- a/internal/interfaces/api/mapper/response_from_post_find_by_id_result.go +++ b/internal/interfaces/api/mapper/response_from_post_find_by_id_result.go @@ -15,5 +15,7 @@ func ResponseFormPostFindByIdResult(result *queries.PostFindByIdResult) response Content: res.Content, CreatedAt: res.CreatedAt, UpdatedAt: res.UpdatedAt, + Tags: res.Tags, + Category: res.Category, } } diff --git a/internal/interfaces/api/mapper/response_from_post_getall_result.go b/internal/interfaces/api/mapper/response_from_post_getall_result.go index faf0371..e2ffcc2 100644 --- a/internal/interfaces/api/mapper/response_from_post_getall_result.go +++ b/internal/interfaces/api/mapper/response_from_post_getall_result.go @@ -9,8 +9,12 @@ import ( func itemFromResult(item *common.PostResult) responses.GetListPostResponseItem { return responses.GetListPostResponseItem{ Id: item.Id.String(), + UserId: item.UserId.String(), Title: item.Title, Description: item.Description, + UpdatedAt: item.UpdatedAt.String(), + Tags: item.Tags, + Category: item.Category, } } diff --git a/internal/interfaces/api/mapper/response_from_post_result.go b/internal/interfaces/api/mapper/response_from_post_result.go index 86ba7c6..f4b6927 100644 --- a/internal/interfaces/api/mapper/response_from_post_result.go +++ b/internal/interfaces/api/mapper/response_from_post_result.go @@ -14,5 +14,7 @@ func ResponseFromPostResult(result *common.PostResult) responses.PostResponse { Content: result.Content, CreatedAt: result.CreatedAt, UpdatedAt: result.UpdatedAt, + Category: result.Category, + Tags: result.Tags, } } diff --git a/internal/interfaces/api/requests/create_post_request.go b/internal/interfaces/api/requests/create_post_request.go index a552ebc..d933b1a 100644 --- a/internal/interfaces/api/requests/create_post_request.go +++ b/internal/interfaces/api/requests/create_post_request.go @@ -1,8 +1,10 @@ package requests type CreatePostRequest struct { - Title string `json:"title" validate:"required,min=8,max=255"` - Description string `json:"description" validate:"required,min=8,max=255"` - Content string `json:"content" validate:"required,min=36"` - UserId string `json:"userId" validate:"required,uuid5"` + Title string `json:"title" validate:"required,min=8,max=255"` + Description string `json:"description" validate:"required,min=8,max=255"` + Content string `json:"content" validate:"required,min=36"` + UserId string `json:"userId" validate:"required,uuid5"` + Category string `json:"category"` + Tags []string `json:"tags"` } diff --git a/internal/interfaces/api/requests/login_user_request.go b/internal/interfaces/api/requests/login_user_request.go new file mode 100644 index 0000000..36b00a9 --- /dev/null +++ b/internal/interfaces/api/requests/login_user_request.go @@ -0,0 +1,6 @@ +package requests + +type LoginUserRequest 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/get_list_post_response.go b/internal/interfaces/api/responses/get_list_post_response.go index 75c6be2..ad3c860 100644 --- a/internal/interfaces/api/responses/get_list_post_response.go +++ b/internal/interfaces/api/responses/get_list_post_response.go @@ -1,9 +1,14 @@ package responses type GetListPostResponseItem struct { - Id string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` + Id string `json:"id"` + UserId string `json:"userId"` + Title string `json:"title"` + Description string `json:"description"` + UpdatedAt string `json:"updatedAt"` + Tags []string `json:"tags"` + Category string `json:"category"` + Username string `json:"username"` } type GetListPostResponse []GetListPostResponseItem diff --git a/internal/interfaces/api/responses/image_response.go b/internal/interfaces/api/responses/image_response.go new file mode 100644 index 0000000..483df24 --- /dev/null +++ b/internal/interfaces/api/responses/image_response.go @@ -0,0 +1,6 @@ +package responses + +type ImageResponse struct { + Id string `json:"id"` + Path string `json:"path"` +} diff --git a/internal/interfaces/api/responses/post_response.go b/internal/interfaces/api/responses/post_response.go index b4fe9c4..0aa0bb9 100644 --- a/internal/interfaces/api/responses/post_response.go +++ b/internal/interfaces/api/responses/post_response.go @@ -10,6 +10,9 @@ type PostResponse struct { Content string `json:"content"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` + Tags []string `json:"tags"` + Category string `json:"category"` + Username string `json:"username"` } type PostResponseList []*PostResponse diff --git a/internal/interfaces/route.go b/internal/interfaces/route.go index 1dbafa1..de88e66 100644 --- a/internal/interfaces/route.go +++ b/internal/interfaces/route.go @@ -2,27 +2,41 @@ package interfaces import ( "58team_blog/internal/application/services" + "58team_blog/internal/infrastructure" "58team_blog/internal/interfaces/api/controllers" "github.com/gin-gonic/gin" ) -func BindPostAdmin(service *services.PostService, group *gin.RouterGroup) { - post := controllers.CreatePostController(service) +func BindPostAdmin(service *services.PostService, userService *services.UserService, group *gin.RouterGroup) { + post := controllers.CreatePostController(service, userService) g := group.Group("/post") - g.GET("/", post.GetAll) - g.GET("/offset/:offset", post.GetAllWithOffset) - g.GET("/:id", post.GetById) + g.Use(infrastructure.AuthRequired) + g.POST("/", post.Post) g.PUT("/:id", post.Put) g.DELETE("/:id", post.Delete) } -func BindUser(service *services.UserService, group *gin.RouterGroup) { - user := controllers.CreateUserController(service) +func BindPost(service *services.PostService, userService *services.UserService, group *gin.RouterGroup) { + post := controllers.CreatePostController(service, userService) + g := group.Group("/post") + + g.GET("/", post.GetAll) + g.GET("/offset/:offset", post.GetAllWithOffset) + g.GET("/:id", post.GetById) +} + +func BindUser(adminName string, adminPass string, service *services.UserService, group *gin.RouterGroup) { + user := controllers.CreateUserController(service, adminName, adminPass) + + group.POST("/login", user.Login) + group.GET("/logout", user.Logout) g := group.Group("/user/") + g.Use(infrastructure.AuthRequired) + g.POST("/", user.Post) g.GET("/", user.GetAll) g.GET("/:id", user.FindById) @@ -30,3 +44,12 @@ func BindUser(service *services.UserService, group *gin.RouterGroup) { g.PUT("/:id", user.Put) g.DELETE("/:id", user.Delete) } + +func BindImages(images_path string, service *services.ImagesService, group *gin.RouterGroup) { + images := controllers.CreateImagesController(images_path, service) + + g := group.Group("/images/") + g.POST("/", images.PostImage) + g.GET("/:path", images.GetImage) + g.DELETE("/:path", images.DeleteImage) +} diff --git a/internal/utils/error_handler.go b/internal/utils/error_handler.go index 5975535..f23271b 100644 --- a/internal/utils/error_handler.go +++ b/internal/utils/error_handler.go @@ -16,6 +16,8 @@ func HandleError(err error) responses.ErrorResponse { if errors.Is(&ie.ValidationError{}, err) { errorCode = http.StatusBadRequest + } else if errors.Is(&ie.ReadFileError{}, err) { + errorCode = http.StatusInternalServerError } else if errors.Is(&ie.NotFoundError{}, err) { errorCode = http.StatusNotFound } else if errors.Is(&ie.AlreadyExistsError{}, err) { diff --git a/internal/utils/mime_image.go b/internal/utils/mime_image.go new file mode 100644 index 0000000..6a05a4c --- /dev/null +++ b/internal/utils/mime_image.go @@ -0,0 +1,29 @@ +package utils + +import ( + "errors" + "net/http" +) + +var allowedTypes = map[string]bool{ + "image/jpeg": true, + "image/jpg": true, + "image/png": true, + "image/gif": true, + "image/webp": true, + "image/bmp": true, +} + +func IsImageMime(data string) bool { + return allowedTypes[data] +} + +func GetImageMimeType(data []byte) (string, error) { + content_type := http.DetectContentType(data) + + if !IsImageMime(content_type) { + return "", errors.New("Unexpected image format.") + } + + return content_type, nil +} diff --git a/internal/utils/password_crypt.go b/internal/utils/password_crypt.go index 4f63565..a64489c 100644 --- a/internal/utils/password_crypt.go +++ b/internal/utils/password_crypt.go @@ -6,9 +6,10 @@ import ( "golang.org/x/crypto/bcrypt" ) +const salt = "58_team:%s:1205secret" + func EncryptPassword(pass string) (string, error) { var salted string - salt := "58_team:%s:1205secret" salted = fmt.Sprintf(salt, pass) @@ -19,3 +20,14 @@ func EncryptPassword(pass string) (string, error) { return string(hashed), nil } + +func CheckPassword(pass_hashed string, pass string) bool { + salted := fmt.Sprintf(salt, pass) + err := bcrypt.CompareHashAndPassword([]byte(pass_hashed), []byte(salted)) + + if err != nil { + return false + } + + return true +} diff --git a/migrations/00002_update_post.down.sql b/migrations/00002_update_post.down.sql new file mode 100644 index 0000000..85a76d6 --- /dev/null +++ b/migrations/00002_update_post.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE post DROP COLUMN category; +ALTER TABLE post DROP COLUMN tags; diff --git a/migrations/00002_update_post.up.sql b/migrations/00002_update_post.up.sql new file mode 100644 index 0000000..6e65710 --- /dev/null +++ b/migrations/00002_update_post.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE post ADD COLUMN category TEXT; +ALTER TABLE post ADD COLUMN tags TEXT[]; diff --git a/migrations/00003_update_old_posts.up.sql b/migrations/00003_update_old_posts.up.sql new file mode 100644 index 0000000..0d0f778 --- /dev/null +++ b/migrations/00003_update_old_posts.up.sql @@ -0,0 +1,2 @@ +UPDATE post SET category = '' WHERE category = NULL; +UPDATE post SET tags = '{}' WHERE tags = NULL;