Added authorization
This commit is contained in:
parent
c3c3d65d32
commit
b96dd39795
50 changed files with 685 additions and 410 deletions
|
|
@ -7,4 +7,6 @@ type CreatePostCommand struct {
|
|||
Title string
|
||||
Description string
|
||||
Content string
|
||||
Category string
|
||||
Tags []string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package commands
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type CreatePostsCommand struct {
|
||||
PostId uuid.UUID
|
||||
UserId uuid.UUID
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package commands
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type DeletePostsCommand struct {
|
||||
Id uuid.UUID
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ type PostResult struct {
|
|||
Content string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Category string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
type PostResultList struct {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
15
internal/application/errors/read_file_error.go
Normal file
15
internal/application/errors/read_file_error.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package queries
|
||||
|
||||
import "58team_blog/internal/application/common"
|
||||
|
||||
type PostsGetAllResult struct {
|
||||
Result *common.PostsResultList
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
19
internal/infrastructure/auth.go
Normal file
19
internal/infrastructure/auth.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
13
internal/interfaces/api/mapper/response_from_image_result.go
Normal file
13
internal/interfaces/api/mapper/response_from_image_result.go
Normal 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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
6
internal/interfaces/api/requests/login_user_request.go
Normal file
6
internal/interfaces/api/requests/login_user_request.go
Normal 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"`
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
6
internal/interfaces/api/responses/image_response.go
Normal file
6
internal/interfaces/api/responses/image_response.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package responses
|
||||
|
||||
type ImageResponse struct {
|
||||
Id string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
29
internal/utils/mime_image.go
Normal file
29
internal/utils/mime_image.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue