├── .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 |
2 | 3 |
4 | 5 |
6 | 7 |

{{.Name}}

8 | 9 |
10 |
11 | 14 | 15 | 18 | 19 |   22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 |
30 | 31 | 35 | 36 | 40 | 41 |
42 | 43 |
44 | 45 | 46 | 47 |
48 | 49 |
50 | {{ range .Items }} 51 | 52 | {{ template "item.html" . }} 53 | 54 | {{ end }} 55 | 56 |
57 | 58 | 59 | 60 |
61 | 62 | 65 | 66 |
67 | 70 | 71 | 72 |   73 |
74 | 75 |
76 | 77 | 78 | 79 |
80 | 81 | 82 |
83 | {{ range .CatChanges }} 84 | {{ template "history.html" .}} 85 | {{end}} 86 |
87 | 88 |
-------------------------------------------------------------------------------- /templates/design.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | List 📝 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |

🥦 Veggies

24 | 25 |
26 | 27 | 28 |
29 |
30 | 31 |
32 |

◾ Item 1

33 | 34 |
35 | 36 | 37 |
38 |
39 | 40 |
41 |

◾ Item 1

42 | 43 |
44 | 45 | 46 |
47 |
48 |
49 | 50 | 51 | 52 |
53 |
54 | 55 |

🛒 Groceries

56 | 57 |
58 | 59 | 60 |
61 |
62 | 63 |
64 |

◾ Item 1

65 | 66 |
67 | 68 | 69 |
70 |
71 | 72 |
73 |

◾ Item 1

74 | 75 |
76 | 77 | 78 |
79 |
80 |
81 | 82 |
83 | 84 | 85 | 86 |
87 | 88 | 89 | 90 |
91 |
92 | 93 |
94 | 95 |
96 |
Item 13 from 🥦 Veggies
97 |
30min ago ⌛
98 |
99 |
100 |
Item 13 from 🥦 Veggies
101 |
30min ago ⌛
102 |
103 | 104 |
105 |
Item 2 from 🛒 Groceries
106 |
4hs ago ⌛
107 |
108 | 109 |
110 | 111 |
112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /templates/footer.html: -------------------------------------------------------------------------------- 1 |
2 |

website 🛠️ by me

3 |
4 | 5 | 26 | 27 | 44 | 45 | -------------------------------------------------------------------------------- /templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ .title }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /templates/history.html: -------------------------------------------------------------------------------- 1 | {{ if eq .TypeChange 1 }} 2 | 3 |
4 |
🤡 {{ .Title }}
5 |
6 |
7 | 8 | {{ else if eq .TypeChange 2 }} 9 | 10 |
11 |
😥 {{ .Title }}
12 |
13 |
14 | 15 | {{ else if eq .TypeChange 0 }} 16 | 17 |
18 |
🛠️ {{ .Title }}
19 |
20 |
21 | 22 | {{ else if eq .TypeChange 3 }} 23 | 24 |
25 |
😎 {{ .Title }}
26 |
27 |
28 | 29 | {{ end }} -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" .}} 2 | 3 |
4 | 5 |
6 |

FREE SHAREABLE GROCERY LISTS

7 |

No Login Required

8 |
9 | 10 |
11 | 16 |
17 | 18 |
19 | 20 | 23 | 24 | 35 | 36 |
37 | 38 | 39 | 40 | 41 |
42 |

Last activity of the community

43 | 44 |
45 | 46 | 47 | 48 | 49 |
50 | 51 | {{ template "footer.html" .}} -------------------------------------------------------------------------------- /templates/item.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 8 | {{.Name}} 9 |
10 | 11 |
12 |   13 | 14 |   17 |
18 | 19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /templates/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ .title }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | {{ if eq .copyAlert true }} 24 |
26 |
⚠️ Warning! Save the URL if you want to acces this list later.
27 | 29 |
30 | {{ end }} 31 | 32 |
33 | 34 |
35 | 36 | 39 | 40 |
41 | 44 | 45 |   46 |
47 | 48 |
49 | 50 | 53 | 54 |
55 | 56 |
57 | 58 | {{ range .list.Categories}} 59 | 60 | {{ template "category.html" . }} 61 | 62 | {{end}} 63 | 64 |
65 | 66 | 67 | 68 |
69 | 70 | 93 | 94 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | {{ template "footer.html" .}} -------------------------------------------------------------------------------- /tmp/build-errors.log: -------------------------------------------------------------------------------- 1 | exit status 2exit status 1exit status 1exit status 1exit status 1exit status 2exit status 2exit status 1exit status 2exit status 2exit status 1exit status 1exit status 2exit status 2exit status 2exit status 2exit status 2exit status 1exit status 1exit status 1exit status 1exit status 1exit status 2exit status 2exit status 1exit status 1exit status 1exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 0xc000013aexit status 2exit status 1exit status 1exit status 1exit status 2exit status 2exit status 2exit status 1exit status 1exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 0xc000013a --------------------------------------------------------------------------------