Added authorization
This commit is contained in:
parent
c3c3d65d32
commit
b96dd39795
50 changed files with 685 additions and 410 deletions
14
cmd/main.go
14
cmd/main.go
|
|
@ -23,6 +23,8 @@ import (
|
||||||
"58team_blog/internal/utils"
|
"58team_blog/internal/utils"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
@ -30,9 +32,14 @@ import (
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const secret = "58secret"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Setup cookie container
|
||||||
|
router.Use(sessions.Sessions("session", cookie.NewStore([]byte(secret))))
|
||||||
|
|
||||||
// Register custom validators
|
// Register custom validators
|
||||||
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
v.RegisterValidation("password", utils.PasswordValidator)
|
v.RegisterValidation("password", utils.PasswordValidator)
|
||||||
|
|
@ -64,12 +71,15 @@ func main() {
|
||||||
|
|
||||||
postRepository := repo.CreatePostRepository(d)
|
postRepository := repo.CreatePostRepository(d)
|
||||||
userRepository := repo.CreateUserRepository(d)
|
userRepository := repo.CreateUserRepository(d)
|
||||||
|
imagesRepository := repo.CreateImagesRepository(d)
|
||||||
|
|
||||||
postService := services.CreatePostService(&postRepository)
|
postService := services.CreatePostService(&postRepository)
|
||||||
userService := services.CreateUserService(&userRepository)
|
userService := services.CreateUserService(&userRepository)
|
||||||
|
imagesService := services.CreateImagesService(&imagesRepository)
|
||||||
|
|
||||||
interfaces.BindPostAdmin(&postService, g)
|
interfaces.BindPostAdmin(&postService, &userService, g)
|
||||||
interfaces.BindUser(&userService, g)
|
interfaces.BindUser(config.AdminName, config.AdminPassword, &userService, g)
|
||||||
|
interfaces.BindImages(config.ImagesPath, &imagesService, g)
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ db-password: 1205
|
||||||
db-host: localhost
|
db-host: localhost
|
||||||
db-port: 5432
|
db-port: 5432
|
||||||
admin_name: muts
|
admin_name: muts
|
||||||
admin_pass: 1205
|
admin_pass: Abc1205
|
||||||
images_path: ./images/
|
images_path: ./images/
|
||||||
posts_path: ./posts/
|
posts_path: ./posts/
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,8 @@ basePath: /api/v1
|
||||||
definitions:
|
definitions:
|
||||||
requests.CreatePostRequest:
|
requests.CreatePostRequest:
|
||||||
properties:
|
properties:
|
||||||
|
category:
|
||||||
|
type: string
|
||||||
content:
|
content:
|
||||||
minLength: 36
|
minLength: 36
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -9,6 +11,10 @@ definitions:
|
||||||
maxLength: 255
|
maxLength: 255
|
||||||
minLength: 8
|
minLength: 8
|
||||||
type: string
|
type: string
|
||||||
|
tags:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
title:
|
title:
|
||||||
maxLength: 255
|
maxLength: 255
|
||||||
minLength: 8
|
minLength: 8
|
||||||
|
|
@ -35,6 +41,20 @@ definitions:
|
||||||
- password
|
- password
|
||||||
- username
|
- username
|
||||||
type: object
|
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:
|
requests.PutPostRequest:
|
||||||
properties:
|
properties:
|
||||||
content:
|
content:
|
||||||
|
|
@ -67,15 +87,36 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
responses.GetListPostResponseItem:
|
responses.GetListPostResponseItem:
|
||||||
properties:
|
properties:
|
||||||
|
category:
|
||||||
|
type: string
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
|
tags:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
userId:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
responses.ImageResponse:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
responses.PostResponse:
|
responses.PostResponse:
|
||||||
properties:
|
properties:
|
||||||
|
category:
|
||||||
|
type: string
|
||||||
content:
|
content:
|
||||||
type: string
|
type: string
|
||||||
createdAt:
|
createdAt:
|
||||||
|
|
@ -84,12 +125,18 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
|
tags:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
userId:
|
userId:
|
||||||
type: string
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
responses.UserResponse:
|
responses.UserResponse:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -112,13 +159,36 @@ info:
|
||||||
title: 58team blog backend
|
title: 58team blog backend
|
||||||
version: "1.0"
|
version: "1.0"
|
||||||
paths:
|
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}:
|
/images/{path}:
|
||||||
get:
|
delete:
|
||||||
description: get image by path
|
description: Delete image from server by given path
|
||||||
parameters:
|
parameters:
|
||||||
- description: Path to image
|
- description: Path to image
|
||||||
in: query
|
in: path
|
||||||
name: path
|
name: filename
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
produces:
|
produces:
|
||||||
|
|
@ -127,7 +197,103 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
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
|
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:
|
/post:
|
||||||
get:
|
get:
|
||||||
description: Return first 5 posts
|
description: Return first 5 posts
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -14,6 +14,7 @@ require (
|
||||||
github.com/creasty/defaults v1.8.0 // indirect
|
github.com/creasty/defaults v1.8.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.10 // 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-contrib/sse v1.1.0 // indirect
|
||||||
github.com/gin-gonic/gin v1.10.1 // indirect
|
github.com/gin-gonic/gin v1.10.1 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // 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/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/golang-migrate/migrate v3.5.4+incompatible // indirect
|
github.com/golang-migrate/migrate v3.5.4+incompatible // indirect
|
||||||
github.com/google/uuid v1.6.0 // 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/jmoiron/sqlx v1.4.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
|
|
||||||
14
go.sum
14
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/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 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
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 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
|
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/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 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
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 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
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/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 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
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 h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
|
||||||
github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
|
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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
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/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 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
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=
|
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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
|
|
||||||
BIN
images/1f3b3e94-7574-4e97-8cfb-35ab11744bca
Normal file
BIN
images/1f3b3e94-7574-4e97-8cfb-35ab11744bca
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
BIN
images/79fed5e1-f1fa-40c1-9c5d-88aa257908a6
Normal file
BIN
images/79fed5e1-f1fa-40c1-9c5d-88aa257908a6
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
|
|
@ -7,4 +7,6 @@ type CreatePostCommand struct {
|
||||||
Title string
|
Title string
|
||||||
Description string
|
Description string
|
||||||
Content 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
|
Content string
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
Category string
|
||||||
|
Tags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostResultList struct {
|
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,
|
Content: entity.Content,
|
||||||
CreatedAt: entity.CreatedAt,
|
CreatedAt: entity.CreatedAt,
|
||||||
UpdatedAt: entity.UpdatedAt,
|
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
|
repo repository.ImagesRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImagesService(repo repository.ImagesRepository) ImagesService {
|
func CreateImagesService(repo repository.ImagesRepository) ImagesService {
|
||||||
return ImagesService{
|
return ImagesService{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ func CreatePostService(repo repository.PostRepository) PostService {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PostService) Create(cmd commands.CreatePostCommand) (*common.PostResult, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.NewValidationError("Invalid input data " + err.Error())
|
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"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PostTable = "post"
|
const PostTable = "post"
|
||||||
|
|
||||||
type Post struct {
|
type Post struct {
|
||||||
Id uuid.UUID `db:"id"`
|
Id uuid.UUID `db:"id"`
|
||||||
UserId uuid.UUID `db:"userid"`
|
UserId uuid.UUID `db:"userid"`
|
||||||
Title string `db:"title"`
|
Title string `db:"title"`
|
||||||
Description string `db:"description"`
|
Description string `db:"description"`
|
||||||
Content string `db:"content"`
|
Content string `db:"content"`
|
||||||
CreatedAt time.Time `db:"createdat"`
|
CreatedAt time.Time `db:"createdat"`
|
||||||
UpdatedAt time.Time `db:"updatedat"`
|
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{
|
post = Post{
|
||||||
Id: uuid.New(),
|
Id: uuid.New(),
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
|
|
@ -28,6 +31,8 @@ func CreatePost(userId uuid.UUID, title string, description string, content stri
|
||||||
Content: content,
|
Content: content,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
|
Category: category,
|
||||||
|
Tags: tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = post.Validate()
|
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) {
|
func (r *PostRepository) Create(entity *entities.Post) (*entities.Post, error) {
|
||||||
query := "INSERT INTO " + entities.PostTable + " (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)"
|
"VALUES (:id, :userid, :title, :description, :content, :createdat, :updatedat, :category, :tags)"
|
||||||
_, err := r.conn.Conn.NamedExec(query, entity)
|
_, err := r.conn.Conn.NamedExec(query, entity)
|
||||||
|
|
||||||
return entity, err
|
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) {
|
func (r *PostRepository) FindAllByUserName(userName string) ([]*entities.Post, error) {
|
||||||
var entity_list []*entities.Post
|
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 := "SELECT * FROM " + entities.PostTable + " WHERE userid=?"
|
||||||
|
|
||||||
query, args, err := sqlx.In(query, userName)
|
query, args, err = sqlx.In(query, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 := "SELECT * FROM " + entities.UserTable + " WHERE id=?"
|
||||||
query, arg, err := sqlx.In(query, 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)
|
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) {
|
func (r *UserRepository) FindByName(username string) (*entities.User, error) {
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,155 @@
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"58team_blog/internal/application/commands"
|
||||||
|
"58team_blog/internal/application/errors"
|
||||||
"58team_blog/internal/application/services"
|
"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/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ImagesController struct {
|
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{
|
return ImagesController{
|
||||||
service: service,
|
images_path: images_path,
|
||||||
|
service: service,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get /images/{path}
|
// @Summary Upload new image
|
||||||
// post /images
|
// @Description Upload new image and returns uploaded image json object
|
||||||
// delete /images/{id}
|
// @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
|
// @Summary Get an image by path
|
||||||
// @Description get 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/png
|
||||||
// @Produce image/jpeg
|
// @Produce image/jpeg
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Router /images/{path} [get]
|
// @Failure 400 {object} responses.ErrorResponse
|
||||||
func (r *ImagesController) GetImage(c *gin.Context) {
|
// @Failure 404 {object} responses.ErrorResponse
|
||||||
// TODO: return image
|
// @Failure 500 {object} responses.ErrorResponse
|
||||||
panic("Not implemented")
|
// @Router /images/{path} [delete]
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ImagesController) PostImage(c *gin.Context) {
|
|
||||||
// TODO: return image
|
|
||||||
panic("Not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ImagesController) DeleteImage(c *gin.Context) {
|
func (r *ImagesController) DeleteImage(c *gin.Context) {
|
||||||
// TODO: return image
|
|
||||||
panic("Not implemented")
|
panic("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type PostController struct {
|
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{
|
return PostController{
|
||||||
service: service,
|
service: service,
|
||||||
|
userService: userService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,6 +63,8 @@ func (r *PostController) Post(c *gin.Context) {
|
||||||
Title: request.Title,
|
Title: request.Title,
|
||||||
Description: request.Description,
|
Description: request.Description,
|
||||||
Content: request.Content,
|
Content: request.Content,
|
||||||
|
Category: request.Category,
|
||||||
|
Tags: request.Tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := r.service.Create(cmd)
|
res, err := r.service.Create(cmd)
|
||||||
|
|
@ -70,7 +74,16 @@ func (r *PostController) Post(c *gin.Context) {
|
||||||
return
|
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 := mapper.ResponseFromPostResult(res)
|
||||||
|
response.Username = user.Result.UserName
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, response)
|
c.JSON(http.StatusCreated, response)
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +107,17 @@ func (r *PostController) GetAll(c *gin.Context) {
|
||||||
|
|
||||||
res := mapper.ResponseFromPostGetAllResult(result)
|
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)
|
c.JSON(http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,6 +151,17 @@ func (r *PostController) GetAllWithOffset(c *gin.Context) {
|
||||||
|
|
||||||
res := mapper.ResponseFromPostGetAllResult(result)
|
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)
|
c.JSON(http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,6 +201,14 @@ func (r *PostController) GetById(c *gin.Context) {
|
||||||
|
|
||||||
result := mapper.ResponseFormPostFindByIdResult(posts)
|
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)
|
c.JSON(http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,117 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserController struct {
|
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{
|
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
|
// @Summary Create new user
|
||||||
// @Description Creates new user in system
|
// @Description Creates new user in system
|
||||||
// @Tags user
|
// @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,
|
Content: res.Content,
|
||||||
CreatedAt: res.CreatedAt,
|
CreatedAt: res.CreatedAt,
|
||||||
UpdatedAt: res.UpdatedAt,
|
UpdatedAt: res.UpdatedAt,
|
||||||
|
Tags: res.Tags,
|
||||||
|
Category: res.Category,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,12 @@ import (
|
||||||
func itemFromResult(item *common.PostResult) responses.GetListPostResponseItem {
|
func itemFromResult(item *common.PostResult) responses.GetListPostResponseItem {
|
||||||
return responses.GetListPostResponseItem{
|
return responses.GetListPostResponseItem{
|
||||||
Id: item.Id.String(),
|
Id: item.Id.String(),
|
||||||
|
UserId: item.UserId.String(),
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Description: item.Description,
|
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,
|
Content: result.Content,
|
||||||
CreatedAt: result.CreatedAt,
|
CreatedAt: result.CreatedAt,
|
||||||
UpdatedAt: result.UpdatedAt,
|
UpdatedAt: result.UpdatedAt,
|
||||||
|
Category: result.Category,
|
||||||
|
Tags: result.Tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package requests
|
package requests
|
||||||
|
|
||||||
type CreatePostRequest struct {
|
type CreatePostRequest struct {
|
||||||
Title string `json:"title" validate:"required,min=8,max=255"`
|
Title string `json:"title" validate:"required,min=8,max=255"`
|
||||||
Description string `json:"description" validate:"required,min=8,max=255"`
|
Description string `json:"description" validate:"required,min=8,max=255"`
|
||||||
Content string `json:"content" validate:"required,min=36"`
|
Content string `json:"content" validate:"required,min=36"`
|
||||||
UserId string `json:"userId" validate:"required,uuid5"`
|
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
|
package responses
|
||||||
|
|
||||||
type GetListPostResponseItem struct {
|
type GetListPostResponseItem struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Title string `json:"title"`
|
UserId string `json:"userId"`
|
||||||
Description string `json:"description"`
|
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
|
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"`
|
Content string `json:"content"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostResponseList []*PostResponse
|
type PostResponseList []*PostResponse
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,41 @@ package interfaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"58team_blog/internal/application/services"
|
"58team_blog/internal/application/services"
|
||||||
|
"58team_blog/internal/infrastructure"
|
||||||
"58team_blog/internal/interfaces/api/controllers"
|
"58team_blog/internal/interfaces/api/controllers"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BindPostAdmin(service *services.PostService, group *gin.RouterGroup) {
|
func BindPostAdmin(service *services.PostService, userService *services.UserService, group *gin.RouterGroup) {
|
||||||
post := controllers.CreatePostController(service)
|
post := controllers.CreatePostController(service, userService)
|
||||||
|
|
||||||
g := group.Group("/post")
|
g := group.Group("/post")
|
||||||
g.GET("/", post.GetAll)
|
g.Use(infrastructure.AuthRequired)
|
||||||
g.GET("/offset/:offset", post.GetAllWithOffset)
|
|
||||||
g.GET("/:id", post.GetById)
|
|
||||||
g.POST("/", post.Post)
|
g.POST("/", post.Post)
|
||||||
g.PUT("/:id", post.Put)
|
g.PUT("/:id", post.Put)
|
||||||
g.DELETE("/:id", post.Delete)
|
g.DELETE("/:id", post.Delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BindUser(service *services.UserService, group *gin.RouterGroup) {
|
func BindPost(service *services.PostService, userService *services.UserService, group *gin.RouterGroup) {
|
||||||
user := controllers.CreateUserController(service)
|
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 := group.Group("/user/")
|
||||||
|
g.Use(infrastructure.AuthRequired)
|
||||||
|
|
||||||
g.POST("/", user.Post)
|
g.POST("/", user.Post)
|
||||||
g.GET("/", user.GetAll)
|
g.GET("/", user.GetAll)
|
||||||
g.GET("/:id", user.FindById)
|
g.GET("/:id", user.FindById)
|
||||||
|
|
@ -30,3 +44,12 @@ func BindUser(service *services.UserService, group *gin.RouterGroup) {
|
||||||
g.PUT("/:id", user.Put)
|
g.PUT("/:id", user.Put)
|
||||||
g.DELETE("/:id", user.Delete)
|
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) {
|
if errors.Is(&ie.ValidationError{}, err) {
|
||||||
errorCode = http.StatusBadRequest
|
errorCode = http.StatusBadRequest
|
||||||
|
} else if errors.Is(&ie.ReadFileError{}, err) {
|
||||||
|
errorCode = http.StatusInternalServerError
|
||||||
} else if errors.Is(&ie.NotFoundError{}, err) {
|
} else if errors.Is(&ie.NotFoundError{}, err) {
|
||||||
errorCode = http.StatusNotFound
|
errorCode = http.StatusNotFound
|
||||||
} else if errors.Is(&ie.AlreadyExistsError{}, err) {
|
} 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"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const salt = "58_team:%s:1205secret"
|
||||||
|
|
||||||
func EncryptPassword(pass string) (string, error) {
|
func EncryptPassword(pass string) (string, error) {
|
||||||
var salted string
|
var salted string
|
||||||
salt := "58_team:%s:1205secret"
|
|
||||||
|
|
||||||
salted = fmt.Sprintf(salt, pass)
|
salted = fmt.Sprintf(salt, pass)
|
||||||
|
|
||||||
|
|
@ -19,3 +20,14 @@ func EncryptPassword(pass string) (string, error) {
|
||||||
|
|
||||||
return string(hashed), nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
2
migrations/00002_update_post.down.sql
Normal file
2
migrations/00002_update_post.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE post DROP COLUMN category;
|
||||||
|
ALTER TABLE post DROP COLUMN tags;
|
||||||
2
migrations/00002_update_post.up.sql
Normal file
2
migrations/00002_update_post.up.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE post ADD COLUMN category TEXT;
|
||||||
|
ALTER TABLE post ADD COLUMN tags TEXT[];
|
||||||
2
migrations/00003_update_old_posts.up.sql
Normal file
2
migrations/00003_update_old_posts.up.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
UPDATE post SET category = '' WHERE category = NULL;
|
||||||
|
UPDATE post SET tags = '{}' WHERE tags = NULL;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue