├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── api
├── docs.go
├── swagger.json
└── swagger.yaml
├── cmd
├── cli
│ └── main.go
└── server
│ └── main.go
├── db
└── migrations
│ ├── 000001_create_posts_table.down.sql
│ ├── 000001_create_posts_table.up.sql
│ ├── 000002_add_unique_to_posts.down.sql
│ ├── 000002_add_unique_to_posts.up.sql
│ ├── 000003_create_users_table.down.sql
│ ├── 000003_create_users_table.up.sql
│ ├── 000004_add_author_id_to_posts.down.sql
│ ├── 000004_add_author_id_to_posts.up.sql
│ ├── 000005_add_fk_author_id_to_users.down.sql
│ ├── 000005_add_fk_author_id_to_users.up.sql
│ ├── 000006_create_role_table.down.sql
│ ├── 000006_create_role_table.up.sql
│ ├── 000007_add_dk_role_id_to_users.down.sql
│ └── 000007_add_dk_role_id_to_users.up.sql
├── go.mod
├── mux
└── pkg
├── adding
├── post.go
└── service.go
├── deleting
└── service.go
├── http
└── rest
│ ├── auth
│ └── auth.go
│ ├── middleware
│ └── middleware.go
│ ├── post_handler.go
│ ├── router.go
│ └── user_handler.go
├── listing
├── post.go
└── service.go
├── login
├── service.go
└── user.go
├── register
├── service.go
└── user.go
├── storage
├── memory
│ ├── post.go
│ └── repository.go
└── postgres
│ ├── main.go
│ ├── post.go
│ ├── user.go
│ └── userpolicy.go
└── userpolicy
└── service.go
/.gitignore:
--------------------------------------------------------------------------------
1 | go.sum
2 | build/
3 | main
4 | .idea/
5 | bin/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Abylaikhan Zulbukharov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SERVER_FILE=cmd/server/main.go
2 | SERVER_DEST=bin/blog-web
3 |
4 | all: build
5 |
6 | build:
7 | go build -o ${SERVER_DEST} ${SERVER_FILE}
8 |
9 | run:
10 | go build -o ${SERVER_DEST} ${SERVER_FILE}
11 | ${SERVER_DEST}
12 |
13 | # test:
14 | # go test -v ./...
15 |
16 | clean:
17 | rm -f ${SERVER_DEST}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Golang DDD+HEX demo blog api
2 |
3 | - tactical design
4 | - entities
5 | - an object defined primarily by its identity
6 | - value objects
7 | - immutable
8 | - repositories
9 | - how will communicate with storage
10 | - services
11 | - corresponds application use cases
12 | - example:
13 | ` oauth -> user policy -> adding post`
14 |
15 | context: blogpost
16 | ubiquitous language: author, post, vote, comments, storage
17 | entities: author, post, votes, comments
18 | value objects: it can be part of entity object (author, voter, commenter)
19 | aggregates: PostAuthor, PostVotes, PostComments
20 | - service:
21 | - stateless operations
22 | - post adder / listing,
23 | - vote increment / decrement / count
24 | - comment adder / listing
25 | - author register / login
26 | - events: can affect to the system (errors, logs)
27 | - repository: facade over backend
28 |
29 | ## Goals: good structure goal
30 |
31 | - consistent.
32 | - easy to test.
33 | - easy to understand.
34 | - easy to change.
35 |
36 | ## Project Specs
37 |
38 | - author can add a post.
39 | - author can add a comment for the post.
40 | - author can vote for the post.
41 | - author can list all posts with votes.
42 | - author can list all reviews for the post.
43 |
44 |
45 | ## Dependencies:
46 | - [pgx](https://github.com/jackc/pgx)
47 |
48 | ## Status:
49 | in progress
50 |
51 | ## Run:
52 | ```sh
53 | go build cmd/boilerplate/main.go
54 | ```
55 |
56 | ## Example
57 |
58 | http://localhost:8000/swagger/index.html
59 |
60 | ```sh
61 | # add new post
62 | curl -X POST "http://localhost:8000/api/post" -H "accept: application/json" -H "Content-Type: application/json" -d '{"content": "hello cruel world"}'
63 |
64 | # get all posts
65 | curl -X GET "http://localhost:8000/api/posts" -H "accept: application/json"
66 |
67 | # migrate
68 | migrate -source file://$PWD/db/migrations -database "postgres://adm:1234@localhost:5432/alem?sslmode=disable" up
69 |
70 | swag init -g cmd/server/main.go --parseDependency --parseInternal -o ./api
71 | ```
72 |
73 | ## Links
74 | https://khalilstemmler.com/wiki/conways-law/
75 | https://medium.com/@hatajoe/clean-architecture-in-go-4030f11ec1b1
76 | https://khalilstemmler.com/articles/software-design-architecture/organizing-app-logic/
77 | http://olivergierke.de/2020/03/Implementing-DDD-Building-Blocks-in-Java/
78 | https://archfirst.org/domain-driven-design-8-conclusion/
79 | https://herbertograca.com/2017/09/14/ports-adapters-architecture/
80 | https://blog.fedecarg.com/2009/03/11/domain-driven-design-and-mvc-architectures/#:~:text=According%20to%20Eric%20Evans%2C%20Domain,a%20technology%20or%20a%20methodology.&text=Domain%2Ddriven%20design%20separates%20the,to%20retrieve%20and%20store%20data.
81 | http://www.newtonmeters.com/blog/domain-driven-design-in-go/
82 | https://vaadin.com/learn/tutorials/ddd/ddd_and_hexagonal
83 | https://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
84 |
85 | 
86 |
--------------------------------------------------------------------------------
/api/docs.go:
--------------------------------------------------------------------------------
1 | // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
2 | // This file was generated by swaggo/swag
3 |
4 | package api
5 |
6 | import (
7 | "bytes"
8 | "encoding/json"
9 | "strings"
10 |
11 | "github.com/alecthomas/template"
12 | "github.com/swaggo/swag"
13 | )
14 |
15 | var doc = `{
16 | "schemes": {{ marshal .Schemes }},
17 | "swagger": "2.0",
18 | "info": {
19 | "description": "{{.Description}}",
20 | "title": "{{.Title}}",
21 | "contact": {
22 | "name": "Zulbukharov Abylaikhan",
23 | "email": "l1pt0n1905@gmail.com"
24 | },
25 | "license": {
26 | "name": "MIT License",
27 | "url": "https://github.com/Zulbukharov/golang-ddd-hex/blob/master/LICENSE"
28 | },
29 | "version": "{{.Version}}"
30 | },
31 | "host": "{{.Host}}",
32 | "basePath": "{{.BasePath}}",
33 | "paths": {
34 | "/api/posts": {
35 | "get": {
36 | "description": "get posts",
37 | "consumes": [
38 | "application/json"
39 | ],
40 | "produces": [
41 | "application/json"
42 | ],
43 | "summary": "List posts",
44 | "parameters": [
45 | {
46 | "type": "integer",
47 | "description": "Account ID",
48 | "name": "id",
49 | "in": "path",
50 | "required": true
51 | }
52 | ],
53 | "responses": {
54 | "200": {
55 | "description": "OK",
56 | "schema": {
57 | "type": "array",
58 | "items": {
59 | "$ref": "#/definitions/listing.Post"
60 | }
61 | }
62 | }
63 | }
64 | }
65 | }
66 | },
67 | "definitions": {
68 | "listing.Post": {
69 | "type": "object",
70 | "properties": {
71 | "author_id": {
72 | "type": "integer"
73 | },
74 | "content": {
75 | "type": "string"
76 | },
77 | "id": {
78 | "type": "integer"
79 | }
80 | }
81 | }
82 | }
83 | }`
84 |
85 | type swaggerInfo struct {
86 | Version string
87 | Host string
88 | BasePath string
89 | Schemes []string
90 | Title string
91 | Description string
92 | }
93 |
94 | // SwaggerInfo holds exported Swagger Info so clients can modify it
95 | var SwaggerInfo = swaggerInfo{
96 | Version: "1.0",
97 | Host: "localhost:8080",
98 | BasePath: "/api",
99 | Schemes: []string{},
100 | Title: "Blogspot API",
101 | Description: "This is a sample blogspot server.",
102 | }
103 |
104 | type s struct{}
105 |
106 | func (s *s) ReadDoc() string {
107 | sInfo := SwaggerInfo
108 | sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
109 |
110 | t, err := template.New("swagger_info").Funcs(template.FuncMap{
111 | "marshal": func(v interface{}) string {
112 | a, _ := json.Marshal(v)
113 | return string(a)
114 | },
115 | }).Parse(doc)
116 | if err != nil {
117 | return doc
118 | }
119 |
120 | var tpl bytes.Buffer
121 | if err := t.Execute(&tpl, sInfo); err != nil {
122 | return doc
123 | }
124 |
125 | return tpl.String()
126 | }
127 |
128 | func init() {
129 | swag.Register(swag.Name, &s{})
130 | }
131 |
--------------------------------------------------------------------------------
/api/swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "description": "This is a sample blogspot server.",
5 | "title": "Blogspot API",
6 | "contact": {
7 | "name": "Zulbukharov Abylaikhan",
8 | "email": "l1pt0n1905@gmail.com"
9 | },
10 | "license": {
11 | "name": "MIT License",
12 | "url": "https://github.com/Zulbukharov/golang-ddd-hex/blob/master/LICENSE"
13 | },
14 | "version": "1.0"
15 | },
16 | "host": "localhost:8080",
17 | "basePath": "/api",
18 | "paths": {
19 | "/api/posts": {
20 | "get": {
21 | "description": "get posts",
22 | "consumes": [
23 | "application/json"
24 | ],
25 | "produces": [
26 | "application/json"
27 | ],
28 | "summary": "List posts",
29 | "parameters": [
30 | {
31 | "type": "integer",
32 | "description": "Account ID",
33 | "name": "id",
34 | "in": "path",
35 | "required": true
36 | }
37 | ],
38 | "responses": {
39 | "200": {
40 | "description": "OK",
41 | "schema": {
42 | "type": "array",
43 | "items": {
44 | "$ref": "#/definitions/listing.Post"
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 | },
52 | "definitions": {
53 | "listing.Post": {
54 | "type": "object",
55 | "properties": {
56 | "author_id": {
57 | "type": "integer"
58 | },
59 | "content": {
60 | "type": "string"
61 | },
62 | "id": {
63 | "type": "integer"
64 | }
65 | }
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/api/swagger.yaml:
--------------------------------------------------------------------------------
1 | basePath: /api
2 | definitions:
3 | listing.Post:
4 | properties:
5 | author_id:
6 | type: integer
7 | content:
8 | type: string
9 | id:
10 | type: integer
11 | type: object
12 | host: localhost:8080
13 | info:
14 | contact:
15 | email: l1pt0n1905@gmail.com
16 | name: Zulbukharov Abylaikhan
17 | description: This is a sample blogspot server.
18 | license:
19 | name: MIT License
20 | url: https://github.com/Zulbukharov/golang-ddd-hex/blob/master/LICENSE
21 | title: Blogspot API
22 | version: "1.0"
23 | paths:
24 | /api/posts:
25 | get:
26 | consumes:
27 | - application/json
28 | description: get posts
29 | parameters:
30 | - description: Account ID
31 | in: path
32 | name: id
33 | required: true
34 | type: integer
35 | produces:
36 | - application/json
37 | responses:
38 | "200":
39 | description: OK
40 | schema:
41 | items:
42 | $ref: '#/definitions/listing.Post'
43 | type: array
44 | summary: List posts
45 | swagger: "2.0"
46 |
--------------------------------------------------------------------------------
/cmd/cli/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/Zulbukharov/golang-ddd-hex/pkg/storage/memory"
5 |
6 | "github.com/Zulbukharov/golang-ddd-hex/pkg/adding"
7 | )
8 |
9 | func main() {
10 | s := new(memory.Storage)
11 | adder := adding.NewService(s)
12 | // listing := listing.NewService(s)
13 |
14 | adder.AddPost(adding.Post{Content: "sample1"})
15 | adder.AddPost(adding.Post{Content: "sample2"})
16 | }
17 |
--------------------------------------------------------------------------------
/cmd/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/Zulbukharov/golang-ddd-hex/pkg/deleting"
6 | auth2 "github.com/Zulbukharov/golang-ddd-hex/pkg/http/rest/auth"
7 | "github.com/Zulbukharov/golang-ddd-hex/pkg/http/rest/middleware"
8 | "github.com/Zulbukharov/golang-ddd-hex/pkg/userpolicy"
9 | "log"
10 | "net/http"
11 |
12 | "github.com/Zulbukharov/golang-ddd-hex/pkg/adding"
13 | "github.com/Zulbukharov/golang-ddd-hex/pkg/http/rest"
14 | "github.com/Zulbukharov/golang-ddd-hex/pkg/listing"
15 | "github.com/Zulbukharov/golang-ddd-hex/pkg/login"
16 | "github.com/Zulbukharov/golang-ddd-hex/pkg/register"
17 | "github.com/Zulbukharov/golang-ddd-hex/pkg/storage/postgres"
18 | )
19 |
20 | // @title Blogspot API
21 | // @version 1.0
22 | // @description This is a sample blogspot server.
23 |
24 | // @contact.name Zulbukharov Abylaikhan
25 | // @contact.email l1pt0n1905@gmail.com
26 |
27 | // @license.name MIT License
28 | // @license.url https://github.com/Zulbukharov/golang-ddd-hex/blob/master/LICENSE
29 |
30 | // @host localhost:8080
31 | // @BasePath /api
32 |
33 | func main() {
34 | c, err := postgres.NewStorage("localhost", "5432", "adm", "1234", "blog")
35 | if err != nil {
36 | log.Printf("%v", err)
37 | return
38 | }
39 | postRepo := postgres.NewPostRepository(c)
40 | userRepo := postgres.NewUserRepository(c)
41 | userPolicyRepo := postgres.NewUserPolicyRepository(c)
42 |
43 | adder := adding.NewService(postRepo)
44 | listing := listing.NewService(postRepo)
45 | deleting := deleting.NewService(postRepo)
46 | login := login.NewService(userRepo)
47 | register := register.NewService(userRepo)
48 | userPolicy := userpolicy.NewService(userPolicyRepo)
49 |
50 | auth := auth2.NewAuthenticator("ok")
51 | middleware := middleware.NewRules(auth)
52 |
53 | postHandler := rest.NewPostHandler(listing, adder, deleting, userPolicy)
54 | userHandler := rest.NewUserHandler(login, register, auth)
55 |
56 | server := &http.Server{
57 | Addr: fmt.Sprint(":8000"),
58 | Handler: rest.Route(postHandler, userHandler, middleware),
59 | }
60 | log.Printf("Starting HTTP Server. Listening at %q", server.Addr)
61 | if err := server.ListenAndServe(); err != nil {
62 | log.Printf("%v", err)
63 | } else {
64 | log.Println("Server closed ! ")
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/db/migrations/000001_create_posts_table.down.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS posts;
2 |
3 |
--------------------------------------------------------------------------------
/db/migrations/000001_create_posts_table.up.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE posts (
2 | id SERIAL NOT NULL UNIQUE PRIMARY KEY,
3 | content VARCHAR not NULL
4 | );
5 |
--------------------------------------------------------------------------------
/db/migrations/000002_add_unique_to_posts.down.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE posts DROP CONSTRAINT posts_unique_content_key;
2 |
--------------------------------------------------------------------------------
/db/migrations/000002_add_unique_to_posts.up.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE posts ADD CONSTRAINT posts_unique_content_key UNIQUE (content);
2 |
--------------------------------------------------------------------------------
/db/migrations/000003_create_users_table.down.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS users;
2 |
3 |
--------------------------------------------------------------------------------
/db/migrations/000003_create_users_table.up.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE users (
2 | id SERIAL NOT NULL UNIQUE PRIMARY KEY,
3 | username VARCHAR not NULL UNIQUE,
4 | password VARCHAR not null
5 | );
6 |
--------------------------------------------------------------------------------
/db/migrations/000004_add_author_id_to_posts.down.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE posts DROP COLUMN IF EXISTS author_id;
2 |
--------------------------------------------------------------------------------
/db/migrations/000004_add_author_id_to_posts.up.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE posts ADD COLUMN author_id INTEGER;
2 |
--------------------------------------------------------------------------------
/db/migrations/000005_add_fk_author_id_to_users.down.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE posts DROP CONSTRAINT fk_author_id_to_users;
2 |
--------------------------------------------------------------------------------
/db/migrations/000005_add_fk_author_id_to_users.up.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE posts ADD CONSTRAINT fk_author_id_to_users FOREIGN KEY (author_id) REFERENCES users(id);
2 |
--------------------------------------------------------------------------------
/db/migrations/000006_create_role_table.down.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS roles;
2 |
3 |
--------------------------------------------------------------------------------
/db/migrations/000006_create_role_table.up.sql:
--------------------------------------------------------------------------------
1 | BEGIN;
2 |
3 | CREATE TABLE roles (
4 | id SERIAL NOT NULL UNIQUE PRIMARY KEY,
5 | role_name VARCHAR not NULL
6 | );
7 |
8 | INSERT INTO roles(id, role_name) VALUES
9 | (1, 'USER'),
10 | (2, 'AUTHOR'),
11 | (3, 'ADMIN');
12 |
13 | COMMIT;
--------------------------------------------------------------------------------
/db/migrations/000007_add_dk_role_id_to_users.down.sql:
--------------------------------------------------------------------------------
1 | BEGIN;
2 |
3 | ALTER TABLE users DROP CONSTRAINT fk_role_id_to_users;
4 | ALTER TABLE users DROP COLUMN IF EXISTS author_id;
5 |
6 | COMMIT;
--------------------------------------------------------------------------------
/db/migrations/000007_add_dk_role_id_to_users.up.sql:
--------------------------------------------------------------------------------
1 | BEGIN;
2 |
3 | ALTER TABLE users ADD COLUMN role_id INTEGER;
4 | ALTER TABLE users ADD CONSTRAINT fk_role_id_to_users FOREIGN KEY (role_id) REFERENCES roles(id);
5 |
6 | COMMIT;
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Zulbukharov/golang-ddd-hex
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
7 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect
8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
9 | github.com/go-openapi/spec v0.19.9 // indirect
10 | github.com/go-openapi/swag v0.19.9 // indirect
11 | github.com/gorilla/mux v1.7.4
12 | github.com/jackc/pgx/v4 v4.8.1
13 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
14 | github.com/kr/pty v1.1.8 // indirect
15 | github.com/lib/pq v1.3.0
16 | github.com/mailru/easyjson v0.7.2 // indirect
17 | github.com/stretchr/objx v0.2.0 // indirect
18 | github.com/swaggo/http-swagger v0.0.0-20200308142732-58ac5e232fba
19 | github.com/swaggo/swag v1.6.7
20 | golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
21 | golang.org/x/tools v0.0.0-20200806234136-990129eca547 // indirect
22 | gopkg.in/yaml.v2 v2.3.0 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/mux:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zulbukharov/golang-ddd-hex/88b2b22881cb9ee6773500bf1ee1fd0c4f727419/mux
--------------------------------------------------------------------------------
/pkg/adding/post.go:
--------------------------------------------------------------------------------
1 | package adding
2 |
3 | // Post basic post struct
4 | type Post struct {
5 | AuthorID uint `json:"author_id"`
6 | Content string `json:"content"`
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/adding/service.go:
--------------------------------------------------------------------------------
1 | package adding
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // ErrDuplicate defines the error message
8 | var ErrDuplicate = fmt.Errorf("Post already exist")
9 |
10 | // Service provides Post adding operations.
11 | type Service interface {
12 | AddPost(Post) error
13 | }
14 |
15 | // Repository provides access to Post repository.
16 | type Repository interface {
17 | AddPost(Post) error
18 | }
19 |
20 | type service struct {
21 | tR Repository
22 | }
23 |
24 | // NewService creates an adding service with the necessary dependencies
25 | func NewService(r Repository) Service {
26 | return &service{r}
27 | }
28 |
29 | // AddPost adds the given Post to the database
30 | func (s *service) AddPost(u Post) error {
31 | if u.AuthorID == 0 || u.Content == "" {
32 | return fmt.Errorf("invalid input")
33 | }
34 | return s.tR.AddPost(u)
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/deleting/service.go:
--------------------------------------------------------------------------------
1 | package deleting
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Service provides Post adding operations.
8 | type Service interface {
9 | DeletePost(uint) error
10 | }
11 |
12 | // Repository provides access to Post repository.
13 | type Repository interface {
14 | DeletePost(uint) error
15 | }
16 |
17 | type service struct {
18 | dR Repository
19 | }
20 |
21 | // NewService creates an adding service with the necessary dependencies
22 | func NewService(r Repository) Service {
23 | return &service{r}
24 | }
25 |
26 | // AddPost adds the given Post to the database
27 | func (s *service) DeletePost(u uint) error {
28 | if u == 0 {
29 | return fmt.Errorf("invalid post id")
30 | }
31 | return s.dR.DeletePost(u)
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/http/rest/auth/auth.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "fmt"
5 | "github.com/dgrijalva/jwt-go"
6 | "time"
7 | )
8 |
9 | type AppClaims struct {
10 | ID uint `json:"id,omitempty"`
11 | //Roles []string `json:"roles,omitempty"`
12 | jwt.StandardClaims
13 | }
14 |
15 | type Authenticator interface {
16 | ParseToken(token string) (*AppClaims, error)
17 | GenerateToken(id uint) (string, error)
18 | }
19 |
20 | type authenticator struct {
21 | secretKey []byte
22 | }
23 |
24 | func NewAuthenticator(secretKey string) Authenticator {
25 | return &authenticator{[]byte(secretKey)}
26 | }
27 |
28 | func (a authenticator) ParseToken(tokenString string) (*AppClaims, error) {
29 | var claims AppClaims
30 | token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
31 | return a.secretKey, nil
32 | })
33 | if err != nil {
34 | return nil, fmt.Errorf("failed to parse token with claims")
35 | }
36 |
37 | if claim, ok := token.Claims.(*AppClaims); ok && token.Valid {
38 | return claim, nil
39 | }
40 | return nil, fmt.Errorf("invalid token")
41 | }
42 |
43 | func (a authenticator) GenerateToken(id uint) (string, error) {
44 | var claims AppClaims
45 | claims.ID = id
46 | claims.ExpiresAt = time.Now().Add(time.Hour * 72).Unix()
47 |
48 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
49 | return token.SignedString(a.secretKey)
50 | }
51 |
52 | func (a authenticator) Validate(claims AppClaims) error {
53 | if claims.ID == 0 {
54 | return fmt.Errorf("user ID must be set")
55 | }
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/http/rest/middleware/middleware.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "context"
5 | "github.com/Zulbukharov/golang-ddd-hex/pkg/http/rest/auth"
6 | "net/http"
7 | )
8 |
9 | type Rules interface {
10 | LoggedIn(next http.Handler) http.Handler
11 | }
12 |
13 | type rules struct {
14 | auth auth.Authenticator
15 | }
16 |
17 | func NewRules(auth auth.Authenticator) Rules {
18 | return &rules{auth}
19 | }
20 |
21 | // LoggedIn simple middleware to push value to the context
22 | func (m rules) LoggedIn(next http.Handler) http.Handler {
23 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24 | credentials, err := r.Cookie("credentials")
25 | if err != nil {
26 | http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
27 | return
28 | }
29 |
30 | t, err := m.auth.ParseToken(credentials.Value)
31 | if err != nil {
32 | http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
33 | return
34 | }
35 | ctx := context.WithValue(r.Context(), "credentials", t)
36 | next.ServeHTTP(w, r.WithContext(ctx))
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/http/rest/post_handler.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/Zulbukharov/golang-ddd-hex/pkg/deleting"
6 | "github.com/Zulbukharov/golang-ddd-hex/pkg/http/rest/auth"
7 | "github.com/Zulbukharov/golang-ddd-hex/pkg/userpolicy"
8 | "github.com/gorilla/mux"
9 | "net/http"
10 | "strconv"
11 |
12 | "github.com/Zulbukharov/golang-ddd-hex/pkg/adding"
13 | "github.com/Zulbukharov/golang-ddd-hex/pkg/listing"
14 | )
15 |
16 | // PostHandler provides access to Post api methods.
17 | type PostHandler interface {
18 | GetPosts(w http.ResponseWriter, r *http.Request)
19 | AddPost(w http.ResponseWriter, r *http.Request)
20 | DeletePost(w http.ResponseWriter, r *http.Request)
21 | EditPost(w http.ResponseWriter, r *http.Request)
22 | // FindPostByID()
23 | // UpdatePost()
24 | // DeletePost()
25 | }
26 |
27 | type postHandler struct {
28 | l listing.Service
29 | a adding.Service
30 | d deleting.Service
31 | u userpolicy.Service
32 | // logger
33 | }
34 |
35 | // NewPostHandler post handler
36 | func NewPostHandler(l listing.Service, a adding.Service, d deleting.Service, u userpolicy.Service) PostHandler {
37 | return &postHandler{
38 | l: l,
39 | a: a,
40 | d: d,
41 | u: u,
42 | }
43 | }
44 |
45 | // GetPosts godoc
46 | // @Summary List posts
47 | // @Description get posts
48 | // @Accept json
49 | // @Produce json
50 | // @Success 200 {array} listing.Post
51 | // @Router /posts [get]
52 | func (h postHandler) GetPosts(w http.ResponseWriter, r *http.Request) {
53 | w.Header().Set("Content-Type", "application/json")
54 | list, err := h.l.GetAllPosts()
55 |
56 | if err != nil {
57 | http.Error(w, "Failed to get posts", http.StatusBadRequest)
58 | return
59 | }
60 | json.NewEncoder(w).Encode(list)
61 | }
62 |
63 | // AddPost handler for POST /api/post requests
64 | func (h postHandler) AddPost(w http.ResponseWriter, r *http.Request) {
65 | var post adding.Post
66 |
67 | decoder := json.NewDecoder(r.Body)
68 | if err := decoder.Decode(&post); err != nil {
69 | http.Error(w, "Failed to parse post", http.StatusBadRequest)
70 | return
71 | }
72 |
73 | credentials := r.Context().Value("credentials").(*auth.AppClaims)
74 | post.AuthorID = credentials.ID
75 |
76 | if err := h.a.AddPost(post); err != nil {
77 | http.Error(w, "Failed to add post", http.StatusBadRequest)
78 | return
79 | }
80 |
81 | w.Header().Set("Content-Type", "application/json")
82 | json.NewEncoder(w).Encode("New post added.")
83 | }
84 |
85 | func (h postHandler) DeletePost(w http.ResponseWriter, r *http.Request) {
86 | params := mux.Vars(r)
87 | postID, err := strconv.ParseUint(params["id"], 10, 64)
88 | if err != nil || postID == 0 {
89 | http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
90 | return
91 | }
92 |
93 | credentials := r.Context().Value("credentials").(*auth.AppClaims)
94 | userID := credentials.ID
95 |
96 | if allowed := h.u.IsOwnerOfPost(userID, uint(postID)); allowed == false {
97 | http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
98 | return
99 | }
100 | if err := h.d.DeletePost(uint(postID)); err != nil {
101 | http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
102 | return
103 | }
104 |
105 | w.Header().Set("Content-Type", "application/json")
106 | json.NewEncoder(w).Encode("post deleted.")
107 | }
108 |
109 | func (h postHandler) EditPost(w http.ResponseWriter, r *http.Request) {
110 | params := mux.Vars(r)
111 | _, ok := params["id"]
112 | if !ok {
113 | http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
114 | return
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/pkg/http/rest/router.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "encoding/json"
5 | _ "github.com/Zulbukharov/golang-ddd-hex/api"
6 | "github.com/Zulbukharov/golang-ddd-hex/pkg/http/rest/middleware"
7 | "github.com/gorilla/mux"
8 | "github.com/swaggo/http-swagger"
9 | "log"
10 | "net/http"
11 | )
12 |
13 | // Route returns an http handler for the api.
14 | func Route(h PostHandler, u UserHandler, m middleware.Rules) http.Handler {
15 | router := mux.NewRouter()
16 | router.Use(accessControl)
17 |
18 | router.PathPrefix("/swagger/").Handler(httpSwagger.Handler(
19 | httpSwagger.URL("http://localhost:8000/swagger/doc.json"), //The url pointing to API definition
20 | httpSwagger.DeepLinking(true),
21 | httpSwagger.DocExpansion("none"),
22 | httpSwagger.DomID("#swagger-ui"),
23 | ))
24 |
25 | api := router.PathPrefix("/api").Subrouter()
26 | api.HandleFunc("/posts", h.GetPosts).Methods("GET")
27 | //api.Handle("/post/{id}", m.LoggedIn(http.HandlerFunc(h.AddPost))).Methods("GET")
28 | //api.Handle("/post", m.LoggedIn(http.HandlerFunc(h.AddPost))).Methods("POST")
29 | //api.Handle("/post", m.LoggedIn(http.HandlerFunc(h.DeletePost))).Methods("PUT")
30 | //api.Handle("/post/{id}", m.LoggedIn(http.HandlerFunc(h.DeletePost))).Methods("DELETE")
31 | //api.HandleFunc("/login", u.Login).Methods("POST")
32 | //api.HandleFunc("/register", u.Register).Methods("POST")
33 |
34 | http.Handle("/", router)
35 | return router
36 | }
37 |
38 | func accessControl(h http.Handler) http.Handler {
39 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40 | w.Header().Set("Access-Control-Allow-Origin", "*")
41 | w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
42 | w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type")
43 |
44 | if r.Method == "OPTIONS" {
45 | return
46 | }
47 |
48 | h.ServeHTTP(w, r)
49 | })
50 | }
51 |
52 | func respHandler(h func(http.ResponseWriter, *http.Request) (interface{}, int, error)) http.HandlerFunc {
53 | return func(w http.ResponseWriter, r *http.Request) {
54 | data, status, err := h(w, r)
55 | if err != nil {
56 | data = struct {
57 | Error string `json: "error"`
58 | }{
59 | Error: err.Error(),
60 | }
61 | }
62 | w.Header().Set("Content-Type", "application/json")
63 | w.WriteHeader(status)
64 | err = json.NewEncoder(w).Encode(data)
65 | if err != nil {
66 | log.Printf("could not encode response to output: %v", err)
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/http/rest/user_handler.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/Zulbukharov/golang-ddd-hex/pkg/http/rest/auth"
7 | "github.com/Zulbukharov/golang-ddd-hex/pkg/login"
8 | "github.com/Zulbukharov/golang-ddd-hex/pkg/register"
9 | "net/http"
10 | "time"
11 | )
12 |
13 | // UserHandler provides access to User api methods.
14 | type UserHandler interface {
15 | Login(w http.ResponseWriter, r *http.Request)
16 | Register(w http.ResponseWriter, r *http.Request)
17 | }
18 |
19 | type userHandler struct {
20 | l login.Service
21 | r register.Service
22 | auth auth.Authenticator
23 | // logger
24 | }
25 |
26 | // NewUserHandler login handler
27 | func NewUserHandler(l login.Service, r register.Service, auth auth.Authenticator) UserHandler {
28 | return &userHandler{l, r, auth}
29 | }
30 |
31 | // Login handler for POST /api/login requests
32 | func (h userHandler) Login(w http.ResponseWriter, r *http.Request) {
33 | var user login.User
34 |
35 | decoder := json.NewDecoder(r.Body)
36 |
37 | err := decoder.Decode(&user)
38 | if err != nil {
39 | http.Error(w, err.Error(), http.StatusBadRequest)
40 | return
41 | }
42 |
43 | var id uint
44 | id, err = h.l.Login(user)
45 | if err != nil {
46 | http.Error(w, err.Error(), http.StatusBadRequest)
47 | return
48 | }
49 |
50 | w.Header().Set("Content-Type", "application/json")
51 | claims, err := h.auth.GenerateToken(id)
52 | http.SetCookie(w, &http.Cookie{
53 | Name: "credentials",
54 | Value: claims,
55 | Expires: time.Now().Add(72 * time.Hour),
56 | })
57 | json.NewEncoder(w).Encode(fmt.Sprintf("user logged in %v", claims))
58 | }
59 |
60 | func (h userHandler) Register(w http.ResponseWriter, r *http.Request) {
61 | var user register.User
62 |
63 | decoder := json.NewDecoder(r.Body)
64 |
65 | err := decoder.Decode(&user)
66 | if err != nil {
67 | http.Error(w, err.Error(), http.StatusBadRequest)
68 | return
69 | }
70 |
71 | id, err := h.r.Register(user)
72 | if err != nil {
73 | http.Error(w, err.Error(), http.StatusBadRequest)
74 | return
75 | }
76 |
77 | w.Header().Set("Content-Type", "application/json")
78 | claims, err := h.auth.GenerateToken(id)
79 | http.SetCookie(w, &http.Cookie{
80 | Name: "credentials",
81 | Value: claims,
82 | Expires: time.Now().Add(72 * time.Hour),
83 | })
84 | json.NewEncoder(w).Encode(fmt.Sprintf("user registered %v", claims))
85 | }
86 |
--------------------------------------------------------------------------------
/pkg/listing/post.go:
--------------------------------------------------------------------------------
1 | package listing
2 |
3 | // Post defines the properties of a Post to be listed
4 | type Post struct {
5 | ID uint `json:"id"`
6 | AuthorID uint `json:"author_id"`
7 | Content string `json:"content"`
8 | }
9 |
--------------------------------------------------------------------------------
/pkg/listing/service.go:
--------------------------------------------------------------------------------
1 | package listing
2 |
3 | // Service provides Post listing operations.
4 | type Service interface {
5 | GetAllPosts() ([]Post, error)
6 | // FindPostByID(id)
7 | // FindUserPosts(id)
8 | }
9 |
10 | // Repository provides access to Post repository.
11 | type Repository interface {
12 | // GetAllPosts returns all Posts saved in storage.
13 | GetAllPosts() ([]Post, error)
14 | //GetUserPosts(id uint) ([]Post, error)
15 | }
16 |
17 | type service struct {
18 | tR Repository
19 | }
20 |
21 | // NewService creates an list service with the necessary dependencies
22 | func NewService(r Repository) Service {
23 | return &service{r}
24 | }
25 |
26 | // GetAllPosts returns all Posts from the storage
27 | func (s *service) GetAllPosts() ([]Post, error) {
28 | return s.tR.GetAllPosts()
29 | }
30 |
31 | //func (s *service) GetUserPosts(id uint) ([]Post, error) {
32 | // return s.tR.GetUserPosts(id)
33 | //}
34 |
--------------------------------------------------------------------------------
/pkg/login/service.go:
--------------------------------------------------------------------------------
1 | package login
2 |
3 | // Service ...
4 | type Service interface {
5 | Login(User) (uint, error)
6 | }
7 |
8 | // Repository ...
9 | type Repository interface {
10 | Login(User) (uint, error)
11 | }
12 |
13 | type service struct {
14 | lR Repository
15 | }
16 |
17 | // NewService ...
18 | func NewService(r Repository) Service {
19 | return &service{r}
20 | }
21 |
22 | // Login ...
23 | func (s *service) Login(u User) (uint, error) {
24 | // input validation
25 | return s.lR.Login(u)
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/login/user.go:
--------------------------------------------------------------------------------
1 | package login
2 |
3 | type User struct {
4 | Username string `json:"username"`
5 | Password string `json:"password"`
6 | }
7 |
--------------------------------------------------------------------------------
/pkg/register/service.go:
--------------------------------------------------------------------------------
1 | package register
2 |
3 | import "fmt"
4 |
5 | // Service ...
6 | type Service interface {
7 | Register(User) (uint, error)
8 | }
9 |
10 | // Repository ...
11 | type Repository interface {
12 | Register(User) (uint, error)
13 | }
14 |
15 | type service struct {
16 | lR Repository
17 | }
18 |
19 | // NewService ...
20 | func NewService(r Repository) Service {
21 | return &service{r}
22 | }
23 |
24 | // Login ...
25 | func (s *service) Register(u User) (uint, error) {
26 | // input validation
27 | if u.Username == "" || u.Password == "" {
28 | return 0, fmt.Errorf("invalid input data")
29 | }
30 | return s.lR.Register(u)
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/register/user.go:
--------------------------------------------------------------------------------
1 | package register
2 |
3 | type User struct {
4 | Username string `json:"username"`
5 | Password string `json:"password"`
6 | }
7 |
--------------------------------------------------------------------------------
/pkg/storage/memory/post.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | // Post defines the properties of a Post to be listed
4 | type Post struct {
5 | ID uint `json:"id"`
6 | Content string `json:"content"`
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/storage/memory/repository.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "github.com/Zulbukharov/golang-ddd-hex/pkg/adding"
5 | "github.com/Zulbukharov/golang-ddd-hex/pkg/listing"
6 | )
7 |
8 | // Storage Memory keeps data in memory
9 | type Storage struct {
10 | Posts []Post
11 | }
12 |
13 | // AddPost saves the given Post to the repository
14 | func (s *Storage) AddPost(u adding.Post) error {
15 |
16 | for _, e := range s.Posts {
17 | if e.Content == u.Content {
18 | return adding.ErrDuplicate
19 | }
20 | }
21 |
22 | newU := Post{
23 | ID: uint(len(s.Posts) + 1),
24 | Content: u.Content,
25 | }
26 | s.Posts = append(s.Posts, newU)
27 | return nil
28 | }
29 |
30 | // GetAllPosts returns all Posts from the storage
31 | func (s *Storage) GetAllPosts() ([]listing.Post, error) {
32 | var posts []listing.Post
33 |
34 | for i := range s.Posts {
35 | Post := listing.Post{
36 | ID: s.Posts[i].ID,
37 | Content: s.Posts[i].Content,
38 | }
39 |
40 | posts = append(posts, Post)
41 | }
42 |
43 | return posts, nil
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/storage/postgres/main.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 |
7 | _ "github.com/lib/pq"
8 | )
9 |
10 | // NewStorage returns a new Postgres connection
11 | func NewStorage(host, port, user, password, dbName string) (*sql.DB, error) {
12 | connect := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
13 | host, port, user, password, dbName)
14 |
15 | db, err := sql.Open("postgres", connect)
16 | if err != nil {
17 | return nil, fmt.Errorf("unable to connect to database: %v", err)
18 | }
19 |
20 | err = db.Ping()
21 | if err != nil {
22 | return nil, err
23 | }
24 |
25 | return db, nil
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/storage/postgres/post.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "database/sql"
5 | "github.com/Zulbukharov/golang-ddd-hex/pkg/adding"
6 | "github.com/Zulbukharov/golang-ddd-hex/pkg/listing"
7 | )
8 |
9 | // Post defines the properties of a Post to be listed
10 | type Post struct {
11 | ID uint
12 | Content string
13 | AuthorID uint
14 | }
15 |
16 | // PostRepository keeps data in postgres db
17 | type PostRepository struct {
18 | db *sql.DB
19 | }
20 |
21 | // NewPostRepository return a new repo
22 | func NewPostRepository(db *sql.DB) *PostRepository {
23 | return &PostRepository{db}
24 | }
25 |
26 | // AddPost saves the given Post to the repository
27 | func (s *PostRepository) AddPost(u adding.Post) error {
28 | stmt, err := s.db.Prepare("INSERT INTO posts(content, author_id) VALUES($1, $2);")
29 | if err != nil {
30 | return err
31 | }
32 | defer stmt.Close()
33 |
34 | _, err = stmt.Exec(u.Content, u.AuthorID)
35 | if err != nil {
36 | return adding.ErrDuplicate
37 | }
38 | return nil
39 | }
40 |
41 | // GetAllPosts returns all Posts from the storage
42 | func (s *PostRepository) GetAllPosts() ([]listing.Post, error) {
43 | posts := make([]listing.Post, 0)
44 |
45 | var (
46 | id uint
47 | content string
48 | authorID uint
49 | )
50 |
51 | rows, err := s.db.Query("SELECT id, content, author_id FROM posts")
52 | if err != nil {
53 | return nil, err
54 | }
55 | defer rows.Close()
56 | for rows.Next() {
57 | err := rows.Scan(&id, &content, &authorID)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | posts = append(posts, listing.Post{ID: id, Content: content, AuthorID: authorID})
63 | }
64 | err = rows.Err()
65 | if err != nil {
66 | return nil, err
67 | }
68 |
69 | return posts, nil
70 | }
71 |
72 | func (s *PostRepository) DeletePost(id uint) error {
73 | stmt, err := s.db.Prepare("DELETE FROM posts WHERE id = $1;")
74 | if err != nil {
75 | return err
76 | }
77 | defer stmt.Close()
78 |
79 | _, err = stmt.Exec(id)
80 | if err != nil {
81 | return err
82 | }
83 | return nil
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/storage/postgres/user.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "database/sql"
5 | "github.com/Zulbukharov/golang-ddd-hex/pkg/login"
6 | "github.com/Zulbukharov/golang-ddd-hex/pkg/register"
7 | )
8 |
9 | // User defines the properties of a User to be listed
10 | type User struct {
11 | ID uint
12 | Username string
13 | Password string
14 | RoleID uint
15 | }
16 |
17 | // UserRepository keeps data in postgres db
18 | type UserRepository struct {
19 | db *sql.DB
20 | }
21 |
22 | // NewUserRepository returns a new repo
23 | func NewUserRepository(db *sql.DB) *UserRepository {
24 | return &UserRepository{db}
25 | }
26 |
27 | // Login checks the given User
28 | func (s *UserRepository) Login(u login.User) (uint, error) {
29 | stmt, err := s.db.Prepare("SELECT id, username, password FROM users WHERE username = $1 and password = $2")
30 | if err != nil {
31 | return 0, err
32 | }
33 | defer stmt.Close()
34 |
35 | var (
36 | id uint
37 | username string
38 | password string
39 | )
40 | err = stmt.QueryRow(u.Username, u.Password).Scan(&id, &username, &password)
41 | if err != nil {
42 | return 0, err
43 | }
44 | return id, err
45 | }
46 |
47 | func (s *UserRepository) Register(u register.User) (uint, error) {
48 | stmt, err := s.db.Prepare("INSERT INTO users(username, password, role_id) VALUES ($1, $2, 1) RETURNING id")
49 | if err != nil {
50 | return 0, err
51 | }
52 | defer stmt.Close()
53 |
54 | var id uint
55 | err = stmt.QueryRow(u.Username, u.Password).Scan(&id)
56 | return id, err
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/storage/postgres/userpolicy.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "database/sql"
5 | )
6 |
7 | type UserPolicyRepository struct {
8 | db *sql.DB
9 | }
10 |
11 | // NewUserRepository return a new repo
12 | func NewUserPolicyRepository(db *sql.DB) *UserPolicyRepository {
13 | return &UserPolicyRepository{db}
14 | }
15 |
16 | func (s *UserPolicyRepository) IsOwnerOfPost(userID, postID uint) bool {
17 | stmt, err := s.db.Prepare("SELECT COUNT(1) FROM posts WHERE author_id = $1 and id = $2;")
18 | if err != nil {
19 | return false
20 | }
21 | defer stmt.Close()
22 |
23 | var id uint
24 | err = stmt.QueryRow(userID, postID).Scan(&id)
25 | if id == 0 || err != nil {
26 | return false
27 | }
28 | return true
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/userpolicy/service.go:
--------------------------------------------------------------------------------
1 | package userpolicy
2 |
3 | // Service ...
4 | type Service interface {
5 | IsOwnerOfPost(userID uint, postID uint) bool
6 | //SetAdminRole(u User)
7 | //SetAuthorRole(u User)
8 | //SetUserRole(u User)
9 |
10 | //CanDeletePost(u User, postID uint) bool
11 | //CanDeleteComment(User) bool
12 | //CanReact(User) bool
13 | }
14 |
15 | type Repository interface {
16 | IsOwnerOfPost(userID uint, postID uint) bool
17 | //CanDeleteComment(User) bool
18 | //CanReact(User) bool
19 | }
20 |
21 | type service struct {
22 | authR Repository
23 | }
24 |
25 | // NewService ...
26 | func NewService(r Repository) Service {
27 | return &service{r}
28 | }
29 |
30 | // CanAddPost ...
31 | func (s *service) IsOwnerOfPost(userID uint, postID uint) bool {
32 | if userID == 0 || postID == 0 {
33 | return false
34 | }
35 | return s.authR.IsOwnerOfPost(userID, postID)
36 | }
37 |
--------------------------------------------------------------------------------