Rework DB query stuff, use for projects
This commit is contained in:
parent
c2aeaedea9
commit
4fb161b3c6
|
@ -1,2 +1,3 @@
|
|||
src/config/config.go
|
||||
.vscode
|
||||
vendor/
|
||||
|
|
8
go.mod
8
go.mod
|
@ -5,7 +5,7 @@ go 1.16
|
|||
require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/go-stack/stack v1.8.0
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
|
@ -15,7 +15,9 @@ require (
|
|||
github.com/mitchellh/copystructure v1.1.1 // indirect
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/teacat/noire v1.1.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/teacat/noire v1.1.0
|
||||
github.com/wellington/go-libsass v0.9.2
|
||||
)
|
||||
|
||||
replace github.com/rs/zerolog v1.20.0 => github.com/bvisness/zerolog v1.20.1-0.20210321191248-05f63bf0e9e0
|
||||
|
|
27
go.sum
27
go.sum
|
@ -29,6 +29,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/bvisness/zerolog v1.20.1-0.20210321191248-05f63bf0e9e0 h1:2FrBN+MEMyGgts3Sm9M+6sMR6cMYEZ8+4fvotGFtRNE=
|
||||
github.com/bvisness/zerolog v1.20.1-0.20210321191248-05f63bf0e9e0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
|
@ -175,9 +177,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
|
@ -216,8 +220,9 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
|
|||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
|
@ -235,8 +240,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
||||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
|
@ -267,7 +270,6 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
|
@ -279,6 +281,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
|
|||
github.com/wellington/go-libsass v0.9.2 h1:6Ims04UDdBs6/CGSVK5JC8FNikR5ssrsMMKE/uaO5Q8=
|
||||
github.com/wellington/go-libsass v0.9.2/go.mod h1:mxgxgam0N0E+NAUMHLcu20Ccfc3mVpDkyrLDayqfiTs=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
@ -299,6 +302,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
|
@ -320,6 +324,7 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
|
|||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -334,8 +339,9 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -344,6 +350,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -364,6 +371,8 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
@ -387,17 +396,20 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
|
@ -421,6 +433,7 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
|
|||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package color
|
||||
package ansicolor
|
||||
|
||||
import "runtime"
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package config
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
type Environment string
|
||||
|
||||
|
@ -23,6 +27,7 @@ type PostgresConfig struct {
|
|||
Hostname string
|
||||
Port int
|
||||
DbName string
|
||||
LogLevel pgx.LogLevel
|
||||
}
|
||||
|
||||
func (info PostgresConfig) DSN() string {
|
||||
|
|
98
src/db/db.go
98
src/db/db.go
|
@ -2,11 +2,17 @@ package db
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/config"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/jackc/pgx/v4/log/zerologadapter"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func NewConn() *pgx.Conn {
|
||||
|
@ -19,15 +25,99 @@ func NewConn() *pgx.Conn {
|
|||
}
|
||||
|
||||
func NewConnPool(minConns, maxConns int32) *pgxpool.Pool {
|
||||
config, err := pgxpool.ParseConfig(config.Config.Postgres.DSN())
|
||||
cfg, err := pgxpool.ParseConfig(config.Config.Postgres.DSN())
|
||||
|
||||
config.MinConns = minConns
|
||||
config.MaxConns = maxConns
|
||||
cfg.MinConns = minConns
|
||||
cfg.MaxConns = maxConns
|
||||
cfg.ConnConfig.Logger = zerologadapter.NewLogger(log.Logger)
|
||||
cfg.ConnConfig.LogLevel = config.Config.Postgres.LogLevel
|
||||
|
||||
conn, err := pgxpool.ConnectConfig(context.Background(), config)
|
||||
conn, err := pgxpool.ConnectConfig(context.Background(), cfg)
|
||||
if err != nil {
|
||||
panic(oops.New(err, "failed to create database connection pool"))
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
type StructQueryIterator struct {
|
||||
fieldIndices []int
|
||||
rows pgx.Rows
|
||||
}
|
||||
|
||||
func (it *StructQueryIterator) Next(dest interface{}) bool {
|
||||
hasNext := it.rows.Next()
|
||||
if !hasNext {
|
||||
return false
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(dest)
|
||||
if v.Kind() != reflect.Ptr {
|
||||
panic(oops.New(nil, "Next requires a pointer type; got %v", v.Kind()))
|
||||
}
|
||||
|
||||
vals, err := it.rows.Values()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i, val := range vals {
|
||||
field := v.Elem().Field(it.fieldIndices[i])
|
||||
switch field.Kind() {
|
||||
case reflect.Int:
|
||||
field.SetInt(reflect.ValueOf(val).Int())
|
||||
default:
|
||||
field.Set(reflect.ValueOf(val))
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (it *StructQueryIterator) Close() {
|
||||
it.rows.Close()
|
||||
}
|
||||
|
||||
func QueryToStructs(ctx context.Context, conn *pgxpool.Pool, destType interface{}, query string, args ...interface{}) (StructQueryIterator, error) {
|
||||
var fieldIndices []int
|
||||
var columnNames []string
|
||||
|
||||
t := reflect.TypeOf(models.Project{})
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if columnName := f.Tag.Get("db"); columnName != "" {
|
||||
fieldIndices = append(fieldIndices, i)
|
||||
columnNames = append(columnNames, columnName)
|
||||
}
|
||||
}
|
||||
|
||||
columnNamesString := strings.Join(columnNames, ", ")
|
||||
query = strings.Replace(query, "$columns", columnNamesString, -1)
|
||||
|
||||
rows, err := conn.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return StructQueryIterator{}, err
|
||||
}
|
||||
|
||||
return StructQueryIterator{
|
||||
fieldIndices: fieldIndices,
|
||||
rows: rows,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var ErrNoMatchingRows = errors.New("no matching rows")
|
||||
|
||||
func QueryOneToStruct(ctx context.Context, conn *pgxpool.Pool, dest interface{}, query string, args ...interface{}) error {
|
||||
rows, err := QueryToStructs(ctx, conn, dest, query, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
hasRow := rows.Next(dest)
|
||||
if !hasRow {
|
||||
return ErrNoMatchingRows
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@ func init() {
|
|||
log.Logger = log.Output(NewPrettyZerologWriter())
|
||||
}
|
||||
|
||||
func GlobalLogger() *zerolog.Logger {
|
||||
return &log.Logger
|
||||
}
|
||||
|
||||
func Trace() *zerolog.Event {
|
||||
return log.Trace().Timestamp().Stack()
|
||||
}
|
||||
|
@ -47,7 +51,7 @@ func Fatal() *zerolog.Event {
|
|||
}
|
||||
|
||||
func With() zerolog.Context {
|
||||
return log.With()
|
||||
return log.With().Stack()
|
||||
}
|
||||
|
||||
type PrettyZerologWriter struct {
|
||||
|
@ -169,12 +173,20 @@ func (w *PrettyZerologWriter) Write(p []byte) (int, error) {
|
|||
return os.Stderr.Write([]byte(b.String()))
|
||||
}
|
||||
|
||||
func LogPanics() {
|
||||
func LogPanics(logger *zerolog.Logger) {
|
||||
if r := recover(); r != nil {
|
||||
if err, ok := r.(error); ok {
|
||||
Error().Err(err).Msg("recovered from panic")
|
||||
LogPanicValue(logger, r, "recovered from panic")
|
||||
}
|
||||
}
|
||||
|
||||
func LogPanicValue(logger *zerolog.Logger, val interface{}, msg string) {
|
||||
if logger == nil {
|
||||
logger = GlobalLogger()
|
||||
}
|
||||
|
||||
if err, ok := val.(error); ok {
|
||||
logger.Error().Err(err).Msg(msg)
|
||||
} else {
|
||||
Error().Interface("recovered", r).Msg("recovered from panic")
|
||||
}
|
||||
logger.Error().Interface("recovered", val).Msg(msg)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package models
|
||||
|
||||
const HMNProjectID = 1
|
||||
|
||||
type Project struct {
|
||||
ID int `db:"id"`
|
||||
|
||||
Slug string `db:"slug"`
|
||||
Name string `db:"name"`
|
||||
Blurb string `db:"blurb"`
|
||||
Description string `db:"description"`
|
||||
|
||||
Color1 string `db:"color_1"`
|
||||
Color2 string `db:"color_2"`
|
||||
}
|
||||
|
||||
func (p *Project) IsHMN() bool {
|
||||
return p.ID == HMNProjectID
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/url"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/logging"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/templates"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/rs/zerolog"
|
||||
|
@ -15,6 +16,7 @@ import (
|
|||
|
||||
type HMNRouter struct {
|
||||
HttpRouter *httprouter.Router
|
||||
Wrappers []HMNHandlerWrapper
|
||||
}
|
||||
|
||||
func (r *HMNRouter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -22,6 +24,9 @@ func (r *HMNRouter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
func (r *HMNRouter) Handle(method, route string, handler HMNHandler) {
|
||||
for i := len(r.Wrappers) - 1; i >= 0; i-- {
|
||||
handler = r.Wrappers[i](handler)
|
||||
}
|
||||
r.HttpRouter.Handle(method, route, handleHmnHandler(route, handler))
|
||||
}
|
||||
|
||||
|
@ -29,38 +34,52 @@ func (r *HMNRouter) GET(route string, handler HMNHandler) {
|
|||
r.Handle(http.MethodGet, route, handler)
|
||||
}
|
||||
|
||||
// TODO: POST, etc.
|
||||
|
||||
func (r *HMNRouter) ServeFiles(path string, root http.FileSystem) {
|
||||
r.HttpRouter.ServeFiles(path, root)
|
||||
}
|
||||
|
||||
func (r *HMNRouter) WithWrappers(wrappers ...HMNHandlerWrapper) *HMNRouter {
|
||||
result := *r
|
||||
result.Wrappers = append(result.Wrappers, wrappers...)
|
||||
return &result
|
||||
}
|
||||
|
||||
type HMNHandler func(c *RequestContext, p httprouter.Params)
|
||||
type HMNHandlerWrapper func(h HMNHandler) HMNHandler
|
||||
|
||||
type RequestContext struct {
|
||||
StatusCode int
|
||||
Body io.ReadWriter
|
||||
Logger zerolog.Context
|
||||
Body *bytes.Buffer
|
||||
Logger *zerolog.Logger
|
||||
Req *http.Request
|
||||
Errors []error
|
||||
|
||||
rw http.ResponseWriter
|
||||
req *http.Request
|
||||
|
||||
currentProject *models.Project
|
||||
}
|
||||
|
||||
func newRequestContext(rw http.ResponseWriter, req *http.Request, route string) *RequestContext {
|
||||
logger := logging.With().Str("route", route).Logger()
|
||||
|
||||
return &RequestContext{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: new(bytes.Buffer),
|
||||
Logger: logging.With().Str("route", route),
|
||||
Logger: &logger,
|
||||
Req: req,
|
||||
|
||||
rw: rw,
|
||||
req: req,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RequestContext) Context() context.Context {
|
||||
return c.req.Context()
|
||||
return c.Req.Context()
|
||||
}
|
||||
|
||||
func (c *RequestContext) URL() *url.URL {
|
||||
return c.req.URL
|
||||
return c.Req.URL
|
||||
}
|
||||
|
||||
func (c *RequestContext) Headers() http.Header {
|
||||
|
@ -71,9 +90,29 @@ func (c *RequestContext) WriteTemplate(name string, data interface{}) error {
|
|||
return templates.Templates[name].Execute(c.Body, data)
|
||||
}
|
||||
|
||||
func (c *RequestContext) AddErrors(errs ...error) {
|
||||
c.Errors = append(c.Errors, errs...)
|
||||
}
|
||||
|
||||
func (c *RequestContext) AbortWithErrors(status int, errs ...error) {
|
||||
c.StatusCode = status
|
||||
c.AddErrors(errs...)
|
||||
}
|
||||
|
||||
func handleHmnHandler(route string, h HMNHandler) httprouter.Handle {
|
||||
return func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
c := newRequestContext(rw, r, route)
|
||||
defer func() {
|
||||
/*
|
||||
This panic recovery is the last resort. If you want to render
|
||||
an error page or something, make it a request wrapper.
|
||||
*/
|
||||
if recovered := recover(); recovered != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
logging.LogPanicValue(c.Logger, recovered, "request panicked and was not handled")
|
||||
}
|
||||
}()
|
||||
|
||||
h(c, p)
|
||||
|
||||
rw.WriteHeader(c.StatusCode)
|
||||
|
|
|
@ -2,10 +2,14 @@ package website
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/logging"
|
||||
"git.handmade.network/hmn/hmn/src/db"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
"git.handmade.network/hmn/hmn/src/templates"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
|
@ -23,21 +27,24 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
|
|||
conn: conn,
|
||||
}
|
||||
|
||||
routes.GET("/", routes.Index)
|
||||
routes.GET("/project/:id", routes.Project)
|
||||
routes.GET("/assets/project.css", routes.ProjectCSS)
|
||||
mainRoutes := routes.WithWrappers(routes.CommonWebsiteDataWrapper)
|
||||
mainRoutes.GET("/", routes.Index)
|
||||
mainRoutes.GET("/project/:id", routes.Project)
|
||||
mainRoutes.GET("/assets/project.css", routes.ProjectCSS)
|
||||
|
||||
routes.ServeFiles("/public/*filepath", http.Dir("public"))
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
func (s *websiteRoutes) Index(c *RequestContext, p httprouter.Params) {
|
||||
err := c.WriteTemplate("index.html", templates.BaseData{
|
||||
func (s *websiteRoutes) getBaseData(c *RequestContext) templates.BaseData {
|
||||
return templates.BaseData{
|
||||
Project: templates.Project{
|
||||
Name: "Handmade Network",
|
||||
Color: "cd4e31",
|
||||
Name: c.currentProject.Name,
|
||||
Subdomain: c.currentProject.Slug,
|
||||
Color: c.currentProject.Color1,
|
||||
|
||||
IsHMN: true,
|
||||
IsHMN: c.currentProject.IsHMN(),
|
||||
|
||||
HasBlog: true,
|
||||
HasForum: true,
|
||||
|
@ -45,7 +52,33 @@ func (s *websiteRoutes) Index(c *RequestContext, p httprouter.Params) {
|
|||
HasLibrary: true,
|
||||
},
|
||||
Theme: "dark",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FetchProjectBySlug(ctx context.Context, conn *pgxpool.Pool, slug string) (*models.Project, error) {
|
||||
var subdomainProject models.Project
|
||||
err := db.QueryOneToStruct(ctx, conn, &subdomainProject, "SELECT $columns FROM handmade_project WHERE slug = $1", slug)
|
||||
if err == nil {
|
||||
return &subdomainProject, nil
|
||||
} else if !errors.Is(err, db.ErrNoMatchingRows) {
|
||||
return nil, oops.New(err, "failed to get projects by slug")
|
||||
}
|
||||
|
||||
var defaultProject models.Project
|
||||
err = db.QueryOneToStruct(ctx, conn, &defaultProject, "SELECT $columns FROM handmade_project WHERE id = $1", models.HMNProjectID)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNoMatchingRows) {
|
||||
return nil, oops.New(nil, "default project didn't exist in the database")
|
||||
} else {
|
||||
return nil, oops.New(err, "failed to get default project")
|
||||
}
|
||||
}
|
||||
|
||||
return &defaultProject, nil
|
||||
}
|
||||
|
||||
func (s *websiteRoutes) Index(c *RequestContext, p httprouter.Params) {
|
||||
err := c.WriteTemplate("index.html", s.getBaseData(c))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -82,7 +115,27 @@ func (s *websiteRoutes) ProjectCSS(c *RequestContext, p httprouter.Params) {
|
|||
c.Headers().Add("Content-Type", "text/css")
|
||||
err := c.WriteTemplate("project.css", templateData)
|
||||
if err != nil {
|
||||
logging.Error().Err(err).Msg("failed to generate project CSS")
|
||||
c.Logger.Error().Err(err).Msg("failed to generate project CSS")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *websiteRoutes) CommonWebsiteDataWrapper(h HMNHandler) HMNHandler {
|
||||
return func(c *RequestContext, p httprouter.Params) {
|
||||
slug := ""
|
||||
hostParts := strings.SplitN(c.Req.Host, ".", 3)
|
||||
if len(hostParts) >= 3 {
|
||||
slug = hostParts[0]
|
||||
}
|
||||
|
||||
dbProject, err := FetchProjectBySlug(c.Context(), s.conn, slug)
|
||||
if err != nil {
|
||||
c.AbortWithErrors(http.StatusInternalServerError, oops.New(err, "failed to fetch current project"))
|
||||
return
|
||||
}
|
||||
|
||||
c.currentProject = dbProject
|
||||
|
||||
h(c, p)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"git.handmade.network/hmn/hmn/src/db"
|
||||
"git.handmade.network/hmn/hmn/src/logging"
|
||||
"git.handmade.network/hmn/hmn/src/templates"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -21,7 +20,7 @@ var WebsiteCommand = &cobra.Command{
|
|||
Run: func(cmd *cobra.Command, args []string) {
|
||||
templates.Init()
|
||||
|
||||
defer logging.LogPanics()
|
||||
defer logging.LogPanics(nil)
|
||||
|
||||
logging.Info().Msg("Hello, HMN!")
|
||||
|
||||
|
@ -37,7 +36,8 @@ var WebsiteCommand = &cobra.Command{
|
|||
go func() {
|
||||
<-signals
|
||||
logging.Info().Msg("Shutting down the website")
|
||||
timeout, _ := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
timeout, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
server.Shutdown(timeout)
|
||||
|
||||
<-signals
|
||||
|
@ -52,19 +52,3 @@ var WebsiteCommand = &cobra.Command{
|
|||
}
|
||||
},
|
||||
}
|
||||
|
||||
func withRequestLogger(h httprouter.Handle) httprouter.Handle {
|
||||
return func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
defer logging.LogPanics()
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
end := time.Now()
|
||||
logging.Info().Dur("duration", end.Sub(start)).Msg("Completed request")
|
||||
}()
|
||||
|
||||
h(rw, r, p)
|
||||
}
|
||||
}
|
||||
|
||||
// func handleRequestResults
|
||||
|
|
Loading…
Reference in New Issue