Added authorization

This commit is contained in:
KamilM1205 2025-09-25 09:01:00 +04:00
parent c3c3d65d32
commit b96dd39795
50 changed files with 685 additions and 410 deletions

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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

View file

@ -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,
}
}

View file

@ -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,
}
}

View file

@ -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,
}
}

View file

@ -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,
}
}

View file

@ -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"`
}

View file

@ -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"`
}

View file

@ -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

View file

@ -0,0 +1,6 @@
package responses
type ImageResponse struct {
Id string `json:"id"`
Path string `json:"path"`
}

View file

@ -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

View file

@ -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)
}