├── .env.example
├── .gitignore
├── Dockerfile
├── README.md
├── common
└── utils.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── internal
├── config
│ ├── config.go
│ └── db.go
├── handler
│ ├── auth.handler.go
│ ├── home.handler.go
│ ├── post.handler.go
│ └── user.handler.go
├── model
│ ├── post.go
│ └── user.go
├── router
│ ├── auth.route.go
│ ├── base.route.go
│ ├── post.route.go
│ └── user.route.go
└── views
│ ├── _posts.html
│ ├── createpost.html
│ ├── footer.html
│ ├── header.html
│ ├── index.html
│ ├── login.html
│ ├── profile.html
│ ├── signup.html
│ └── users.html
├── main.go
└── public
├── .DS_Store
├── css
└── style.css
├── img
├── .DS_Store
├── chat_icon.svg
├── delete.svg
├── guy_standing.svg
├── profile_header.jpg
└── profile_pic.svg
├── js
└── index.js
└── test.txt
/.env.example:
--------------------------------------------------------------------------------
1 | JWT_KEY=
2 | DB_HOST=
3 | DB_PORT=
4 | DB_USER=
5 | DB_PASS=
6 | DB_NAME=
7 | HASH_KEY=
8 | PGADMIN_DEFAULT_EMAIL=live@admin.com
9 | PGADMIN_DEFAULT_PASSWORD=password
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | .air.toml
3 | /tmp
4 | tmp
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | FROM golang:alpine
3 | # AS builder
4 | ADD . /go/src/go-social-network
5 | WORKDIR /go/src/go-social-network
6 | # # Git is required for fetching the dependencies.
7 | # RUN ls
8 | RUN apk update && apk add --no-cache git
9 | # RUN go get github.com/dsa0x/go-social-network
10 | COPY . .
11 | RUN go get
12 | RUN go install
13 |
14 | # # Build the binary.
15 | # RUN go build -o main .
16 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o main .
17 |
18 |
19 |
20 | RUN ls
21 | ENV PORT=8080
22 | EXPOSE 8080
23 | RUN chmod +x /go/src/go-social-network/main
24 | CMD ["/go/src/go-social-network/main"]
25 | ENTRYPOINT ["/go/src/go-social-network/main"]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chipper :: go-social-network
2 |
3 |
4 |
5 | # Introduction
6 | This is a mini social network created with golang, gorilla/mux and gorm. I built this to get more familiar with the Go language.
7 | I also decided to use go template just to get familiar with them. I'll probably go with rest apis for future projects.
8 | I intend to do some refactoring later on, but the functionalities are currently working. Feel free to fork it or make some suggestions to the current implementation.
9 | Inspiration gotten from yTakkar's .
10 | The website is live at https://chipper.daredev.xyz
11 |
12 | # Requirements
13 | Go - from v1.2
14 |
15 | PostgreSQL - from v10
16 |
17 | # Setup
18 | - Before setup, you must have PostgreSQL on your machine. Then clone this repository
19 |
20 | > https://github.com/dsa0x/go-social-network.git
21 |
22 | - cd into the project directory
23 |
24 | > cd go-social-network
25 |
26 | - Set environment variables
27 |
28 | - A sample is presented in the repository.
29 |
30 | > cp .env.example .env
31 |
32 | - Run application
33 |
34 | > go run main.go
35 |
36 |
37 | # Usage
38 |
39 | Endpoint | Usage
40 | ------------ | -------------
41 | / | The Home page. It also lists the posts of all users
42 | /signup | Use this route to sign up a new user
43 | /login | Authenticate user to access protected endpoints
44 | /user/{id} | The User profile. Also showing the number of posts, followers, and followings of the user
45 | /logout | Logout the user
46 | /user/follow | To follow a user
47 | /user/unfollow | To unfollow a user
48 | /users | Displays a list of all users
49 | /post/create | To create a new post
50 | /post/{id}/update | status.NotYetImplemented
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/common/utils.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "net/http"
7 |
8 | "reflect"
9 | "strings"
10 |
11 | "golang.org/x/crypto/bcrypt"
12 |
13 | "github.com/dsa0x/go-social-network/internal/config"
14 | "github.com/go-playground/validator/v10"
15 | )
16 |
17 | // Err error logger
18 | func Err(err interface{}, message ...string) {
19 | if err != nil {
20 | log.Fatal(message[0], err)
21 | }
22 | }
23 |
24 | //hashPassword to hash password
25 | func hashPassword(password string) ([]byte, error) {
26 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
27 | return bytes, err
28 | }
29 |
30 | var validate *validator.Validate
31 |
32 | func init() {
33 | validate = validator.New()
34 | }
35 |
36 | // Validate v
37 | func Validate(s interface{}) map[string][]string {
38 |
39 | if err := validate.Struct(s); err != nil {
40 |
41 | if err, ok := err.(*validator.InvalidValidationError); ok {
42 | panic(err)
43 | }
44 |
45 | //Validation errors occurred
46 | errors := make(map[string][]string)
47 |
48 | reflected := reflect.ValueOf(s)
49 | for _, err := range err.(validator.ValidationErrors) {
50 |
51 | field, _ := reflected.Type().FieldByName(err.StructField())
52 | var name string
53 |
54 | if name = field.Tag.Get("json"); name == "" {
55 | name = strings.ToLower(err.StructField())
56 | }
57 |
58 | switch err.Tag() {
59 | case "required":
60 | errors[name] = append(errors[name], "The "+name+" field is required")
61 | break
62 | case "email":
63 | errors[name] = append(errors[name], "The "+name+" field is not valid email")
64 | break
65 | case "eqfield":
66 | errors[name] = append(errors[name], "The "+name+" should be equal to the "+err.Param())
67 | break
68 | default:
69 | errors[name] = append(errors[name], "The "+name+" is invalid")
70 | break
71 | }
72 | }
73 |
74 | return errors
75 | }
76 |
77 | return nil
78 |
79 | }
80 |
81 | // CheckPasswordHash check the hash password match
82 | func CheckPasswordHash(password, hashedPassword string) bool {
83 | err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
84 | return err == nil
85 | }
86 |
87 | //HashPassword to hash password
88 | func HashPassword(password string) ([]byte, error) {
89 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
90 | return bytes, err
91 | }
92 |
93 | // Jsonify js
94 | func Jsonify(message interface{}) interface{} {
95 | // res := make(map[string]interface{})
96 | res, _ := json.Marshal(map[string]interface{}{"message": message})
97 | return res
98 |
99 | }
100 |
101 | // ExecTemplate execute template
102 | func ExecTemplate(w http.ResponseWriter, template string, data interface{}) {
103 |
104 | err := config.Tpl.ExecuteTemplate(w, template, data)
105 | if err != nil {
106 | log.Fatalln("template didn't execute: ", template, err)
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | app:
4 | container_name: go_social
5 | build:
6 | dockerfile: Dockerfile
7 | context: .
8 | ports:
9 | - 8080:8080
10 | # restart: on-failure
11 | # volumes:
12 | # - .:/go/src/go-social-network/
13 | # working_dir: /go/src/go-social-network/
14 | # command: ls
15 | depends_on:
16 | - db # Uncomment this when using postgres.
17 | networks:
18 | - fullstack
19 | db:
20 | image: postgres:latest
21 | container_name: db_postgres
22 | environment:
23 | - POSTGRES_USER=${DB_USER}
24 | - POSTGRES_PASSWORD=${DB_PASS}
25 | - POSTGRES_DB=${DB_NAME}
26 | - DATABASE_HOST=${DB_HOST}
27 | ports:
28 | - "5432:5432"
29 | volumes:
30 | - database_postgres:/var/lib/postgresql/data
31 | networks:
32 | - fullstack
33 | pgadmin:
34 | image: dpage/pgadmin4
35 | container_name: pgadmin_container
36 | environment:
37 | PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
38 | PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
39 | depends_on:
40 | - db
41 | ports:
42 | - "5050:80"
43 | networks:
44 | - fullstack
45 | restart: unless-stopped
46 |
47 | volumes:
48 | api:
49 | database_postgres: # Uncomment this when using postgres.
50 |
51 | # Networks to be created to facilitate communication between containers
52 | networks:
53 | fullstack:
54 | driver: bridge
55 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/dsa0x/go-social-network
2 |
3 | go 1.14
4 |
5 | require (
6 | // github.com/cosmtrek/air v1.21.2 // indirect
7 | github.com/creack/pty v1.1.11 // indirect
8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
9 | github.com/dsa0x/go-esocial v0.0.0-20200815154133-0c309e8e80f3
10 | github.com/fatih/color v1.9.0 // indirect
11 | github.com/go-playground/validator/v10 v10.3.0
12 | github.com/gorilla/mux v1.7.4
13 | github.com/gorilla/securecookie v1.1.1
14 | github.com/imdario/mergo v0.3.11 // indirect
15 | github.com/jinzhu/gorm v1.9.16
16 | github.com/joho/godotenv v1.3.0
17 | github.com/kelseyhightower/envconfig v1.4.0
18 | github.com/mattn/go-colorable v0.1.7 // indirect
19 | github.com/pelletier/go-toml v1.8.0 // indirect
20 | github.com/satori/go.uuid v1.2.0
21 | go.mongodb.org/mongo-driver v1.3.5
22 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd
23 | golang.org/x/sys v0.0.0-20200819171115-d785dc25833f // indirect
24 | )
25 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
4 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
7 | github.com/cosmtrek/air v1.21.2 h1:PwChdKs3qlSkKucKwwC04daw5eoy4SVgiEBQiHX5L9A=
8 | github.com/cosmtrek/air v1.21.2/go.mod h1:5EsgUqrBIHlW2ghNoevwPBEG1FQvF5XNulikjPte538=
9 | github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
10 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
11 | github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
12 | github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
16 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
17 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
18 | github.com/dsa0x/go-esocial v0.0.0-20200815154133-0c309e8e80f3 h1:AFgXy7/KciCppnoNuAJq3XQeubWa4aag2AE0l8Ye4mA=
19 | github.com/dsa0x/go-esocial v0.0.0-20200815154133-0c309e8e80f3/go.mod h1:vlX24kG19TcHA2QfAkSXdK6jooDuLWOPYpTd3LXJd7g=
20 | github.com/dsa0x/social-network v0.0.0-20200815150311-363d00a49209 h1:SHnYXMDQT5I+G9D11mEY1gNEgdT9kB2eCOE6AfAYJ0k=
21 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
22 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
23 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
24 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
25 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
26 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
27 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
28 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
29 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
30 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
31 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
32 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
33 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
34 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
35 | github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o=
36 | github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
37 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
38 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
39 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
40 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
41 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
42 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
43 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
44 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
45 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
46 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
47 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
48 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
49 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
50 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
51 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
52 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
53 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
54 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
55 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
56 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
57 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
58 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
59 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
60 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
61 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
62 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
63 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
64 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
65 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
66 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
67 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
68 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
69 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
70 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
71 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
72 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
73 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
74 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
75 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
76 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
77 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
78 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
79 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
80 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
81 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
82 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
83 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
84 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
85 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
86 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
87 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
88 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
89 | github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
90 | github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
91 | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
92 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
93 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
94 | github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
95 | github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
96 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
97 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
98 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
99 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
100 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
101 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
102 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
103 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
104 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
105 | github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
106 | github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
107 | github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
108 | github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
109 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
110 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
111 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
112 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
113 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
114 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
115 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
116 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
117 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
118 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
119 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
120 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
121 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
122 | github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
123 | github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
124 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
125 | github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
126 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
127 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
128 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
129 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
130 | github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
131 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
132 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
133 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
134 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
135 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
136 | github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
137 | github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
138 | github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
139 | github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
140 | github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
141 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
142 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
143 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
144 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
145 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
146 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
147 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
148 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
149 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
150 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
151 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
152 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
153 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
154 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
155 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
156 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
157 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
158 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
159 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
160 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
161 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
162 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
163 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
164 | github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo=
165 | github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
166 | go.mongodb.org/mongo-driver v1.3.5 h1:S0ZOruh4YGHjD7JoN7mIsTrNjnQbOjrmgrx6l6pZN7I=
167 | go.mongodb.org/mongo-driver v1.3.5/go.mod h1:Ual6Gkco7ZGQw8wE1t4tLnvBsf6yVSM60qW6TgOeJ5c=
168 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
169 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
170 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
171 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
172 | golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
173 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM=
174 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
175 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
176 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
177 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
178 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
179 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
180 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
181 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
182 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
183 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
184 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
185 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
186 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
187 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
188 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
189 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
190 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
191 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
192 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
193 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
194 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
195 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
196 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
197 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
198 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
199 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
200 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
201 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
202 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
203 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
204 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
205 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
206 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
207 | golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
208 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
209 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
210 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
211 | golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
212 | golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
213 | golang.org/x/sys v0.0.0-20200819171115-d785dc25833f h1:KJuwZVtZBVzDmEDtB2zro9CXkD9O0dpCv4o2LHbQIAw=
214 | golang.org/x/sys v0.0.0-20200819171115-d785dc25833f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
215 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
216 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
217 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
218 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
219 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
220 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
221 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
222 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
223 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
224 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
225 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
226 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
227 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
228 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
229 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
230 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
231 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
232 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
233 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
234 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
235 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
236 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
237 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
238 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
239 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
240 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
241 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
242 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
243 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
244 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
245 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
246 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
247 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
248 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
249 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
250 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
251 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
252 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
253 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
254 |
--------------------------------------------------------------------------------
/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "html/template"
5 | "log"
6 | "os"
7 |
8 | "github.com/joho/godotenv"
9 | "github.com/kelseyhightower/envconfig"
10 | )
11 |
12 | type envVars struct {
13 | Dbhost string `required:"true" envconfig:"DB_HOST"`
14 | Dbport string `required:"true" envconfig:"DB_PORT"`
15 | Dbuser string `required:"true" envconfig:"DB_USER"`
16 | Dbpassword string `required:"true" envconfig:"DB_PASS"`
17 | Dbname string `required:"true" envconfig:"DB_NAME"`
18 | JwtKey string `required:"true" envconfig:"JWT_KEY"`
19 | HashKey string `required:"true" envconfig:"HASH_KEY"`
20 | }
21 |
22 | //Env holds application config variables
23 | var Env envVars
24 |
25 | // Tpl template
26 | var Tpl *template.Template
27 |
28 | func init() {
29 |
30 | wd, err := os.Getwd()
31 | if err != nil {
32 | log.Println(err, "::Unable to get paths")
33 | }
34 |
35 | Tpl = template.Must(template.ParseGlob(wd + "/internal/views/*.html"))
36 |
37 | //load .env file
38 | err = godotenv.Load(wd + "/./.env")
39 |
40 | if err != nil {
41 | log.Println("Error loading .env file, falling back to cli passed env")
42 | }
43 |
44 | err = envconfig.Process("", &Env)
45 |
46 | if err != nil {
47 | log.Fatalln("Error loading environment variables", err)
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/internal/config/db.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 |
8 | "github.com/jinzhu/gorm"
9 | _ "github.com/jinzhu/gorm/dialects/postgres"
10 | )
11 |
12 | // DB returns the database object
13 | func DB() *gorm.DB {
14 |
15 | host := os.Getenv("DB_HOST")
16 | port := os.Getenv("DB_PORT")
17 | dbname := os.Getenv("DB_NAME")
18 | username := os.Getenv("DB_USER")
19 | password := os.Getenv("DB_PASS")
20 |
21 | psqlInfo := fmt.Sprintf("host=%s port=%s user=%s "+
22 | "password=%s dbname=%s sslmode=disable",
23 | host, port, username, password, dbname)
24 |
25 | db, err := gorm.Open("postgres", psqlInfo)
26 |
27 | db = db.BlockGlobalUpdate(true)
28 |
29 | if err != nil {
30 | log.Fatal("Database error: ", err)
31 | panic(err)
32 | }
33 | log.Println("Database Connected")
34 |
35 | return db
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/internal/handler/auth.handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "strings"
9 | "time"
10 |
11 | "github.com/dgrijalva/jwt-go"
12 | "github.com/dsa0x/go-social-network/common"
13 | "github.com/dsa0x/go-social-network/internal/config"
14 | "github.com/dsa0x/go-social-network/internal/model"
15 | "github.com/gorilla/securecookie"
16 | )
17 |
18 | type Credentials struct {
19 | Email string `json:"email" validate:"required,email"`
20 | Password string `json:"password" validate:"required"`
21 | }
22 |
23 | type Claims struct {
24 | Email string `json:"email"`
25 | ID uint `json:"id"`
26 | Name string `json:"username"`
27 | jwt.StandardClaims
28 | }
29 |
30 | type ClaimsCred struct {
31 | Email string `json:"email"`
32 | ID uint `json:"id"`
33 | Name string `json:"username"`
34 | }
35 |
36 | type ContextKey string
37 |
38 | type Error map[string][]string
39 |
40 | var jwtKey = []byte(config.Env.JwtKey)
41 | var hashKey = []byte(config.Env.HashKey)
42 | var secureCookie = securecookie.New(hashKey, nil)
43 |
44 | func Login(w http.ResponseWriter, r *http.Request) {
45 | w.Header().Set("Content-Type", "text/html; charset=utf-8")
46 |
47 | var errs = make(Error)
48 |
49 | if r.Method == http.MethodPost {
50 | creds := Credentials{}
51 | email := r.FormValue("email")
52 | password := r.FormValue("password")
53 | creds.Email = strings.ToLower(email)
54 | creds.Password = password
55 |
56 | if err := common.Validate(creds); err != nil {
57 | w.WriteHeader(http.StatusBadRequest)
58 | log.Println(err, "::Validation failed")
59 | errs["mismatch"] = append(errs["mismatch"], "Username or password incorrect")
60 | common.ExecTemplate(w, "login.html", err)
61 | return
62 | }
63 |
64 | user, err := model.FindOne(creds.Email)
65 | if err != nil {
66 | w.WriteHeader(http.StatusNotFound)
67 | log.Println(err, "::No user found")
68 | errs["mismatch"] = append(errs["mismatch"], "Username or password incorrect")
69 | common.ExecTemplate(w, "login.html", errs)
70 | return
71 | }
72 |
73 | matched := common.CheckPasswordHash(creds.Password, user.Password)
74 | if err != nil || !matched {
75 | log.Println(err, user.ID, user.UserName, "::password mismatch")
76 | w.WriteHeader(http.StatusForbidden)
77 | errs["mismatch"] = append(errs["mismatch"], "Username or password incorrect")
78 | common.ExecTemplate(w, "login.html", errs)
79 | return
80 | }
81 |
82 | expirationTime := time.Now().Add(30 * time.Minute)
83 |
84 | claims := &Claims{
85 | Email: user.Email,
86 | ID: user.ID,
87 | Name: user.UserName,
88 | StandardClaims: jwt.StandardClaims{
89 | // In JWT, the expiry time is expressed as unix milliseconds
90 | ExpiresAt: expirationTime.Unix(),
91 | },
92 | }
93 |
94 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
95 | tokenString, err := token.SignedString(jwtKey)
96 |
97 | if err != nil {
98 | log.Println(err, "token signature error")
99 | w.WriteHeader(http.StatusInternalServerError)
100 | errs["mismatch"] = append(errs["mismatch"], "An error occured. please try again")
101 | common.ExecTemplate(w, "login.html", errs)
102 | return
103 | }
104 |
105 | encoded, err := secureCookie.Encode("session", tokenString)
106 | expire := time.Now().Add(24 * time.Hour)
107 | cookie := &http.Cookie{
108 | Name: "session", Value: encoded, HttpOnly: true, Expires: expire,
109 | }
110 |
111 | http.SetCookie(w, cookie)
112 |
113 | http.Redirect(w, r, "/", http.StatusSeeOther)
114 | }
115 |
116 | common.ExecTemplate(w, "login.html", nil)
117 |
118 | }
119 |
120 | func SignUp(w http.ResponseWriter, r *http.Request) {
121 | var errs = make(Error)
122 | if r.Method == http.MethodPost {
123 |
124 | email := r.FormValue("email")
125 | password := r.FormValue("password")
126 | cpassword := r.FormValue("cpassword")
127 | username := r.FormValue("username")
128 | user := model.User{}
129 | user.Email = email
130 | user.Password = password
131 | user.ConfirmPassword = cpassword
132 | user.UserName = username
133 |
134 | if user.Password == "" || user.Password != user.ConfirmPassword {
135 | w.WriteHeader(http.StatusNotFound)
136 | errs["mismatch"] = append(errs["mismatch"], "Passwords do not match")
137 | common.ExecTemplate(w, "signup.html", errs)
138 | return
139 | }
140 |
141 | id, err := model.CreateUser(user)
142 | if err != nil {
143 | log.Println(err)
144 | w.WriteHeader(http.StatusBadRequest)
145 | if strings.Contains(err.Error(), `unique constraint "users_user_name_key"`) {
146 | errs["mismatch"] = append(errs["mismatch"], "Username already exists")
147 | } else if strings.Contains(err.Error(), `unique constraint "users_email_key"`) {
148 | errs["mismatch"] = append(errs["mismatch"], "Email already exists")
149 | } else {
150 | errs["mismatch"] = append(errs["mismatch"], err.Error())
151 | }
152 | common.ExecTemplate(w, "signup.html", errs)
153 | return
154 | }
155 |
156 | log.Printf("User with id %d created", id)
157 |
158 | // redirect
159 | http.Redirect(w, r, "/login", http.StatusSeeOther)
160 |
161 | }
162 |
163 | common.ExecTemplate(w, "signup.html", nil)
164 |
165 | }
166 |
167 | func Logout(w http.ResponseWriter, r *http.Request) {
168 |
169 | expire := time.Now().Add(-7 * 24 * time.Hour)
170 | cookie := &http.Cookie{
171 | Name: "session", Value: "", Expires: expire,
172 | }
173 |
174 | http.SetCookie(w, cookie)
175 | http.Redirect(w, r, "/", http.StatusSeeOther)
176 |
177 | }
178 |
179 | //Auth authenticate user
180 | func Auth(f http.HandlerFunc) http.HandlerFunc {
181 | return func(w http.ResponseWriter, r *http.Request) {
182 | w.Header().Set("Content-Type", "text/html; charset=utf-8")
183 | cookie, err := r.Cookie("session")
184 | if err != nil {
185 | log.Println(err, "Invalid Session")
186 | http.Redirect(w, r, "/guest", http.StatusSeeOther)
187 | return
188 | }
189 | var reqToken string
190 | if err = secureCookie.Decode("session", cookie.Value, &reqToken); err != nil {
191 | log.Println(err, "Unable to decode session")
192 | http.Redirect(w, r, "/login", http.StatusSeeOther)
193 | return
194 | }
195 | claims := &Claims{}
196 |
197 | token, err := jwt.ParseWithClaims(reqToken, claims, func(token *jwt.Token) (interface{}, error) {
198 | return jwtKey, nil
199 | })
200 | if err != nil {
201 | log.Println(err, "Invalid Authorization")
202 | http.Redirect(w, r, "/login", http.StatusSeeOther)
203 | return
204 | }
205 | if !token.Valid {
206 | log.Println(err, "Invalid Token")
207 | w.WriteHeader(http.StatusUnauthorized)
208 | http.Redirect(w, r, "/login", http.StatusSeeOther)
209 | return
210 | }
211 | const cKey = ContextKey("user")
212 | ctx := context.WithValue(r.Context(), cKey, ClaimsCred{Email: claims.Email, ID: claims.ID, Name: claims.Name})
213 |
214 | var data struct {
215 | ID string
216 | Name string
217 | Title string
218 | }
219 | data.ID = fmt.Sprint(claims.ID)
220 | data.Name = claims.Name
221 |
222 | r = r.WithContext(ctx)
223 | f(w, r)
224 | }
225 |
226 | }
227 |
--------------------------------------------------------------------------------
/internal/handler/home.handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "net/http"
5 | "strconv"
6 |
7 | "github.com/dsa0x/go-social-network/internal/model"
8 |
9 | "github.com/dsa0x/go-social-network/common"
10 | )
11 |
12 | type HomePosts struct {
13 | Posts []model.Post
14 | User ClaimsCred
15 | ID string
16 | LoggedInUserId string
17 | }
18 |
19 | // Home function for home handler
20 | func Home(w http.ResponseWriter, r *http.Request) {
21 |
22 | //get user from context
23 | const cKey = ContextKey("user")
24 | user := r.Context().Value(cKey)
25 |
26 | deletePostID := r.FormValue("postId")
27 | if user != nil && r.Method == http.MethodPost && deletePostID == "" {
28 | CreatePost2(w, r, "/", deletePostID)
29 | }
30 | if user != nil && r.Method == http.MethodPost && deletePostID != "" {
31 | DeletePost2(w, r, "/", deletePostID)
32 | }
33 |
34 | // fetch all posts for homepage
35 | _posts := []model.Post{}
36 | err := model.FetchPosts(&_posts)
37 | if err != nil {
38 | http.Redirect(w, r, "/login", http.StatusSeeOther)
39 | return
40 | }
41 | posts := HomePosts{}
42 | if user != nil {
43 | posts.User = user.(ClaimsCred)
44 | posts.ID = strconv.Itoa(int(posts.User.ID))
45 | posts.LoggedInUserId = posts.ID
46 | }
47 |
48 | posts.Posts = _posts
49 | common.ExecTemplate(w, "index.html", posts)
50 |
51 | }
52 |
53 | func Wrap(ID string, Title string) map[string]interface{} {
54 | return map[string]interface{}{
55 | "ID": ID,
56 | "Title": Title,
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/internal/handler/post.handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "strconv"
8 | "strings"
9 |
10 | "github.com/gorilla/mux"
11 |
12 | "github.com/dsa0x/go-social-network/common"
13 | "github.com/dsa0x/go-social-network/internal/model"
14 | )
15 |
16 | func CreatePost(w http.ResponseWriter, r *http.Request) {
17 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
18 | post := model.Post{}
19 |
20 | err := json.NewDecoder(r.Body).Decode(&post)
21 | if err != nil {
22 | http.Error(w, err.Error(), http.StatusBadRequest)
23 | return
24 | }
25 |
26 | const cKey = ContextKey("user")
27 | user := r.Context().Value(cKey).(ClaimsCred)
28 |
29 | post.CreatedBy = user.ID
30 | post.Author = user.Name
31 |
32 | post.Title = strings.TrimSpace(post.Title)
33 | post.Content = strings.TrimSpace(post.Content)
34 |
35 | ID, err := model.CreatePost(post)
36 | if err != nil {
37 | w.WriteHeader(http.StatusInternalServerError)
38 | w.Write([]byte(`{"message": "Internal server error"}`))
39 | return
40 | }
41 |
42 | resp := map[string]interface{}{"message": "Post created", "postId": ID}
43 | jsonPost, _ := json.Marshal(resp)
44 | w.WriteHeader(http.StatusOK)
45 | w.Write([]byte(jsonPost))
46 |
47 | }
48 |
49 | func CreatePost2(w http.ResponseWriter, r *http.Request, redirectPath string, deletePostID string) {
50 | const cKey = ContextKey("user")
51 | user := r.Context().Value(cKey)
52 | if user == nil {
53 | common.ExecTemplate(w, "index.html", user)
54 | http.Redirect(w, r, "/login", http.StatusSeeOther)
55 |
56 | }
57 |
58 | if user != nil && r.Method == http.MethodPost && deletePostID == "" {
59 | user := user.(ClaimsCred)
60 | post := model.Post{}
61 | chip := r.FormValue("chip")
62 | post.Content = strings.TrimSpace(chip)
63 | if post.Content == "" {
64 | http.Redirect(w, r, redirectPath, http.StatusSeeOther)
65 |
66 | }
67 | post.Author = user.Name
68 | post.CreatedBy = user.ID
69 | ID, err := model.CreatePost(post)
70 | if err != nil {
71 | w.WriteHeader(http.StatusInternalServerError)
72 | w.Write([]byte(`{"message": "Internal server error"}` + fmt.Sprint(ID)))
73 | return
74 | }
75 | http.Redirect(w, r, redirectPath, http.StatusSeeOther)
76 |
77 | }
78 | }
79 |
80 | func DeletePost2(w http.ResponseWriter, r *http.Request, redirectPath string, deletePostID string) {
81 | //delete post from homepage
82 | const cKey = ContextKey("user")
83 | user := r.Context().Value(cKey)
84 | if user == nil {
85 | common.ExecTemplate(w, "index.html", user)
86 | http.Redirect(w, r, "/login", http.StatusSeeOther)
87 |
88 | }
89 | if user != nil && r.Method == http.MethodPost && deletePostID != "" {
90 | user := user.(ClaimsCred)
91 | deletePostIDInt, _ := strconv.Atoi(deletePostID)
92 | _, err := model.DeletePost(uint(deletePostIDInt), user.ID)
93 | if err != nil {
94 | w.WriteHeader(http.StatusInternalServerError)
95 | w.Write([]byte(`{"message": "Internal server error"}`))
96 | return
97 | }
98 | // common.ExecTemplate(w, "index.html", posts)
99 | http.Redirect(w, r, "/", http.StatusSeeOther)
100 | // return
101 | }
102 | }
103 |
104 | func DeletePost(w http.ResponseWriter, r *http.Request) {
105 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
106 |
107 | var postId, err = strconv.Atoi(mux.Vars(r)["id"])
108 | if err != nil {
109 | w.WriteHeader(http.StatusBadRequest)
110 | w.Write([]byte(`{"message": "Bad Request"}`))
111 | return
112 | }
113 |
114 | const cKey = ContextKey("user")
115 | user := r.Context().Value(cKey).(ClaimsCred)
116 |
117 | ID, err := model.DeletePost(uint(postId), user.ID)
118 | if err != nil {
119 | w.WriteHeader(http.StatusNotFound)
120 | w.Write([]byte(`{"message": "Post not found"}`))
121 | return
122 | }
123 |
124 | resp := map[string]interface{}{"message": "Post deleted", "postId": ID}
125 | jsonPost, _ := json.Marshal(resp)
126 | // w.WriteHeader(http.StatusFound)
127 | w.Write([]byte(jsonPost))
128 |
129 | }
130 |
131 | func FetchAllPosts(w http.ResponseWriter, r *http.Request) {
132 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
133 | posts := []model.Post{}
134 |
135 | err := model.FetchPosts(&posts)
136 | if err != nil {
137 | w.WriteHeader(http.StatusInternalServerError)
138 | w.Write([]byte(`{"message": "An error occurred"}`))
139 | return
140 | }
141 |
142 | jsonPosts, _ := json.Marshal(posts)
143 | w.WriteHeader(http.StatusFound)
144 | w.Write([]byte(jsonPosts))
145 |
146 | }
147 | func FetchUserPosts(w http.ResponseWriter, r *http.Request) {
148 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
149 | posts := []model.Post{}
150 |
151 | const cKey = ContextKey("user")
152 | user := r.Context().Value(cKey).(ClaimsCred)
153 |
154 | err := model.FetchUserPosts(&posts, user.ID)
155 | if err != nil {
156 | w.WriteHeader(http.StatusInternalServerError)
157 | w.Write([]byte(`{"message": "An error occurred"}`))
158 | return
159 | }
160 |
161 | jsonPosts, _ := json.Marshal(posts)
162 | w.WriteHeader(http.StatusFound)
163 | w.Write([]byte(jsonPosts))
164 |
165 | }
166 | func UpdatePost(w http.ResponseWriter, r *http.Request) {
167 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
168 | post := model.Post{}
169 | err := json.NewDecoder(r.Body).Decode(&post)
170 | if err != nil {
171 | http.Error(w, err.Error(), http.StatusBadRequest)
172 | return
173 | }
174 |
175 | postID, err := strconv.Atoi(mux.Vars(r)["id"])
176 | if err != nil {
177 | w.WriteHeader(http.StatusBadRequest)
178 | w.Write([]byte(`{"message": "Bad Request"}`))
179 | return
180 | }
181 |
182 | const cKey = ContextKey("user")
183 | user := r.Context().Value(cKey).(ClaimsCred)
184 |
185 | post, err = model.UpdatePost(post, uint(postID), user.ID)
186 | if err != nil {
187 | w.WriteHeader(http.StatusInternalServerError)
188 | w.Write([]byte(`{"message": "An error occurred"}`))
189 | return
190 | }
191 |
192 | jsonPost, _ := json.Marshal(post)
193 | w.WriteHeader(http.StatusFound)
194 | w.Write([]byte(jsonPost))
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/internal/handler/user.handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "net/http"
5 | "strconv"
6 | "strings"
7 |
8 | "github.com/dsa0x/go-social-network/common"
9 | "github.com/dsa0x/go-social-network/internal/model"
10 |
11 | "github.com/gorilla/mux"
12 | )
13 |
14 | type Profile struct {
15 | Username string
16 | ID string
17 | Following int
18 | Followers int
19 | IsFollower bool
20 | Avatar string
21 | MyProfile bool
22 | Posts []model.Post
23 | User ClaimsCred
24 | LoggedInUserId string
25 | LoggedInUsername string
26 | PostCount int
27 | }
28 |
29 | func GetUser(w http.ResponseWriter, r *http.Request) {
30 |
31 | const cKey = ContextKey("user")
32 | loggedUser := r.Context().Value(cKey)
33 |
34 | if loggedUser == nil {
35 | common.ExecTemplate(w, "index.html", loggedUser)
36 | http.Redirect(w, r, "/login", http.StatusSeeOther)
37 | // return
38 | }
39 | params := mux.Vars(r)
40 | redirectPath := "/user/" + params["id"]
41 | deletePostID := r.FormValue("postId")
42 | if loggedUser != nil && r.Method == http.MethodPost && deletePostID == "" {
43 | CreatePost2(w, r, redirectPath, deletePostID)
44 | }
45 | if loggedUser != nil && r.Method == http.MethodPost && deletePostID != "" {
46 | DeletePost2(w, r, "/", deletePostID)
47 | }
48 |
49 | profile := Profile{}
50 |
51 | id, err := strconv.Atoi(params["id"])
52 | if err != nil {
53 | w.WriteHeader(http.StatusBadRequest)
54 | w.Write([]byte(`{"message": "Bad request"}`))
55 | return
56 | }
57 | user, err := model.FindByID(uint(id))
58 | if err != nil {
59 | w.WriteHeader(http.StatusNotFound)
60 | w.Write([]byte(`{"message": "User not found"}`))
61 | return
62 | }
63 | loggedInUser := loggedUser.(ClaimsCred)
64 | if loggedInUser.ID == user.ID {
65 | profile.MyProfile = true
66 | }
67 |
68 | followings, err := model.CountFollowings(user.ID)
69 | if err != nil {
70 | w.WriteHeader(http.StatusNotFound)
71 | w.Write([]byte(`{"message": "User not found"}`))
72 | return
73 | }
74 | followers, err := model.CountFollowers(user.ID)
75 | if err != nil {
76 | w.WriteHeader(http.StatusNotFound)
77 | w.Write([]byte(`{"message": "User not found"}`))
78 | return
79 | }
80 | isFollower, _ := model.IsFollower(loggedInUser.ID, user.ID)
81 |
82 | posts := []model.Post{}
83 | err = model.FetchUserPosts(&posts, user.ID)
84 | if err != nil {
85 | w.WriteHeader(http.StatusInternalServerError)
86 | w.Write([]byte(`{"message": "An error occurred"}`))
87 | return
88 | }
89 | count, err := model.CountUserPosts(user.ID)
90 | if err != nil {
91 | w.WriteHeader(http.StatusInternalServerError)
92 | w.Write([]byte(`{"message": "An error occurred"}`))
93 | return
94 | }
95 |
96 | profile.Following = followings
97 | profile.Followers = followers
98 | profile.IsFollower = isFollower
99 | profile.ID = strconv.Itoa(int(user.ID))
100 | profile.Username = strings.Title(user.UserName)
101 | profile.Avatar = "profile_pic.svg"
102 | profile.Avatar = "/public/img/" + profile.Avatar
103 | profile.Posts = posts
104 | profile.User.Name = profile.Username
105 | profile.User.ID = loggedInUser.ID
106 | profile.LoggedInUserId = strconv.Itoa(int(loggedInUser.ID))
107 | profile.LoggedInUsername = loggedInUser.Name
108 | profile.PostCount = count
109 |
110 | // w.WriteHeader(http.StatusFound)
111 | common.ExecTemplate(w, "profile.html", profile)
112 |
113 | }
114 | func FollowUser(w http.ResponseWriter, r *http.Request) {
115 | // w.Header().Set("Content-Type", "text/html; charset=utf-8")
116 | params := mux.Vars(r)
117 | id, err := strconv.Atoi(params["id"])
118 | if err != nil {
119 | w.WriteHeader(http.StatusBadRequest)
120 | w.Write([]byte(`{"message": "Bad request"}`))
121 | return
122 | }
123 | const cKey = ContextKey("user")
124 | user := r.Context().Value(cKey).(ClaimsCred)
125 |
126 | _, err = model.Follow(user.ID, uint(id))
127 | if err != nil {
128 | http.Redirect(w, r, "/login", http.StatusNotFound)
129 | w.WriteHeader(http.StatusNotFound)
130 | w.Write([]byte(`{"message": "User not found"}`))
131 | return
132 | }
133 | http.Redirect(w, r, "/user/"+params["id"], http.StatusSeeOther)
134 | return
135 | }
136 |
137 | func UnfollowUser(w http.ResponseWriter, r *http.Request) {
138 | w.Header().Set("Content-Type", "text/html; charset=utf-8")
139 | params := mux.Vars(r)
140 | id, err := strconv.Atoi(params["id"])
141 | if err != nil {
142 | w.WriteHeader(http.StatusBadRequest)
143 | w.Write([]byte(`{"message": "Bad request"}`))
144 | return
145 | }
146 | const cKey = ContextKey("user")
147 | user := r.Context().Value(cKey).(ClaimsCred)
148 |
149 | _, err = model.Unfollow(user.ID, uint(id))
150 | if err != nil {
151 | w.WriteHeader(http.StatusNotFound)
152 | w.Write([]byte(`{"message": "User not found"}`))
153 | return
154 | }
155 |
156 | http.Redirect(w, r, "/user/"+params["id"], http.StatusSeeOther)
157 | return
158 | }
159 |
160 | func GetAllUsers(w http.ResponseWriter, r *http.Request) {
161 | users := []model.User{}
162 | err := model.FetchUsers(&users)
163 | if err != nil {
164 | common.ExecTemplate(w, "users.html", nil)
165 | return
166 | }
167 |
168 | const cKey = ContextKey("user")
169 | user := r.Context().Value(cKey).(ClaimsCred)
170 | userID := strconv.Itoa(int(user.ID))
171 | data := struct {
172 | LoggedInUserId string
173 | Users []model.User
174 | }{
175 | userID,
176 | users,
177 | }
178 |
179 | common.ExecTemplate(w, "users.html", data)
180 | }
181 |
--------------------------------------------------------------------------------
/internal/model/post.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/jinzhu/gorm"
7 | )
8 |
9 | type Post struct {
10 | gorm.Model `json:"-"`
11 | Title string
12 | Content string
13 | CreatedBy uint
14 | Author string
15 | }
16 |
17 | func init() {
18 | // Db.CreateTable(&Post{})
19 | }
20 |
21 | // CreatePost create post
22 | func CreatePost(post Post) (uint, error) {
23 | createdPost := Db.Create(&post)
24 | if createdPost.Error != nil {
25 | log.Println(createdPost.Error)
26 | return post.ID, createdPost.Error
27 | }
28 | return post.ID, nil
29 | }
30 |
31 | // DeletePost delete post
32 | func DeletePost(postID uint, userID uint) (uint, error) {
33 | deletedPost := Db.Unscoped().Delete(Post{}, "ID = ? and created_by = ?", postID, userID)
34 | if deletedPost.Error != nil {
35 | log.Println(deletedPost.Error)
36 | return postID, deletedPost.Error
37 | }
38 |
39 | return postID, nil
40 | }
41 |
42 | // UpdatePost updates the post
43 | func UpdatePost(post Post, postID uint, userID uint) (Post, error) {
44 | oldPost := Post{}
45 | updatedPost := Db.First(&oldPost, postID)
46 | oldPost.Title = post.Title
47 | oldPost.Content = post.Content
48 | updatedPost = Db.Save(&oldPost)
49 | if updatedPost.Error != nil {
50 | log.Println(updatedPost.Error)
51 | return oldPost, updatedPost.Error
52 | }
53 |
54 | return oldPost, nil
55 | }
56 |
57 | // FetchPosts fetch post
58 | func FetchPosts(posts *[]Post) error {
59 | allPosts := Db.Model(&Post{}).Order("created_at desc").Find(&posts)
60 | if allPosts.Error != nil {
61 | log.Println(allPosts.Error)
62 | return allPosts.Error
63 | }
64 |
65 | return nil
66 | }
67 |
68 | func FetchUserPosts(posts *[]Post, userID uint) error {
69 | allPosts := Db.Model(&Post{}).Order("created_at desc").Find(&posts, "created_by = ?", userID)
70 | if allPosts.Error != nil {
71 | log.Println(allPosts.Error)
72 | return allPosts.Error
73 | }
74 |
75 | return nil
76 | }
77 | func CountUserPosts(userID uint) (int, error) {
78 | var count int
79 | allPosts := Db.Model(&Post{}).Where("created_by = ?", userID).Count(&count)
80 | if allPosts.Error != nil {
81 | log.Println(allPosts.Error)
82 | return count, allPosts.Error
83 | }
84 |
85 | return count, nil
86 | }
87 |
--------------------------------------------------------------------------------
/internal/model/user.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/dsa0x/go-social-network/common"
7 | "github.com/dsa0x/go-social-network/internal/config"
8 | "github.com/jinzhu/gorm"
9 | )
10 |
11 | // Db database
12 | var Db = config.DB()
13 |
14 | func init() {
15 | // defer db.Close()
16 | // Db.DropTableIfExists(&User{})
17 | // Db.CreateTable(&User{})
18 | Db.AutoMigrate(&User{})
19 | }
20 |
21 | // User struct declaration
22 | type User struct {
23 | gorm.Model
24 | UserName string `json:"username" binding:"required" gorm:"unique;not null" validate:"required,min=5,max=100"`
25 | Email string `json:"email" binding:"required" gorm:"unique;not null" validate:"required,min=8,max=100"`
26 | Friends []User `gorm:"many2many:friendships;association_jointable_foreignkey:friend_id"`
27 | Password string `json:"password,omitempty" binding:"required" validate:"required,min=6,max=100"`
28 | ConfirmPassword string `json:"confirmPassword,omitempty" binding:"required" sql:"-"`
29 | }
30 |
31 | func FetchUsers(users *[]User) error {
32 | allUsers := Db.Model(&User{}).Order("user_name asc").Find(&users)
33 | if allUsers.Error != nil {
34 | log.Println(allUsers.Error)
35 | return allUsers.Error
36 | }
37 |
38 | return nil
39 | }
40 |
41 | // FindOne finds one user by emails
42 | func FindOne(email string) (*User, error) {
43 | user := &User{}
44 |
45 | if err := Db.Where("email = ?", email).First(user).Error; err != nil {
46 | // var resp := map[string]interface{} {"status": false, "message": "invalid "}
47 | return user, err
48 | }
49 |
50 | return user, nil
51 |
52 | }
53 |
54 | // FindByID finds one user by id
55 | func FindByID(ID uint) (*User, error) {
56 | user := &User{}
57 |
58 | if err := Db.First(user, ID).Error; err != nil {
59 | return user, err
60 | }
61 |
62 | return user, nil
63 |
64 | }
65 |
66 | // CreateUser creates a new user
67 | func CreateUser(user User) (uint, error) {
68 | pass, err := common.HashPassword(user.Password)
69 |
70 | if err != nil {
71 | return user.ID, err
72 | }
73 | user.Password = string(pass)
74 | createdUser := Db.Create(&user)
75 | var errMessage = createdUser.Error
76 |
77 | if createdUser.Error != nil {
78 | return user.ID, errMessage
79 | }
80 | return user.ID, nil
81 | }
82 |
83 | func Follow(userID uint, friendID uint) (uint, error) {
84 |
85 | user := User{}
86 | friend, err := FindByID(friendID)
87 | if err != nil {
88 | log.Println(err)
89 | return userID, err
90 | }
91 | Db.Preload("Friends").First(&user, "id = ?", userID)
92 | Db.Model(&user).Association("Friends").Append(friend)
93 | return userID, nil
94 | }
95 |
96 | func Unfollow(userID uint, friendID uint) (uint, error) {
97 |
98 | user := User{}
99 | friend, err := FindByID(friendID)
100 | if err != nil {
101 | log.Println(err)
102 | return userID, err
103 | }
104 | Db.Preload("Friends").First(&user, "id = ?", userID)
105 | Db.Model(&user).Association("Friends").Delete(friend)
106 | return userID, nil
107 | }
108 |
109 | func IsFollower(userID uint, friendID uint) (bool, error) {
110 |
111 | friend := User{}
112 | user := User{}
113 | friend.ID = friendID
114 | user.ID = userID
115 | Db.Model(&user).Association("Friends").Find(&friend)
116 | if friend.Email == "" {
117 | return false, nil
118 | }
119 | return true, nil
120 | }
121 | func CountFollowings(userID uint) (int, error) {
122 | user := User{}
123 | user.ID = userID
124 | count := Db.Model(&user).Association("Friends").Count()
125 | return count, nil
126 | }
127 | func CountFollowers(userID uint) (int, error) {
128 | count := 0
129 | Db.Table("friendships").Select("friend_id").Where("friend_id = ?", userID).Count(&count)
130 | return count, nil
131 | }
132 |
--------------------------------------------------------------------------------
/internal/router/auth.route.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "github.com/dsa0x/go-social-network/internal/handler"
5 | )
6 |
7 | func init() {
8 |
9 | APIRouter.HandleFunc("/login", handler.Login).Methods("GET", "POST")
10 | APIRouter.HandleFunc("/logout", handler.Logout).Methods("GET", "POST")
11 | APIRouter.HandleFunc("/signup", handler.SignUp).Methods("GET", "POST")
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/internal/router/base.route.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "github.com/dsa0x/go-social-network/internal/handler"
5 | "github.com/gorilla/mux"
6 | )
7 |
8 | // APIRouter is the main router
9 | var APIRouter = mux.NewRouter()
10 |
11 | func init() {
12 |
13 | APIRouter.HandleFunc("/", handler.Auth(handler.Home)).Methods("GET", "POST")
14 | APIRouter.HandleFunc("/guest", handler.Home).Methods("GET", "POST")
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/internal/router/post.route.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "github.com/dsa0x/go-social-network/internal/handler"
5 | )
6 |
7 | func init() {
8 |
9 | APIRouter.HandleFunc("/post/create", handler.Auth(handler.CreatePost)).Methods("POST")
10 | APIRouter.HandleFunc("/post/{id}", handler.Auth(handler.DeletePost)).Methods("DELETE")
11 | APIRouter.HandleFunc("/posts", handler.Auth(handler.FetchAllPosts)).Methods("GET")
12 | APIRouter.HandleFunc("/posts/mine", handler.Auth(handler.FetchUserPosts)).Methods("GET")
13 | APIRouter.HandleFunc("/post/{id}", handler.Auth(handler.UpdatePost)).Methods("PUT")
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/internal/router/user.route.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "github.com/dsa0x/go-social-network/internal/handler"
5 | )
6 |
7 | func init() {
8 |
9 | APIRouter.HandleFunc("/user/{id}", handler.Auth(handler.GetUser)).Methods("GET", "POST")
10 | APIRouter.HandleFunc("/user/{id}/follow", handler.Auth(handler.FollowUser)).Methods("GET", "POST")
11 | APIRouter.HandleFunc("/user/{id}/unfollow", handler.Auth(handler.UnfollowUser)).Methods("GET", "POST")
12 | APIRouter.HandleFunc("/users", handler.Auth(handler.GetAllUsers)).Methods("GET", "POST")
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/internal/views/_posts.html:
--------------------------------------------------------------------------------
1 | {{define "_posts"}} {{$loggedInUserID := print .ID}}
2 | {{$loggedInUserInt := .User.ID}}
3 |