├── .air.toml
├── .gitignore
├── Procfile
├── README.md
├── assets
└── icons
│ ├── close.png
│ ├── deleted.png
│ ├── done.png
│ ├── edit.svg
│ └── trash.svg
├── bin
└── app
├── db
└── db.go
├── go.mod
├── go.sum
├── handlers.go
├── helpers
└── helpers.go
├── main.go
├── models
└── models.go
├── routes.go
├── templates
├── category.html
├── design.html
├── footer.html
├── header.html
├── history.html
├── home.html
├── item.html
└── list.html
└── tmp
└── build-errors.log
/.air.toml:
--------------------------------------------------------------------------------
1 | root = "."
2 | testdata_dir = "testdata"
3 | tmp_dir = "tmp"
4 |
5 | [build]
6 | bin = "tmp\\main.exe"
7 | cmd = "go build -o ./tmp/main.exe ."
8 | delay = 1000
9 | exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules]
10 | exclude_file = []
11 | exclude_regex = ["_test.go"]
12 | exclude_unchanged = false
13 | follow_symlink = false
14 | full_bin = ""
15 | include_dir = []
16 | include_ext = ["go", "tpl", "tmpl", "html"]
17 | kill_delay = "0s"
18 | log = "build-errors.log"
19 | send_interrupt = false
20 | stop_on_error = true
21 |
22 | [color]
23 | app = ""
24 | build = "yellow"
25 | main = "magenta"
26 | runner = "green"
27 | watcher = "cyan"
28 |
29 | [log]
30 | time = false
31 |
32 | [misc]
33 | clean_on_exit = false
34 |
35 | [screen]
36 | clear_on_rebuild = false
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: bin/app
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a Go web APP that I made to learn some basics with go.
2 |
3 | This project uses [HTMX](https://github.com/bigskysoftware/htmx) to send AJAX requests, this expects a response in html format.
4 | Due to this, all the CRUD based operations retrieve small html partials that are inserted in to the DOM.
5 | This increases user experience because no page reload is needed.
6 |
7 | Technologies used in this project and why:
8 |
9 | BACKEND:
10 | - Go: The main programming language, the http server is running here.
11 | - [Go Gin](https://github.com/gin-gonic/gin): Framework for simplify the code.
12 | - PostgreSQL: Database Engine, running on AWS RDS free tier instance.
13 | - [GORM](https://github.com/go-gorm/gorm): ORM for interacting with the database, used to increase simplicity and security.
14 | - [IP Limiter](https://github.com/ulule/limiter): IP Rate Limiter middleware. In-Memory cache used for increase security.
15 | - Heroku: Server is running here, on a free dyno.
16 |
17 | FRONTEND:
18 | - [HTMX](https://github.com/bigskysoftware/htmx): Used for adding reactivity without the need of refreshing the page. Acomplished sending and receiving AJAX request.
19 | - [Alpinejs](https://github.com/tailwindlabs/tailwindcss): Adding js behaviour in HTML. In this case its just showing/hiding forms and interacting with localStorage.
20 | - [TailwindCSS](https://github.com/tailwindlabs/tailwindcss): CSS framework for rapid UI development.
21 |
--------------------------------------------------------------------------------
/assets/icons/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diazmartinx/go-webapp/19b778ef226b70a78e0813f7892de382aedf2aa9/assets/icons/close.png
--------------------------------------------------------------------------------
/assets/icons/deleted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diazmartinx/go-webapp/19b778ef226b70a78e0813f7892de382aedf2aa9/assets/icons/deleted.png
--------------------------------------------------------------------------------
/assets/icons/done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diazmartinx/go-webapp/19b778ef226b70a78e0813f7892de382aedf2aa9/assets/icons/done.png
--------------------------------------------------------------------------------
/assets/icons/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/trash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/app:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diazmartinx/go-webapp/19b778ef226b70a78e0813f7892de382aedf2aa9/bin/app
--------------------------------------------------------------------------------
/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "gorm.io/driver/postgres"
8 | "gorm.io/gorm"
9 | )
10 |
11 | // DB SERVER (AWS)
12 | func dbvar() string {
13 |
14 | // THIS IS FOT GET THE ENV VARIABLES IN LOCAL, IN HEROKU IT IS DIFFERENT
15 | //err := godotenv.Load(".env")
16 | //if err != nil {
17 | // fmt.Println("Error loading .env file / HEROKU DEPLOY ?") // IF NO .env -> IS RUNNING ON HEROKU
18 | //}
19 |
20 | // CONFIG VARS
21 | DB_HOST := os.Getenv("DB_HOST")
22 | DB_PORT := os.Getenv("DB_PORT")
23 | DB_PASSWORD := os.Getenv("DB_PASSWORD")
24 | DB_NAME := os.Getenv("DB_NAME")
25 | DB_USER := os.Getenv("DB_USER")
26 |
27 | dsn := ("host=" + DB_HOST +
28 | " user=" + DB_USER +
29 | " password=" + DB_PASSWORD +
30 | " dbname=" + DB_NAME +
31 | " port=" + DB_PORT +
32 | " sslmode=disable TimeZone=Asia/Shanghai") //
33 |
34 | // LOCAL DB
35 | //dsn := "host=localhost user=root password=secret dbname=root port=5431 sslmode=disable TimeZone=Asia/Shanghai"
36 |
37 | return dsn
38 | }
39 |
40 | var DB = func() (db *gorm.DB) {
41 |
42 | if db, err := gorm.Open(postgres.Open(dbvar()), &gorm.Config{}); err != nil {
43 | fmt.Println("Connection to database failed", err)
44 | panic(err)
45 | } else {
46 | fmt.Println("Connected to database")
47 | return db
48 | }
49 | }()
50 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module app
2 |
3 | go 1.18
4 | // +heroku goVersion go1.18
5 |
6 | require (
7 | github.com/gin-gonic/gin v1.7.7
8 | github.com/joho/godotenv v1.4.0
9 | github.com/ulule/limiter/v3 v3.10.0
10 | gorm.io/driver/postgres v1.3.3
11 | gorm.io/gorm v1.23.4
12 | )
13 |
14 | require (
15 | github.com/gin-contrib/sse v0.1.0 // indirect
16 | github.com/go-playground/locales v0.13.0 // indirect
17 | github.com/go-playground/universal-translator v0.17.0 // indirect
18 | github.com/go-playground/validator/v10 v10.4.1 // indirect
19 | github.com/golang/protobuf v1.5.2 // indirect
20 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
21 | github.com/jackc/pgconn v1.11.0 // indirect
22 | github.com/jackc/pgio v1.0.0 // indirect
23 | github.com/jackc/pgpassfile v1.0.0 // indirect
24 | github.com/jackc/pgproto3/v2 v2.2.0 // indirect
25 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
26 | github.com/jackc/pgtype v1.10.0 // indirect
27 | github.com/jackc/pgx/v4 v4.15.0 // indirect
28 | github.com/jinzhu/inflection v1.0.0 // indirect
29 | github.com/jinzhu/now v1.1.4 // indirect
30 | github.com/json-iterator/go v1.1.9 // indirect
31 | github.com/leodido/go-urn v1.2.0 // indirect
32 | github.com/mattn/go-isatty v0.0.12 // indirect
33 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
34 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
35 | github.com/pkg/errors v0.9.1 // indirect
36 | github.com/ugorji/go/codec v1.1.7 // indirect
37 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
38 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
39 | golang.org/x/text v0.3.7 // indirect
40 | google.golang.org/protobuf v1.26.0 // indirect
41 | gopkg.in/yaml.v2 v2.4.0 // indirect
42 | )
43 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
3 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
4 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
5 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
6 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
7 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
8 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
13 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
14 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
15 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
16 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
17 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
18 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
19 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
20 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
21 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
22 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
23 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
24 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
25 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
26 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
27 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
28 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
29 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
30 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
31 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
32 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
33 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
34 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
36 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
37 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
38 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
39 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
40 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
41 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
42 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
43 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
44 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
45 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
46 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
47 | github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
48 | github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
49 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
50 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
51 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
52 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
53 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
54 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
55 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
56 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
57 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
58 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
59 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
60 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
61 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
62 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
63 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
64 | github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
65 | github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
66 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
67 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
68 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
69 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
70 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
71 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
72 | github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
73 | github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
74 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
75 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
76 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
77 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
78 | github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
79 | github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
80 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
81 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
82 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
83 | github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
84 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
85 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
86 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
87 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
88 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
89 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
90 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
91 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
92 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
93 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
94 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
95 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
96 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
97 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
98 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
99 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
100 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
101 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
102 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
103 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
104 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
105 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
106 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
107 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
108 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
109 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
110 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
111 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
112 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
113 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
114 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
115 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
116 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
117 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
118 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
119 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
120 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
121 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
122 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
123 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
124 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
125 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
126 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
127 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
128 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
129 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
130 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
131 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
132 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
133 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
134 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
135 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
136 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
137 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
138 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
139 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
140 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
141 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
142 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
143 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
144 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
145 | github.com/ulule/limiter/v3 v3.10.0 h1:C9mx3tgxYnt4pUYKWktZf7aEOVPbRYxR+onNFjQTEp0=
146 | github.com/ulule/limiter/v3 v3.10.0/go.mod h1:NqPA/r8QfP7O11iC+95X6gcWJPtRWjKrtOUw07BTvoo=
147 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
148 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
149 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
150 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
151 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
152 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
153 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
154 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
155 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
156 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
157 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
158 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
159 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
160 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
161 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
162 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
163 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
164 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
165 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
166 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
167 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
168 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
169 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
170 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
171 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
172 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
173 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
174 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
175 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
176 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
177 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
178 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
179 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
180 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
181 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
182 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
183 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
184 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
185 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
186 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
187 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
188 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
189 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
190 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
191 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
192 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
193 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
194 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
195 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
196 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
197 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
198 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
199 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
200 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
201 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
202 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
203 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
204 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
205 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
206 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
207 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
208 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
209 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
210 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
211 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
212 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
213 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
214 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
215 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
216 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
217 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
218 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
219 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
220 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
221 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
222 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
223 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
224 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
225 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
226 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
227 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
228 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
229 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
230 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
231 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
232 | gorm.io/driver/postgres v1.3.3 h1:y6DU2kJgDNisxfAlmxRaQZOIy4ytnuYrpzpSFYnSfCY=
233 | gorm.io/driver/postgres v1.3.3/go.mod h1:y0vEuInFKJtijuSGu9e5bs5hzzSzPK+LancpKpvbRBw=
234 | gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
235 | gorm.io/gorm v1.23.4 h1:1BKWM67O6CflSLcwGQR7ccfmC4ebOxQrTfOQGRE9wjg=
236 | gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
237 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
238 |
--------------------------------------------------------------------------------
/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "app/db"
5 | "app/helpers"
6 | "app/models"
7 | "net/http"
8 | "strconv"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | const (
14 | ContentTypeBinary = "application/octet-stream"
15 | ContentTypeForm = "application/x-www-form-urlencoded"
16 | ContentTypeJSON = "application/json"
17 | ContentTypeHTML = "text/html; charset=utf-8"
18 | ContentTypeText = "text/plain; charset=utf-8"
19 | )
20 |
21 | func GenerateUrl() string {
22 | var url string
23 |
24 | urlExist := true
25 | for urlExist {
26 | url = helpers.GenerateRandomString(7)
27 | //Verify is the url is unique
28 | list := models.List{}
29 | db.DB.Where("url = ?", url).First(&list)
30 | if list.ID == 0 {
31 | urlExist = false
32 | }
33 | }
34 |
35 | return url
36 | }
37 |
38 | func CreateList(c *gin.Context) {
39 | url := GenerateUrl()
40 | var list models.List
41 | list.Url = url
42 | db.DB.Create(&list)
43 |
44 | c.Redirect(http.StatusMovedPermanently, "/"+url)
45 | c.Abort()
46 | }
47 |
48 | func Home(c *gin.Context) {
49 | render(c, gin.H{}, "home.html")
50 | }
51 |
52 | func ShowList(c *gin.Context) {
53 | url := c.Param("url")
54 | var list models.List
55 | //var histories []models.CatChange
56 | //var deleteHistories []models.CatChange
57 | var copyAlert bool // DEFAULT -> FALSE
58 |
59 | db.DB.Preload("Categories.Items").Where("url = ?", url).First(&list)
60 |
61 | if list.ID != 0 {
62 |
63 | for i, cat := range list.Categories {
64 | db.DB.Where("category_id = ?", cat.ID).Order("updated_at desc").Offset(5).Find(&list.Categories[i].CatChanges)
65 | db.DB.Delete(&list.Categories[i].CatChanges) // NO ACUMULA MAS DE 5 EVENTOS
66 |
67 | db.DB.Where("category_id = ?", cat.ID).Order("updated_at desc").Find(&list.Categories[i].CatChanges)
68 | }
69 |
70 | if true {
71 | copyAlert = true
72 | }
73 |
74 | render(c, gin.H{"title": "Grocery List", "list": list, "copyAlert": copyAlert}, "list.html")
75 |
76 | }
77 |
78 | }
79 |
80 | func Category(c *gin.Context) {
81 |
82 | url := c.Param("url")
83 |
84 | switch c.Request.Method {
85 |
86 | case "POST":
87 | {
88 | name := c.PostForm("name")
89 | newCat := models.Category{
90 | Name: name,
91 | Url: url,
92 | }
93 | db.DB.Create(&newCat)
94 |
95 | c.HTML(http.StatusOK, "category.html", newCat)
96 | }
97 |
98 | case "DELETE":
99 | {
100 | id := c.Param("id")
101 | var category models.Category
102 | db.DB.Delete(&category, id)
103 | }
104 |
105 | case "PUT":
106 | {
107 | id := c.Param("id")
108 | name := c.PostForm("name")
109 |
110 | var category models.Category
111 | db.DB.First(&category, id)
112 |
113 | oldname := category.Name
114 | category.Name = name
115 | db.DB.Save(&category)
116 |
117 | // ------------- ADD EVENT TO HISTORY --------------
118 |
119 | var catChange models.CatChange
120 | catChange.Title = "'" + oldname + "'" + " changed to '" + name + "'"
121 | catChange.Url = url
122 | catChange.TypeChange = 1
123 | catChange.CategoryID = category.ID
124 | db.DB.Create(&catChange)
125 |
126 | // --------------------------------------------------
127 |
128 | c.HTML(http.StatusOK, "history.html", catChange)
129 | }
130 |
131 | }
132 |
133 | }
134 |
135 | func Item(c *gin.Context) {
136 | url := c.Param("url")
137 |
138 | switch c.Request.Method {
139 | case "POST":
140 | {
141 | u64, _ := strconv.ParseUint(c.Param("idcat"), 10, 64)
142 | id := uint(u64)
143 | name := c.PostForm("name")
144 | item := models.Item{
145 | Name: name,
146 | CategoryID: id,
147 | Url: url,
148 | }
149 | db.DB.Create(&item)
150 |
151 | c.HTML(http.StatusOK, "item.html", item)
152 | }
153 | case "DELETE":
154 | {
155 | u64, _ := strconv.ParseUint(c.Param("iditem"), 10, 64)
156 | id := uint(u64)
157 | var item models.Item
158 | db.DB.Where("id = ?", id).First(&item)
159 | db.DB.Unscoped().Delete(&item)
160 |
161 | // ------------- ADD EVENT TO HISTORY --------------
162 | var catChange models.CatChange
163 | catChange.Title = item.Name
164 | catChange.Url = url
165 | catChange.TypeChange = 2
166 | catChange.CategoryID = item.CategoryID
167 | db.DB.Create(&catChange)
168 | // --------------------------------------------------
169 | c.HTML(http.StatusOK, "history.html", catChange)
170 | }
171 | case "PATCH":
172 | {
173 | u64, _ := strconv.ParseUint(c.Param("iditem"), 10, 64)
174 | id := uint(u64)
175 |
176 | var item models.Item
177 | db.DB.Where("id = ?", id).First(&item)
178 | db.DB.Unscoped().Delete(&item)
179 |
180 | // ------------- ADD EVENT TO HISTORY ---------------
181 | var catChange models.CatChange
182 | catChange.Title = item.Name
183 | catChange.Url = url
184 | catChange.TypeChange = 3
185 | catChange.CategoryID = item.CategoryID
186 | db.DB.Create(&catChange)
187 | // --------------------------------------------------
188 |
189 | c.HTML(http.StatusOK, "history.html", catChange)
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/helpers/helpers.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "app/db"
5 | "app/models"
6 | "crypto/rand"
7 | "math/big"
8 | )
9 |
10 | var chars = []string{
11 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
12 | "q", "w", "e", "r", "t", "y", "u", "i", "o", "p",
13 | "a", "s", "d", "f", "g", "h", "j", "k", "l",
14 | "z", "x", "c", "v", "b", "n", "m",
15 | "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P",
16 | "A", "S", "D", "F", "G", "H", "J", "K", "L",
17 | "Z", "X", "C", "V", "B", "N", "M",
18 | }
19 |
20 | func randint() int64 {
21 | nBig, err := rand.Int(rand.Reader, big.NewInt(62))
22 | if err != nil {
23 | panic(err)
24 | }
25 | return nBig.Int64()
26 | }
27 |
28 | func GenerateRandomString(length int) string {
29 | var s string
30 | for i := 0; i < length; i++ {
31 | s += chars[randint()]
32 | }
33 | return s
34 | }
35 |
36 | type Stats struct {
37 | Lists int64
38 | Categories int64
39 | Items int64
40 | ItemsCheked int64
41 | ItemsDestroyed int64
42 | }
43 |
44 | func CountStats() Stats {
45 | var stats Stats
46 | db.DB.Model(&models.List{}).Count(&stats.Lists)
47 | db.DB.Model(&models.Category{}).Count(&stats.Categories)
48 | db.DB.Model(&models.Item{}).Count(&stats.Items)
49 | return stats
50 | }
51 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "app/models"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 |
9 | "github.com/ulule/limiter/v3"
10 | mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
11 | "github.com/ulule/limiter/v3/drivers/store/memory"
12 | )
13 |
14 | var router *gin.Engine
15 |
16 | func main() {
17 |
18 | // IP RATE LIMITER
19 | rate, err := limiter.NewRateFromFormatted("240-M")
20 | if err != nil {
21 | panic(err)
22 | }
23 | store := memory.NewStore()
24 | instance_iprate := limiter.New(store, rate)
25 | middleware_iprate := mgin.NewMiddleware(instance_iprate)
26 | // END IP RATE LIMITER
27 |
28 | // DB MIGRATIONS
29 | models.Migrate()
30 | // END DB MIGRATIONS
31 |
32 | router = gin.Default()
33 |
34 | router.ForwardedByClientIP = true
35 | router.Use(middleware_iprate)
36 |
37 | //templ := template.Must(template.New("").ParseFS(embeddedFiles, "templates/*"))
38 | //router.SetHTMLTemplate(templ)
39 | router.Static("/assets", "./assets")
40 |
41 | router.LoadHTMLGlob("templates/*")
42 |
43 | InitializeRoutes()
44 |
45 | router.Run()
46 |
47 | }
48 |
49 | // Render one of HTML or JSON based on the 'Accept' header of the request
50 | // If the header doesn't specify this, HTML is rendered, provided that
51 | // the template name is present
52 | func render(c *gin.Context, data gin.H, templateName string) {
53 |
54 | switch c.Request.Header.Get("Accept") {
55 | case "application/json":
56 | // Respond with JSON
57 | c.JSON(http.StatusOK, data["payload"])
58 | default:
59 | // Respond with HTML
60 | c.HTML(http.StatusOK, templateName, data)
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/models/models.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "app/db"
4 |
5 | type List struct {
6 | ID uint `gorm:"primaryKey"`
7 | Url string `gorm:"not null; unique"`
8 |
9 | UpdatedAt int
10 | Categories []Category `gorm:"foreignkey:Url;references:Url"`
11 | }
12 |
13 | type Category struct {
14 | ID uint `gorm:"primaryKey"`
15 | Name string
16 | Url string
17 | UpdatedAt int
18 | Items []Item `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
19 | CatChanges []CatChange `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
20 | }
21 |
22 | type CatChange struct {
23 | ID uint `gorm:"primaryKey"`
24 | Title string
25 | Url string
26 | TypeChange int // 0(created) - 1(updated) - 2(deleted) - 3(done)
27 |
28 | CategoryID uint
29 | UpdatedAt int
30 | }
31 |
32 | type Item struct {
33 | ID int `gorm:"primaryKey"`
34 | Name string
35 | Url string
36 |
37 | CategoryID uint
38 | UpdatedAt int
39 | }
40 |
41 | func Migrate() {
42 | db.DB.AutoMigrate(&List{}, &Category{}, &Item{}, &CatChange{})
43 | }
44 |
--------------------------------------------------------------------------------
/routes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func InitializeRoutes() {
4 |
5 | router.GET("/", Home)
6 | router.POST("/", CreateList)
7 | router.GET("/:url", ShowList)
8 |
9 | // CATEGORY CRUD
10 | router.POST("/:url/category", Category)
11 | router.DELETE("/:url/category/:id", Category)
12 | router.PUT("/:url/category/:id", Category)
13 |
14 | // ITEM CRUD
15 | router.POST("/:url/item/:idcat", Item)
16 | router.DELETE("/:url/item/:iditem", Item)
17 | router.PUT("/:url/item/:iditem", Item) // CHANGE NAME
18 | router.PATCH("/:url/item/:iditem", Item) // CHANGE DONE
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/templates/category.html:
--------------------------------------------------------------------------------
1 | {{.Name}}
8 |
9 |
26 |
27 |
website 🛠️ by me
3 |