diff --git a/.go-arch-lint.yml b/.go-arch-lint.yml index b1fffd2..533f45f 100644 --- a/.go-arch-lint.yml +++ b/.go-arch-lint.yml @@ -5,9 +5,11 @@ allow: components: domain: { in: internal/domain/** } + repository: {in: internal/domain/repository/**} application: { in: internal/application/** } + commands: { in: internal/application/} infrastructure: { in: internal/infrastructure/** } - interface: { in: internal/interface/** } + interface: { in: internal/interfaces/** } cmd: {in: cmd/**} commonComponents: diff --git a/cmd/main.go b/cmd/main.go index 8fc17f4..f1cbeb2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,31 +1,36 @@ package main -// @title 58team blog backend -// @version 1.0 -// @description 58team blog's backend -// @termsOfService http://swagger.io/terms/ - -// @contact.name API Support -// @contact.url http://www.swagger.io/support -// @contact.email support@swagger.io - -// @license.name Apache 2.0 -// @license.url http://www.apache.org/licenses/LICENSE-2.0.html - -// @host localhost:8080 -// @BasePath /api/v1 - -// @securityDefinitions.basic BasicAuth +// @title 58team blog backend +// @version 1.0 +// @description 58team blog's backend +// @termsOfService http://swagger.io/terms/ +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html +// @host localhost:8080 +// @BasePath /api/v1 +// @securityDefinitions.basic BasicAuth import ( - "58team_blog/docs" + docs "58team_blog/docs" + "58team_blog/internal/application/services" + "58team_blog/internal/infrastructure" + "58team_blog/internal/infrastructure/db" + "58team_blog/internal/infrastructure/db/repo" + "58team_blog/internal/interfaces" + "log" + "github.com/gin-gonic/gin" - "github.com/swaggo/files" - "github.com/swaggo/gin-swagger" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" ) func main() { router := gin.Default() + + // Swagger setup docs.SwaggerInfo.Title = "58team blog" docs.SwaggerInfo.Description = "This is blog 58team" docs.SwaggerInfo.Version = "1.0" @@ -33,6 +38,33 @@ func main() { docs.SwaggerInfo.BasePath = "/api/v1/" docs.SwaggerInfo.Schemes = []string{"http", "https"} - router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + // router.GET("/swagger/*any", func(c *gin.Context) { + // path := c.Param("any") + // if strings.HasPrefix(path, "/doc.json") { + // c.File("docs/swagger.json") + // } else { + // + // } + // }) + router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("http://localhost:8080/swag/doc/doc.json"))) + router.StaticFile("/swag/doc/doc.json", "docs/swagger.json") + + // Routes setup + g := router.Group("/api/v1") + + config, err := infrastructure.LoadConfig() + if err != nil { + log.Fatal("Load config error: ", err) + } + + d, err := db.DatabaseInit(*config) + if err != nil { + log.Fatal("Database error: ", err) + } + postRepository := repo.CreatePostRepository(d) + + postService := services.CreatePostService(&postRepository) + interfaces.BindPostAdmin(&postService, g) + router.Run(":8080") } diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..3666efd --- /dev/null +++ b/config.yaml @@ -0,0 +1,10 @@ +db-user: userpg +db-name: 58blog +db-password: 1205 +db-host: localhost +db-port: 5432 +admin_name: muts +admin_pass: 1205 +images_path: ./images/ +posts_path: ./posts/ + diff --git a/docs/docs.go b/docs/docs.go index 77da281..969fa70 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,35 +1,11 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT +// Code generated by swaggo/swag. DO NOT EDIT. + package docs -import "github.com/swaggo/swag" +import "github.com/swaggo/swag/v2" const docTemplate = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "{{escape .Description}}", - "title": "{{.Title}}", - "termsOfService": "http://swagger.io/terms/", - "contact": { - "name": "API Support", - "url": "http://www.swagger.io/support", - "email": "support@swagger.io" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "{{.Version}}" - }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", - "paths": {}, - "securityDefinitions": { - "BasicAuth": { - "type": "basic" - } - } -}` + "schemes": {{ marshal .Schemes }},"swagger":"2.0","info":{"description":"{{escape .Description}}","title":"{{.Title}}","termsOfService":"http://swagger.io/terms/","contact":{"name":"API Support","url":"http://www.swagger.io/support","email":"support@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"{{.Version}}"},"host":"{{.Host}}","basePath":"{{.BasePath}}","paths":{"/images/{path}":{"get":{"description":"get image by path","produces":["image/png","image/jpeg"],"summary":"Get an image by path","parameters":[{"type":"string","description":"Path to image","name":"path","in":"query","required":true}],"responses":{"200":{"description":"OK"}}}},"/post":{"get":{"description":"Return first 5 posts","produces":["application/json"],"tags":["post"],"summary":"Get all posts","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}}}},"post":{"description":"Create new post in blog","consumes":["application/json"],"produces":["application/json"],"tags":["post"],"summary":"Create new post","parameters":[{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.CreatePostRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/{id}":{"get":{"description":"get post by id","produces":["application/json"],"tags":["post"],"summary":"Get post by id","parameters":[{"type":"string","description":"Id of post","name":"id","in":"query","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.PostResponse"}}}}},"put":{"description":"update post content","produces":["application/json"],"tags":["post"],"summary":"Update post content","parameters":[{"type":"string","description":"Id of post","name":"id","in":"query","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}}}}},"/post/{offset}":{"get":{"description":"return 5 posts after first offset posts","produces":["application/json"],"tags":["post"],"summary":"Get posts after offset","parameters":[{"type":"integer","description":"Offset of posts","name":"offset","in":"query","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}}}}}},"definitions":{"requests.CreatePostRequest":{"type":"object","required":["content","description","title","userId"],"properties":{"content":{"type":"string","minLength":36},"description":{"type":"string","maxLength":255,"minLength":8},"title":{"type":"string","maxLength":255,"minLength":8},"userId":{"type":"string"}}},"responses.ErrorResponse":{"type":"object","properties":{"error_code":{"type":"integer"},"message":{"type":"string"}}},"responses.GetListPostResponseItem":{"type":"object","properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}}},"responses.PostResponse":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}}},"securityDefinitions":{"BasicAuth":{"type":"basic"}}}` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ diff --git a/docs/swagger.json b/docs/swagger.json index 4651580..a76a0f6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,26 +1 @@ -{ - "swagger": "2.0", - "info": { - "description": "58team blog's backend", - "title": "58team blog backend", - "termsOfService": "http://swagger.io/terms/", - "contact": { - "name": "API Support", - "url": "http://www.swagger.io/support", - "email": "support@swagger.io" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "1.0" - }, - "host": "localhost:8080", - "basePath": "/api/v1", - "paths": {}, - "securityDefinitions": { - "BasicAuth": { - "type": "basic" - } - } -} \ No newline at end of file +{"swagger":"2.0","info":{"description":"58team blog's backend","title":"58team blog backend","termsOfService":"http://swagger.io/terms/","contact":{"name":"API Support","url":"http://www.swagger.io/support","email":"support@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0"},"host":"localhost:8080","basePath":"/api/v1","paths":{"/images/{path}":{"get":{"description":"get image by path","produces":["image/png","image/jpeg"],"summary":"Get an image by path","parameters":[{"type":"string","description":"Path to image","name":"path","in":"query","required":true}],"responses":{"200":{"description":"OK"}}}},"/post":{"get":{"description":"Return first 5 posts","produces":["application/json"],"tags":["post"],"summary":"Get all posts","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}}}},"post":{"description":"Create new post in blog","consumes":["application/json"],"produces":["application/json"],"tags":["post"],"summary":"Create new post","parameters":[{"description":"Post data","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/requests.CreatePostRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/responses.ErrorResponse"}}}}},"/post/{id}":{"get":{"description":"get post by id","produces":["application/json"],"tags":["post"],"summary":"Get post by id","parameters":[{"type":"string","description":"Id of post","name":"id","in":"query","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.PostResponse"}}}}},"put":{"description":"update post content","produces":["application/json"],"tags":["post"],"summary":"Update post content","parameters":[{"type":"string","description":"Id of post","name":"id","in":"query","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/responses.PostResponse"}}}}},"/post/{offset}":{"get":{"description":"return 5 posts after first offset posts","produces":["application/json"],"tags":["post"],"summary":"Get posts after offset","parameters":[{"type":"integer","description":"Offset of posts","name":"offset","in":"query","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/responses.GetListPostResponseItem"}}}}}}},"definitions":{"requests.CreatePostRequest":{"type":"object","required":["content","description","title","userId"],"properties":{"content":{"type":"string","minLength":36},"description":{"type":"string","maxLength":255,"minLength":8},"title":{"type":"string","maxLength":255,"minLength":8},"userId":{"type":"string"}}},"responses.ErrorResponse":{"type":"object","properties":{"error_code":{"type":"integer"},"message":{"type":"string"}}},"responses.GetListPostResponseItem":{"type":"object","properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}}},"responses.PostResponse":{"type":"object","properties":{"content":{"type":"string"},"createdAt":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"updatedAt":{"type":"string"},"userId":{"type":"string"}}}},"securityDefinitions":{"BasicAuth":{"type":"basic"}}} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 4b5918b..5e7f99e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,4 +1,59 @@ basePath: /api/v1 +definitions: + requests.CreatePostRequest: + properties: + content: + minLength: 36 + type: string + description: + maxLength: 255 + minLength: 8 + type: string + title: + maxLength: 255 + minLength: 8 + type: string + userId: + type: string + required: + - content + - description + - title + - userId + type: object + responses.ErrorResponse: + properties: + error_code: + type: integer + message: + type: string + type: object + responses.GetListPostResponseItem: + properties: + description: + type: string + id: + type: string + title: + type: string + type: object + responses.PostResponse: + properties: + content: + type: string + createdAt: + type: string + description: + type: string + id: + type: string + title: + type: string + updatedAt: + type: string + userId: + type: string + type: object host: localhost:8080 info: contact: @@ -12,7 +67,123 @@ info: termsOfService: http://swagger.io/terms/ title: 58team blog backend version: "1.0" -paths: {} +paths: + /images/{path}: + get: + description: get image by path + parameters: + - description: Path to image + in: query + name: path + required: true + type: string + produces: + - image/png + - image/jpeg + responses: + "200": + description: OK + summary: Get an image by path + /post: + get: + description: Return first 5 posts + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/responses.GetListPostResponseItem' + type: array + summary: Get all posts + tags: + - post + post: + consumes: + - application/json + description: Create new post in blog + parameters: + - description: Post data + in: body + name: request + required: true + schema: + $ref: '#/definitions/requests.CreatePostRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.PostResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Create new post + tags: + - post + /post/{id}: + get: + description: get post by id + parameters: + - description: Id of post + in: query + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/responses.PostResponse' + type: array + summary: Get post by id + tags: + - post + put: + description: update post content + parameters: + - description: Id of post + in: query + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.PostResponse' + summary: Update post content + tags: + - post + /post/{offset}: + get: + description: return 5 posts after first offset posts + parameters: + - description: Offset of posts + in: query + name: offset + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/responses.GetListPostResponseItem' + type: array + summary: Get posts after offset + tags: + - post securityDefinitions: BasicAuth: type: basic diff --git a/go-arch-lint-graph.svg b/go-arch-lint-graph.svg new file mode 100755 index 0000000..855288a --- /dev/null +++ b/go-arch-lint-graph.svg @@ -0,0 +1,106 @@ + + + + + + + + +cmdapplicationinfrastructureinterface + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod index 79fc64b..787c645 100644 --- a/go.mod +++ b/go.mod @@ -16,12 +16,13 @@ require ( github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.10.1 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/spec v0.20.4 // indirect - github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator v9.31.0+incompatible // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.5 // indirect @@ -33,11 +34,12 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect @@ -45,9 +47,11 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/sv-tools/openapi v0.2.1 // indirect github.com/swaggo/files v1.0.1 // indirect github.com/swaggo/gin-swagger v1.6.1 // indirect github.com/swaggo/swag v1.16.6 // indirect + github.com/swaggo/swag/v2 v2.0.0-rc4 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect diff --git a/go.sum b/go.sum index ef84c3c..62676bc 100644 --- a/go.sum +++ b/go.sum @@ -31,17 +31,28 @@ github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= @@ -65,6 +76,7 @@ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzh github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -76,6 +88,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= @@ -87,6 +101,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= @@ -111,6 +127,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/sv-tools/openapi v0.2.1 h1:ES1tMQMJFGibWndMagvdoo34T1Vllxr1Nlm5wz6b1aA= +github.com/sv-tools/openapi v0.2.1/go.mod h1:k5VuZamTw1HuiS9p2Wl5YIDWzYnHG6/FgPOSFXLAhGg= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= @@ -119,6 +137,8 @@ github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w= github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its= github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +github.com/swaggo/swag/v2 v2.0.0-rc4 h1:SZ8cK68gcV6cslwrJMIOqPkJELRwq4gmjvk77MrvHvY= +github.com/swaggo/swag/v2 v2.0.0-rc4/go.mod h1:Ow7Y8gF16BTCDn8YxZbyKn8FkMLRUHekv1kROJZpbvE= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 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= @@ -177,6 +197,7 @@ google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXn gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/application/services/post_service.go b/internal/application/services/post_service.go index 665f372..ed45e85 100644 --- a/internal/application/services/post_service.go +++ b/internal/application/services/post_service.go @@ -7,18 +7,17 @@ import ( "58team_blog/internal/application/queries" "58team_blog/internal/domain/entities" "58team_blog/internal/domain/repository" + "fmt" "time" ) type PostService struct { - repo repository.PostRepository - postsService PostsService + repo repository.PostRepository } -func CreatePostService(repo repository.PostRepository, postsService PostsService) PostService { +func CreatePostService(repo repository.PostRepository) PostService { return PostService{ - repo: repo, - postsService: postsService, + repo: repo, } } @@ -30,7 +29,7 @@ func (s *PostService) Create(cmd commands.CreatePostCommand) (*common.PostResult post, err := s.repo.Create(&entity) if err != nil { - return nil, err + return nil, fmt.Errorf("Db error: %s", err) } result := mapper.CreatePostResultFromEntity(post) diff --git a/internal/domain/entities/post.go b/internal/domain/entities/post.go index bb2b752..5ac108b 100644 --- a/internal/domain/entities/post.go +++ b/internal/domain/entities/post.go @@ -11,12 +11,12 @@ const PostTable = "post" type Post struct { Id uuid.UUID `db:"id"` - UserId uuid.UUID `db:"user_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"` + CreatedAt time.Time `db:"createdat"` + UpdatedAt time.Time `db:"updatedat"` } func CreatePost(userId uuid.UUID, title string, description string, content string) (post Post, err error) { diff --git a/internal/infrastructure/config.go b/internal/infrastructure/config.go index 68e1b39..e0540c5 100644 --- a/internal/infrastructure/config.go +++ b/internal/infrastructure/config.go @@ -17,10 +17,10 @@ type Config struct { PostsPath string `mapstructure:"posts_path" default:"./posts/"` } -func LoadConfig() (config Config, err error) { - config = Config{} +func LoadConfig() (config *Config, err error) { + config = &Config{} if err = defaults.Set(config); err != nil { - return + return nil, err } viper.SetConfigName("config") @@ -30,12 +30,12 @@ func LoadConfig() (config Config, err error) { viper.AddConfigPath("/58team_blog/cfgs/") if err = viper.ReadInConfig(); err != nil { - return + return nil, err } if err = viper.Unmarshal(&config); err != nil { - return + return nil, err } - return + return config, nil } diff --git a/internal/infrastructure/db.go b/internal/infrastructure/db.go deleted file mode 100644 index f83c2ce..0000000 --- a/internal/infrastructure/db.go +++ /dev/null @@ -1,19 +0,0 @@ -package infrastructure - -import ( - "fmt" - - "github.com/jmoiron/sqlx" -) - -type Database struct { - connection *sqlx.DB -} - -func DatabaseInit(config Config) (db Database, err error) { - db = Database{} - db_setup := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s", config.DBUser, config.DBPass, config.DBHost, config.DBPort, config.DBName) - db.connection, err = sqlx.Connect("postgres", db_setup) - - return -} diff --git a/internal/infrastructure/db/db.go b/internal/infrastructure/db/db.go new file mode 100644 index 0000000..7b24126 --- /dev/null +++ b/internal/infrastructure/db/db.go @@ -0,0 +1,25 @@ +package db + +import ( + "58team_blog/internal/infrastructure" + "fmt" + + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" +) + +type Database struct { + Conn *sqlx.DB +} + +func DatabaseInit(config infrastructure.Config) (db *Database, err error) { + db = &Database{} + + db_setup := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=disable", config.DBUser, config.DBPass, config.DBHost, config.DBPort, config.DBName) + db.Conn, err = sqlx.Connect("postgres", db_setup) + if err != nil { + return nil, err + } + + return db, nil +} diff --git a/internal/infrastructure/db/repo/images_repo.go b/internal/infrastructure/db/repo/images_repo.go new file mode 100644 index 0000000..0e2256b --- /dev/null +++ b/internal/infrastructure/db/repo/images_repo.go @@ -0,0 +1,58 @@ +package repo + +import ( + "58team_blog/internal/domain/entities" + "58team_blog/internal/infrastructure/db" + + "github.com/google/uuid" + "github.com/jmoiron/sqlx" +) + +type ImagesRepository struct { + conn *db.Database +} + +func CreateImagesRepository(conn *db.Database) ImagesRepository { + return ImagesRepository{ + conn: conn, + } +} + +func (r *ImagesRepository) Create(entity *entities.Images) error { + query := "INSERT INTO " + entities.ImagesTable + "(id, path) VALUES (:id, :path)" + _, err := r.conn.Conn.NamedExec(query, entity) + + return err +} + +func (r *ImagesRepository) FindById(id uuid.UUID) (*entities.Images, error) { + var entity *entities.Images + + query := "SELECT * FROM " + entities.ImagesTable + " WHERE id = ?" + query, args, err := sqlx.In(query, id) + if err != nil { + return nil, err + } + + query = r.conn.Conn.Rebind(query) + err = r.conn.Conn.Get(entity, query, args...) + if err != nil { + return nil, err + } + + return entity, nil +} + +func (r *ImagesRepository) Delete(id uuid.UUID) error { + query := "DELETE FROM " + entities.ImagesTable + " WHERE id=?" + + query, args, err := sqlx.In(query, id) + if err != nil { + return err + } + + query = r.conn.Conn.Rebind(query) + _, err = r.conn.Conn.Query(query, args...) + + return err +} diff --git a/internal/infrastructure/db/repo/post_repo.go b/internal/infrastructure/db/repo/post_repo.go new file mode 100644 index 0000000..c66e918 --- /dev/null +++ b/internal/infrastructure/db/repo/post_repo.go @@ -0,0 +1,88 @@ +package repo + +import ( + "58team_blog/internal/domain/entities" + "58team_blog/internal/infrastructure/db" + + "github.com/google/uuid" + "github.com/jmoiron/sqlx" +) + +type PostRepository struct { + conn *db.Database +} + +func CreatePostRepository(conn *db.Database) PostRepository { + return PostRepository{ + conn: conn, + } +} + +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)" + _, err := r.conn.Conn.NamedExec(query, entity) + + return entity, err +} + +func (r *PostRepository) FindById(id uuid.UUID) (*entities.Post, error) { + var entity *entities.Post + query := "SELECT * FROM " + entities.PostTable + " WHERE id=?" + + query, args, err := sqlx.In(query, id) + if err != nil { + return nil, err + } + + query = r.conn.Conn.Rebind(query) + err = r.conn.Conn.Get(entity, query, args) + + return entity, err +} + +func (r *PostRepository) FindAllByUserName(userName string) ([]*entities.Post, error) { + var entity_list []*entities.Post + query := "SELECT * FROM " + entities.PostTable + " WHERE userid=?" + + query, args, err := sqlx.In(query, userName) + if err != nil { + return nil, err + } + + query = r.conn.Conn.Rebind(query) + err = r.conn.Conn.Select(entity_list, query, args...) + + return entity_list, err +} + +func (r *PostRepository) GetAll() ([]*entities.Post, error) { + var entity_list []*entities.Post + query := "SELECT * FROM " + entities.PostTable + " ORDER BY createdat, updatedat LIMIT 5;" + + err := r.conn.Conn.Select(&entity_list, query) + + return entity_list, err +} + +func (r *PostRepository) Update(entity *entities.Post) error { + query := "UPDATE " + entities.PostTable + "SET title=:title, description=:description, content=:content, updatedat=:updatedat WHERE id=:id" + + _, err := r.conn.Conn.NamedExec(query, entity) + + return err +} + +func (r *PostRepository) Delete(id uuid.UUID) error { + query := "DELETE FROM " + entities.PostTable + " WHERE id=?" + + query, args, err := sqlx.In(query, id) + if err != nil { + return err + } + + query = r.conn.Conn.Rebind(query) + _, err = r.conn.Conn.Exec(query, args...) + + return err +} diff --git a/internal/infrastructure/db/repo/user_repo.go b/internal/infrastructure/db/repo/user_repo.go new file mode 100644 index 0000000..d421407 --- /dev/null +++ b/internal/infrastructure/db/repo/user_repo.go @@ -0,0 +1,94 @@ +package repo + +import ( + "58team_blog/internal/domain/entities" + "58team_blog/internal/infrastructure/db" + + "github.com/google/uuid" + "github.com/jmoiron/sqlx" +) + +type UserRepository struct { + conn *db.Database +} + +func CreateUserRepository(conn *db.Database) UserRepository { + return UserRepository{ + conn: conn, + } +} + +func (r *UserRepository) Create(entity *entities.User) (*entities.User, error) { + query := "INSERT INTO " + entities.UserTable + "(id, username, password) VALUES (:id, :username, :password)" + _, err := r.conn.Conn.NamedExec(query, entity) + + if err != nil { + return nil, err + } + + return entity, nil +} + +func (r *UserRepository) FindById(id uuid.UUID) (*entities.User, error) { + var entity *entities.User + + query := "SELECT * FROM " + entities.UserTable + " WHERE id=?" + query, arg, err := sqlx.In(query, id) + if err != nil { + return nil, err + } + + query = r.conn.Conn.Rebind(query) + err = r.conn.Conn.Get(entity, query, arg...) + + return entity, err +} + +func (r *UserRepository) FindByName(username string) (*entities.User, error) { + var entity *entities.User + + query := "SELECT * FROM " + entities.UserTable + " WHERE username=?" + query, arg, err := sqlx.In(query, username) + if err != nil { + return nil, err + } + + query = r.conn.Conn.Rebind(query) + err = r.conn.Conn.Get(entity, query, arg...) + + return entity, err +} + +func (r *UserRepository) GetAll() ([]*entities.User, error) { + var entity_list []*entities.User + + query := "SELECT * FROM " + entities.UserTable + err := r.conn.Conn.Select(entity_list, query) + if err != nil { + return nil, err + } + + return entity_list, nil +} + +func (r *UserRepository) Update(user *entities.User) error { + query := "UPDATE " + entities.UserTable + " SET username=:username, password=:password WHERE id=:id" + + _, err := r.conn.Conn.NamedExec(query, user) + + return err +} + +func (r *UserRepository) Delete(id uuid.UUID) error { + query := "DELETE FROM " + entities.UserTable + " WHERE id=?" + + query, arg, err := sqlx.In(query, id) + if err != nil { + return err + } + + query = r.conn.Conn.Rebind(query) + _, err = r.conn.Conn.Exec(query, arg...) + + return err +} diff --git a/internal/infrastructure/infrastructure.go b/internal/infrastructure/infrastructure.go deleted file mode 100644 index c10af3a..0000000 --- a/internal/infrastructure/infrastructure.go +++ /dev/null @@ -1,20 +0,0 @@ -package infrastructure - -type Infrastructure struct { - Config Config - Db Database -} - -func InfrastructureInit() (infra Infrastructure, err error) { - infra = Infrastructure{} - - if infra.Config, err = LoadConfig(); err != nil { - return - } - - if infra.Db, err = DatabaseInit(infra.Config); err != nil { - return - } - - return -} diff --git a/internal/interfaces/api/controllers/images_controller.go b/internal/interfaces/api/controllers/images_controller.go new file mode 100644 index 0000000..8f7bc5f --- /dev/null +++ b/internal/interfaces/api/controllers/images_controller.go @@ -0,0 +1,43 @@ +package controllers + +import ( + "58team_blog/internal/application/services" + + "github.com/gin-gonic/gin" +) + +type ImagesController struct { + service *services.ImagesService +} + +func CreateImagesController(service *services.ImagesService) ImagesController { + return ImagesController{ + service: service, + } +} + +// get /images/{path} +// post /images +// delete /images/{id} + +// @Summary Get an image by path +// @Description get image by path +// @Param path query 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") +} + +func (r *ImagesController) DeleteImage(c *gin.Context) { + // TODO: return image + panic("Not implemented") +} diff --git a/internal/interfaces/api/controllers/post_controller.go b/internal/interfaces/api/controllers/post_controller.go new file mode 100644 index 0000000..5b1c3f1 --- /dev/null +++ b/internal/interfaces/api/controllers/post_controller.go @@ -0,0 +1,182 @@ +package controllers + +import ( + "58team_blog/internal/application/commands" + "58team_blog/internal/application/queries" + "58team_blog/internal/application/services" + "58team_blog/internal/interfaces/api/dto" + "58team_blog/internal/interfaces/api/requests" + "58team_blog/internal/interfaces/api/responses" + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +type PostController struct { + service *services.PostService +} + +func CreatePostController(service *services.PostService) PostController { + return PostController{ + service: service, + } +} + +// PostPost godoc +// +// @Summary Create new post +// @Description Create new post in blog +// @Tags post +// @Accept json +// @Produce json +// @Param request body requests.CreatePostRequest true "Post data" +// @Success 200 {object} responses.PostResponse +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /post [post] +func (r *PostController) Post(c *gin.Context) { + var request requests.CreatePostRequest + + if err := c.BindJSON(&request); err != nil { + log.Println(err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "BadRequest") + c.IndentedJSON(resp.ErrorCode, resp) + return + } + + userId, err := uuid.Parse(request.UserId) + if err != nil { + log.Println(err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Incorrect user id") + c.IndentedJSON(resp.ErrorCode, resp) + return + } + + cmd := commands.CreatePostCommand{ + UserId: userId, + Title: request.Title, + Description: request.Description, + Content: request.Content, + } + + res, err := r.service.Create(cmd) + if err != nil { + log.Println(err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.IndentedJSON(resp.ErrorCode, resp) + return + } + + response := dto.ResponseFromPostResult(res) + + c.IndentedJSON(http.StatusOK, response) +} + +// GetAllPost godoc +// +// @Summary Get all posts +// @Description Return first 5 posts +// @Tags post +// @Produce json +// @Success 200 {array} responses.GetListPostResponseItem +// @Failure 500 {object} responses.ErrorResponse +// @Router /post [get] +func (r *PostController) GetAll(c *gin.Context) { + result, err := r.service.GetAll() + if err != nil { + log.Println(err) + resp := responses.CreateErrorResponse(http.StatusInternalServerError, "Internal server error") + c.IndentedJSON(resp.ErrorCode, resp) + return + } + + res := dto.ResponseFromPostGetAllResult(result) + + c.JSON(http.StatusOK, res) +} + +// GetAllWithOffsetPost godoc +// @Summary Get posts after offset +// @Description return 5 posts after first offset posts +// @Tags post +// @Param offset query int true "Offset of posts" +// @Produce json +// @Success 200 {array} responses.GetListPostResponseItem +// @Router /post/{offset} [get] +func (r *PostController) GetAllWithOffset(c *gin.Context) { + +} + +// GetByIdPost godoc +// @Summary Get post by id +// @Description get post by id +// @Tags post +// @Param id query string true "Id of post" +// @Produce json +// @Success 200 {array} responses.PostResponse +// @Failure 400 {object} responses.ErrorResponse +// @Router /post/{id} [get] +func (r *PostController) GetById(c *gin.Context) { + id := c.Param("id") + + id_valid, err := uuid.Parse(id) + if err != nil { + log.Println("User get by id error: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Invalid user id") + c.JSON(resp.ErrorCode, resp) + return + } + + query := queries.PostFindByIdQuery{ + Id: id_valid, + } + + posts, err := r.service.FindById(query) + if err != nil { + log.Println("Post service error: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Bad request") + c.JSON(resp.ErrorCode, resp) + return + } + + result := dto.ResponseFormPostFindByIdResult(posts) + + c.JSON(http.StatusOK, result) +} + +// PutPost godoc +// @Summary Update post content +// @Description update post content +// @Tags post +// @Param id query string true "Id of post" +// +// @Param request body requests.PutPostRequest true "Post data" +// +// @Produce json +// @Success 200 {object} responses.PostResponse +// @Failure 400 {object} responses.ErrorResponse +// @Router /post/{id} [put] +func (r *PostController) Put(c *gin.Context) { + var request requests.PutPostRequest + + if err := c.BindJSON(request); err != nil { + log.Println("Post request error: ", err) + resp := responses.CreateErrorResponse(http.StatusBadRequest, "Bad request") + c.JSON(resp.ErrorCode, resp) + } +} + +// Delete godoc +// @Summary Delete post +// @Description Delete post by id +// @Tags post +// @Param id query string true "Id of post" +// @Produce json +// @Success 200 +// @Router /post/{id} [delete] + +func (r *PostController) Delete(c *gin.Context) { + +} diff --git a/internal/interfaces/api/controllers/user_controller.go b/internal/interfaces/api/controllers/user_controller.go new file mode 100644 index 0000000..42a2af8 --- /dev/null +++ b/internal/interfaces/api/controllers/user_controller.go @@ -0,0 +1,50 @@ +package controllers + +import ( + "58team_blog/internal/application/services" + + "github.com/gin-gonic/gin" +) + +type UserController struct { + service *services.UserService +} + +func CreateUserController(service *services.UserService) UserController { + return UserController{ + service: service, + } +} + +// @Summary Create new user +// @Description Creates new user in system +// @Param path query string true "Path to image" +// @Produce +// @Success 200 +// @Router /images/{path} [get] +func (r *UserController) Post(c *gin.Context) { + // TODO: return image + panic("Not implemented") +} + +func (r *UserController) FindById(c *gin.Context) { + +} + +func (r *UserController) FindByName(c *gin.Context) { + +} + +func (r *UserController) GetAll(c *gin.Context) { + // TODO: return image + panic("Not implemented") +} + +func (r *UserController) Put(c *gin.Context) { + +} + +func (r *UserController) Delete(c *gin.Context) { + // TODO: return image + panic("Not implemented") +} diff --git a/internal/interfaces/api/dto/response_from_post_find_by_id_result.go b/internal/interfaces/api/dto/response_from_post_find_by_id_result.go new file mode 100644 index 0000000..9c03af8 --- /dev/null +++ b/internal/interfaces/api/dto/response_from_post_find_by_id_result.go @@ -0,0 +1,19 @@ +package dto + +import ( + "58team_blog/internal/application/queries" + "58team_blog/internal/interfaces/api/responses" +) + +func ResponseFormPostFindByIdResult(result *queries.PostFindByIdResult) responses.PostResponse { + res := result.Result + return responses.PostResponse{ + Id: res.Id.String(), + UserId: res.UserId.String(), + Title: res.Title, + Description: res.Description, + Content: res.Content, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, + } +} diff --git a/internal/interfaces/api/dto/response_from_post_getall_result.go b/internal/interfaces/api/dto/response_from_post_getall_result.go new file mode 100644 index 0000000..0eb6bb2 --- /dev/null +++ b/internal/interfaces/api/dto/response_from_post_getall_result.go @@ -0,0 +1,25 @@ +package dto + +import ( + "58team_blog/internal/application/common" + "58team_blog/internal/application/queries" + "58team_blog/internal/interfaces/api/responses" +) + +func itemFromResult(item *common.PostResult) responses.GetListPostResponseItem { + return responses.GetListPostResponseItem{ + Id: item.Id.String(), + Title: item.Title, + Description: item.Description, + } +} + +func ResponseFromPostGetAllResult(result *queries.PostGetAllResult) responses.GetListPostResponse { + var resp []responses.GetListPostResponseItem + + for _, r := range result.Result.Result { + resp = append(resp, itemFromResult(r)) + } + + return resp +} diff --git a/internal/interfaces/api/dto/response_from_post_result.go b/internal/interfaces/api/dto/response_from_post_result.go new file mode 100644 index 0000000..7702fcc --- /dev/null +++ b/internal/interfaces/api/dto/response_from_post_result.go @@ -0,0 +1,18 @@ +package dto + +import ( + "58team_blog/internal/application/common" + "58team_blog/internal/interfaces/api/responses" +) + +func ResponseFromPostResult(result *common.PostResult) responses.PostResponse { + return responses.PostResponse{ + Id: result.Id.String(), + UserId: result.UserId.String(), + Title: result.Title, + Description: result.Description, + Content: result.Content, + CreatedAt: result.CreatedAt, + UpdatedAt: result.UpdatedAt, + } +} diff --git a/internal/interfaces/api/images_controller.go b/internal/interfaces/api/images_controller.go deleted file mode 100644 index 778f64e..0000000 --- a/internal/interfaces/api/images_controller.go +++ /dev/null @@ -1 +0,0 @@ -package api diff --git a/internal/interfaces/api/post_controller.go b/internal/interfaces/api/post_controller.go deleted file mode 100644 index 778f64e..0000000 --- a/internal/interfaces/api/post_controller.go +++ /dev/null @@ -1 +0,0 @@ -package api diff --git a/internal/interfaces/api/requests/create_post_request.go b/internal/interfaces/api/requests/create_post_request.go new file mode 100644 index 0000000..a552ebc --- /dev/null +++ b/internal/interfaces/api/requests/create_post_request.go @@ -0,0 +1,8 @@ +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"` +} diff --git a/internal/interfaces/api/requests/put_post_response.go b/internal/interfaces/api/requests/put_post_response.go new file mode 100644 index 0000000..ca14660 --- /dev/null +++ b/internal/interfaces/api/requests/put_post_response.go @@ -0,0 +1,7 @@ +package requests + +type PutPostRequest struct { + Title string `json:"title"` + Description string `json:"description"` + Content string `json:"content"` +} diff --git a/internal/interfaces/api/responses/error_response.go b/internal/interfaces/api/responses/error_response.go new file mode 100644 index 0000000..c2b8754 --- /dev/null +++ b/internal/interfaces/api/responses/error_response.go @@ -0,0 +1,13 @@ +package responses + +type ErrorResponse struct { + ErrorCode int `json:"error_code"` + Message string `json:"message"` +} + +func CreateErrorResponse(code int, msg string) ErrorResponse { + return ErrorResponse{ + ErrorCode: code, + Message: msg, + } +} diff --git a/internal/interfaces/api/responses/get_list_post_response.go b/internal/interfaces/api/responses/get_list_post_response.go new file mode 100644 index 0000000..75c6be2 --- /dev/null +++ b/internal/interfaces/api/responses/get_list_post_response.go @@ -0,0 +1,9 @@ +package responses + +type GetListPostResponseItem struct { + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` +} + +type GetListPostResponse []GetListPostResponseItem diff --git a/internal/interfaces/api/responses/post_response.go b/internal/interfaces/api/responses/post_response.go new file mode 100644 index 0000000..b4fe9c4 --- /dev/null +++ b/internal/interfaces/api/responses/post_response.go @@ -0,0 +1,15 @@ +package responses + +import "time" + +type PostResponse struct { + Id string `json:"id"` + UserId string `json:"userId"` + Title string `json:"title"` + Description string `json:"description"` + Content string `json:"content"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type PostResponseList []*PostResponse diff --git a/internal/interfaces/api/user_controller.go b/internal/interfaces/api/user_controller.go deleted file mode 100644 index 778f64e..0000000 --- a/internal/interfaces/api/user_controller.go +++ /dev/null @@ -1 +0,0 @@ -package api diff --git a/internal/interfaces/route.go b/internal/interfaces/route.go index 08badf2..bc9b570 100644 --- a/internal/interfaces/route.go +++ b/internal/interfaces/route.go @@ -1 +1,18 @@ package interfaces + +import ( + "58team_blog/internal/application/services" + "58team_blog/internal/interfaces/api/controllers" + + "github.com/gin-gonic/gin" +) + +func BindPostAdmin(service *services.PostService, group *gin.RouterGroup) { + post := controllers.CreatePostController(service) + + g := group.Group("/post") + g.GET("/", post.GetAllPost) + g.GET("/:id", post.GetByIdPost) + g.POST("/", post.PostPost) + g.PUT("/:id", post.PutPost) +} diff --git a/migrate b/migrate new file mode 100755 index 0000000..d6364f6 Binary files /dev/null and b/migrate differ diff --git a/migrate.go b/migrate.go new file mode 100644 index 0000000..7204c28 --- /dev/null +++ b/migrate.go @@ -0,0 +1,98 @@ +package main + +import ( + "58team_blog/internal/infrastructure" + "database/sql" + "flag" + "fmt" + "log" + + "github.com/golang-migrate/migrate" + "github.com/golang-migrate/migrate/database/postgres" + _ "github.com/golang-migrate/migrate/source/file" + _ "github.com/lib/pq" +) + +func main() { + var command = flag.String("command", "up", "Migration command: up, down, version, force.") + var steps = flag.Int("steps", -1, "Number of migration steps (for up/down commands)") + var version = flag.Int("version", -1, "Target version (for force command)") + + config, err := infrastructure.LoadConfig() + if err != nil { + log.Fatal("Cannot load config file: ", err) + } + + DbUrl := "postgres://" + config.DBUser + ":" + config.DBPass + + "@" + config.DBHost + ":" + config.DBPort + "/" + config.DBName + "?sslmode=disable" + fmt.Println(DbUrl) + + db, err := sql.Open("postgres", DbUrl) + if err != nil { + log.Fatal("Failed to connect to database:", err) + } + defer db.Close() + + driver, err := postgres.WithInstance(db, &postgres.Config{}) + if err != nil { + log.Fatal("Failed to create database driver:", err) + } + + m, err := migrate.NewWithDatabaseInstance("file://./migrations", "postgres", driver) + if err != nil { + log.Fatal("Failed to create migrate instance:", err) + } + defer m.Close() + + switch *command { + case "up": + if *steps > 0 { + err = m.Steps(*steps) + } else { + err = m.Up() + } + if err != nil && err != migrate.ErrNoChange { + log.Fatal("Migration up failed:", err) + } + if err == migrate.ErrNoChange { + fmt.Println("No migrations to apply") + } else { + fmt.Println("Migrations applied successfully") + } + + case "down": + if *steps > 0 { + err = m.Steps(-*steps) + } else { + err = m.Down() + } + if err != nil && err != migrate.ErrNoChange { + log.Fatal("Migration down failed:", err) + } + if err == migrate.ErrNoChange { + fmt.Println("No migrations to rollback") + } else { + fmt.Println("Migrations rolled back successfully") + } + + case "version": + version, dirty, err := m.Version() + if err != nil { + log.Fatal("Failed to get version:", err) + } + fmt.Printf("Current version: %d (dirty: %v)\n", version, dirty) + + case "force": + if *version < 0 { + log.Fatal("Version is required for force command. Use -version flag") + } + err = m.Force(*version) + if err != nil { + log.Fatal("Force migration failed:", err) + } + fmt.Printf("Forced migration to version %d\n", *version) + + default: + log.Fatal("Unknown command:", *command) + } +} diff --git a/migrations/00001_init_tables.down.sql b/migrations/00001_init_tables.down.sql new file mode 100644 index 0000000..d98f907 --- /dev/null +++ b/migrations/00001_init_tables.down.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS images; +DROP TABLE IF EXISTS users; +DROP TABLE IF EXISTS post; diff --git a/migrations/00001_init_tables.up.sql b/migrations/00001_init_tables.up.sql new file mode 100644 index 0000000..764a139 --- /dev/null +++ b/migrations/00001_init_tables.up.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS post ( + id TEXT PRIMARY KEY, + userId TEXT NOT NULL, + title TEXT NOT NULL, + description TEXT NOT NULL, + content TEXT NOT NULL, + createdAt TIMESTAMP NOT NULL, + updatedAt TIMESTAMP NOT NULL, + FOREIGN KEY (userId) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS images ( + id TEXT PRIMARY KEY, + path TEXT NOT NULL +); diff --git a/migrations/create.sql b/migrations/create.sql deleted file mode 100644 index 4e0a1da..0000000 --- a/migrations/create.sql +++ /dev/null @@ -1,47 +0,0 @@ -CREATE TABLE "users" ( - "id" TEXT NOT NULL UNIQUE, - "username" VARCHAR(255) UNIQUE, - "password" VARCHAR(255), - PRIMARY KEY("id") -); - - - - -CREATE TABLE "post" ( - "id" TEXT NOT NULL UNIQUE, - "title" TEXT, - "description" TEXT, - "content" TEXT, - "createdAt" TIMESTAMP, - "updatedAt" TIMESTAMP, - PRIMARY KEY("id") -); - - - - -CREATE TABLE "posts" ( - "id" TEXT NOT NULL UNIQUE, - "user_id" INTEGER, - "post_id" INTEGER, - PRIMARY KEY("id") -); - - - - -CREATE TABLE "images" ( - "id" TEXT NOT NULL UNIQUE, - "path" TEXT, - PRIMARY KEY("id") -); - - - -ALTER TABLE "users" -ADD FOREIGN KEY("id") REFERENCES "posts"("user_id") -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE "post" -ADD FOREIGN KEY("id") REFERENCES "posts"("post_id") -ON UPDATE NO ACTION ON DELETE NO ACTION;