updating module to live at sourcehut

Also moving the 'registry' library into this repo, rather
than maintaining them separately. It will still be decoupled,
just live in this repository.
This commit is contained in:
Ben Morrison 2020-06-20 02:27:31 -04:00
parent 0a69c582ec
commit 538e305925
Signed by untrusted user: gbmor
GPG Key ID: 8F192E4720BB0DAC
38 changed files with 2493 additions and 89 deletions

10
.build.yml Normal file
View File

@ -0,0 +1,10 @@
image: alpine/edge
packages:
- go
sources:
- https://git.sr.ht/~gbmor/getwtxt
tasks:
- build: |
cd getwtxt
go test -v
go test -v --bench . --benchmem

View File

@ -2,7 +2,7 @@ PREFIX?=/usr/local
_INSTDIR=$(PREFIX)
BINDIR?=$(_INSTDIR)/getwtxt
VERSION?=$(shell git tag | grep ^v | sort -V | tail -n 1)
GOFLAGS?=-ldflags '-X github.com/getwtxt/getwtxt/svc.Vers=${VERSION}'
GOFLAGS?=-ldflags '-X git.sr.ht/~gbmor/getwtxt/svc.Vers=${VERSION}'
getwtxt: getwtxt.go go.mod go.sum
@echo

View File

@ -90,7 +90,7 @@ foo_barrington https://example3.com/twtxt.txt 2019-02-26T11:06:44.000Z
foo https://example.com/twtxt.txt 2019-02-26T11:06:44.000Z @&lt;foo_barrington https://example3.com/twtxt.txt&gt; Hey!! Are you still working on that project?</code></pre>
</div>
<div id="foot">
powered by <a href="https://github.com/getwtxt/getwtxt">getwtxt</a>
powered by <a href="https://sr.ht/~gbmor/getwtxt">getwtxt</a>
</div>
</div>
</body>

View File

@ -19,7 +19,7 @@ along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
package main
import "github.com/getwtxt/getwtxt/svc"
import "git.sr.ht/~gbmor/getwtxt/svc"
func main() {
svc.Start()

7
go.mod
View File

@ -1,15 +1,14 @@
module github.com/getwtxt/getwtxt
module git.sr.ht/~gbmor/getwtxt
go 1.11
require (
github.com/fsnotify/fsnotify v1.4.9
github.com/getwtxt/registry v0.4.2
github.com/gorilla/handlers v1.4.2
github.com/gorilla/mux v1.7.4
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.6.2
github.com/spf13/viper v1.7.0
github.com/syndtr/goleveldb v1.0.0
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1
)

185
go.sum
View File

@ -1,30 +1,47 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getwtxt/registry v0.4.2 h1:6qp7KkBi60CZaJpFC21ez29QwiIcCnzazAG3w9OdXaU=
github.com/getwtxt/registry v0.4.2/go.mod h1:BGSIALOFqIRj+ACLB8etWGUOgFAKN8oFDpCsw6YOdYQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@ -34,28 +51,60 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@ -70,11 +119,22 @@ 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/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -82,11 +142,14 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
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/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=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@ -97,6 +160,9 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@ -113,62 +179,139 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
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/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/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=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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-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-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
@ -181,3 +324,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

48
registry/README.md Normal file
View File

@ -0,0 +1,48 @@
# `getwtxt/registry`
### twtxt Registry Library for Go
`getwtxt/registry` helps you implement twtxt registries in Go.
It uses no third-party dependencies whatsoever, only the standard library,
and has no global state.
Specifying your own `http.Client` for requests is encouraged, with a sensible
default available by passing `nil` to the constructor.
## Using the Library
Just add it to your imports list in the file(s) where it's needed.
```go
import (
"git.sr.ht/~gbmor/getwtxt/registry"
)
```
## Documentation
The code is commented, so feel free to browse the files themselves.
Alternatively, the generated documentation can be found at:
[pkg.go.dev/git.sr.ht/~gbmor/getwtxt/registry](https://pkg.go.dev/git.sr.ht/~gbmor/getwtxt/registry)
## Contributions
All contributions are very welcome! Please specify that you are referring to `getwtxt/registry`
when using the following:
* Mailing list (patches, discussion)
* [https://lists.sr.ht/~gbmor/getwtxt](https://lists.sr.ht/~gbmor/getwtxt)
* Ticket tracker
* [https://todo.sr.ht/~gbmor/getwtxt](https://todo.sr.ht/~gbmor/getwtxt)
## Notes
* getwtxt - parent project:
* [sr.ht/~gbmor/getwtxt](https://sr.ht/~gbmor/getwtxt)
* twtxt repository:
* [github.com/buckket/twtxt](https://github.com/buckket/twtxt)
* twtxt documentation:
* [twtxt.readthedocs.io/en/latest/](https://twtxt.readthedocs.io/en/latest/)
* twtxt registry documentation:
* [twtxt.readthedocs.io/en/latest/user/registry.html](https://twtxt.readthedocs.io/en/latest/user/registry.html)

277
registry/fetch.go Normal file
View File

@ -0,0 +1,277 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry // import "git.sr.ht/~gbmor/getwtxt/registry"
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
"sync"
"time"
)
// GetTwtxt fetches the raw twtxt file data from the user's
// provided URL, after validating the URL. If the returned
// boolean value is false, the fetched URL is a single user's
// twtxt file. If true, the fetched URL is the output of
// another registry's /api/plain/tweets. The output of
// GetTwtxt should be passed to either ParseUserTwtxt or
// ParseRegistryTwtxt, respectively.
// Generally, the *http.Client inside a given Registry instance should
// be passed to GetTwtxt. If the *http.Client passed is nil,
// Registry will use a preconstructed client with a
// timeout of 10s and all other values set to default.
func GetTwtxt(urlKey string, client *http.Client) ([]byte, bool, error) {
if !strings.HasPrefix(urlKey, "http://") && !strings.HasPrefix(urlKey, "https://") {
return nil, false, fmt.Errorf("invalid URL: %v", urlKey)
}
res, err := doReq(urlKey, "GET", "", client)
if err != nil {
return nil, false, err
}
defer res.Body.Close()
var textPlain bool
for _, v := range res.Header["Content-Type"] {
if strings.Contains(v, "text/plain") {
textPlain = true
break
}
}
if !textPlain {
return nil, false, fmt.Errorf("received non-text/plain response body from %v", urlKey)
}
if res.StatusCode != http.StatusOK {
return nil, false, fmt.Errorf("didn't get 200 from remote server, received %v: %v", res.StatusCode, urlKey)
}
twtxt, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, false, fmt.Errorf("error reading response body from %v: %v", urlKey, err)
}
// Signal that we're adding another twtxt registry as a "user"
if strings.HasSuffix(urlKey, "/api/plain/tweets") || strings.HasSuffix(urlKey, "/api/plain/tweets/all") {
return twtxt, true, nil
}
return twtxt, false, nil
}
// DiffTwtxt issues a HEAD request on the user's
// remote twtxt data. It then checks the Content-Length
// header. If it's different from the stored result of
// the previous Content-Length header, update the stored
// value for a given user and return true.
// Otherwise, return false. In some error conditions,
// such as the user not being in the registry, it returns true.
// In other error conditions considered "unrecoverable,"
// such as the supplied URL being invalid, it returns false.
func (registry *Registry) DiffTwtxt(urlKey string) (bool, error) {
if !strings.HasPrefix(urlKey, "http://") && !strings.HasPrefix(urlKey, "https://") {
return false, fmt.Errorf("invalid URL: %v", urlKey)
}
registry.Mu.Lock()
user, ok := registry.Users[urlKey]
if !ok {
return true, fmt.Errorf("user not in registry")
}
user.Mu.Lock()
defer func() {
registry.Users[urlKey] = user
user.Mu.Unlock()
registry.Mu.Unlock()
}()
res, err := doReq(urlKey, "HEAD", user.LastModified, registry.HTTPClient)
if err != nil {
return false, err
}
switch res.StatusCode {
case http.StatusOK:
for _, e := range res.Header["Last-Modified"] {
if e != "" {
user.LastModified = e
break
}
}
return true, nil
case http.StatusNotModified:
return false, nil
}
return false, nil
}
// internal function. boilerplate for http requests.
func doReq(urlKey, method, modTime string, client *http.Client) (*http.Response, error) {
if client == nil {
client = &http.Client{
Transport: nil,
CheckRedirect: nil,
Jar: nil,
Timeout: 10 * time.Second,
}
}
var b []byte
buf := bytes.NewBuffer(b)
req, err := http.NewRequest(method, urlKey, buf)
if err != nil {
return nil, err
}
if modTime != "" {
req.Header.Set("If-Modified-Since", modTime)
}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("couldn't %v %v: %v", method, urlKey, err)
}
return res, nil
}
// ParseUserTwtxt takes a fetched twtxt file in the form of
// a slice of bytes, parses it, and returns it as a
// TimeMap. The output may then be passed to Index.AddUser()
func ParseUserTwtxt(twtxt []byte, nickname, urlKey string) (TimeMap, error) {
var erz []byte
if len(twtxt) == 0 {
return nil, fmt.Errorf("no data to parse in twtxt file")
}
reader := bytes.NewReader(twtxt)
scanner := bufio.NewScanner(reader)
timemap := NewTimeMap()
for scanner.Scan() {
nopadding := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(nopadding, "#") || nopadding == "" {
continue
}
columns := strings.Split(nopadding, "\t")
if len(columns) != 2 {
return nil, fmt.Errorf("improperly formatted data in twtxt file")
}
normalizedDatestamp := fixTimestamp(columns[0])
thetime, err := time.Parse(time.RFC3339, normalizedDatestamp)
if err != nil {
erz = append(erz, []byte(fmt.Sprintf("unable to retrieve date: %v\n", err))...)
}
timemap[thetime] = nickname + "\t" + urlKey + "\t" + nopadding
}
if len(erz) == 0 {
return timemap, nil
}
return timemap, fmt.Errorf("%v", string(erz))
}
func fixTimestamp(ts string) string {
normalizeTimestamp := regexp.MustCompile(`[\+][\d][\d][:][\d][\d]`)
return strings.TrimSpace(normalizeTimestamp.ReplaceAllString(ts, "Z"))
}
// ParseRegistryTwtxt takes output from a remote registry and outputs
// the accessible user data via a slice of Users.
func ParseRegistryTwtxt(twtxt []byte) ([]*User, error) {
var erz []byte
if len(twtxt) == 0 {
return nil, fmt.Errorf("received no data")
}
reader := bytes.NewReader(twtxt)
scanner := bufio.NewScanner(reader)
userdata := []*User{}
for scanner.Scan() {
nopadding := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(nopadding, "#") || nopadding == "" {
continue
}
columns := strings.Split(nopadding, "\t")
if len(columns) != 4 {
return nil, fmt.Errorf("improperly formatted data")
}
thetime, err := time.Parse(time.RFC3339, columns[2])
if err != nil {
erz = append(erz, []byte(fmt.Sprintf("%v\n", err))...)
continue
}
parsednickname := columns[0]
dataIndex := 0
parsedurl := columns[1]
inIndex := false
for i, e := range userdata {
if e.Nick == parsednickname || e.URL == parsedurl {
dataIndex = i
inIndex = true
break
}
}
if inIndex {
tmp := userdata[dataIndex]
tmp.Status[thetime] = nopadding
userdata[dataIndex] = tmp
} else {
timeNowRFC := time.Now().Format(time.RFC3339)
if err != nil {
erz = append(erz, []byte(fmt.Sprintf("%v\n", err))...)
}
tmp := &User{
Mu: sync.RWMutex{},
Nick: parsednickname,
URL: parsedurl,
Date: timeNowRFC,
Status: TimeMap{
thetime: nopadding,
},
}
userdata = append(userdata, tmp)
}
}
return userdata, fmt.Errorf("%v", erz)
}

286
registry/fetch_test.go Normal file
View File

@ -0,0 +1,286 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry
import (
"bufio"
"fmt"
"net/http"
"os"
"strings"
"testing"
"time"
)
func constructTwtxt() []byte {
registry := initTestEnv()
var resp []byte
// iterates through each mock user's mock statuses
for _, v := range registry.Users {
for _, e := range v.Status {
split := strings.Split(e, "\t")
status := []byte(split[2] + "\t" + split[3] + "\n")
resp = append(resp, status...)
}
}
return resp
}
// this is just dumping all the mock statuses.
// it'll be served under fake paths as
// "remote" twtxt.txt files
func twtxtHandler(w http.ResponseWriter, _ *http.Request) {
// prepare the response
resp := constructTwtxt()
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
n, err := w.Write(resp)
if err != nil || n == 0 {
fmt.Printf("Got error or wrote zero bytes: %v bytes, %v\n", n, err)
}
}
var getTwtxtCases = []struct {
name string
url string
wantErr bool
localOnly bool
}{
{
name: "Constructed Local twtxt.txt",
url: "http://localhost:8080/twtxt.txt",
wantErr: false,
localOnly: true,
},
{
name: "Inaccessible Site With twtxt.txt",
url: "https://example33333333333.com/twtxt.txt",
wantErr: true,
localOnly: false,
},
{
name: "Inaccessible Site Without twtxt.txt",
url: "https://example333333333333.com",
wantErr: true,
localOnly: false,
},
{
name: "Local File Inclusion 1",
url: "file://init_test.go",
wantErr: true,
localOnly: false,
},
{
name: "Local File Inclusion 2",
url: "/etc/passwd",
wantErr: true,
localOnly: false,
},
{
name: "Remote File Inclusion",
url: "https://example.com/file.cgi",
wantErr: true,
localOnly: false,
},
{
name: "Remote Registry",
url: "https://twtxt.tilde.institute/api/plain/tweets/",
wantErr: false,
localOnly: false,
},
{
name: "Garbage Data",
url: "this will be replaced with garbage data",
wantErr: true,
localOnly: true,
},
}
// Test the function that yoinks the /twtxt.txt file
// for a given user.
func Test_GetTwtxt(t *testing.T) {
var buf = make([]byte, 256)
// read random data into case 4
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
getTwtxtCases[7].url = string(buf)
if !getTwtxtCases[0].localOnly {
http.Handle("/twtxt.txt", http.HandlerFunc(twtxtHandler))
go fmt.Println(http.ListenAndServe(":8080", nil))
}
for _, tt := range getTwtxtCases {
t.Run(tt.name, func(t *testing.T) {
if tt.localOnly {
t.Skipf("Local-only test. Skipping ... \n")
}
out, _, err := GetTwtxt(tt.url, nil)
if tt.wantErr && err == nil {
t.Errorf("Expected error: %v\n", tt.url)
}
if !tt.wantErr && err != nil {
t.Errorf("Unexpected error: %v %v\n", tt.url, err)
}
if !tt.wantErr && out == nil {
t.Errorf("Incorrect data received: %v\n", out)
}
})
}
}
// running the benchmarks separately for each case
// as they have different properties (allocs, time)
func Benchmark_GetTwtxt(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _, err := GetTwtxt("https://gbmor.dev/twtxt.txt", nil)
if err != nil {
continue
}
}
}
var parseTwtxtCases = []struct {
name string
data []byte
wantErr bool
localOnly bool
}{
{
name: "Constructed twtxt file",
data: constructTwtxt(),
wantErr: false,
localOnly: false,
},
{
name: "Incorrectly formatted date",
data: []byte("2019 April 23rd\tI love twtxt!!!11"),
wantErr: true,
localOnly: false,
},
{
name: "No data",
data: []byte{},
wantErr: true,
localOnly: false,
},
{
name: "Variant rfc3339 datestamp",
data: []byte("2020-02-04T21:28:21.868659+00:00\tWill this work?"),
wantErr: false,
localOnly: false,
},
{
name: "Random/garbage data",
wantErr: true,
localOnly: true,
},
}
// See if we can break ParseTwtxt or get it
// to throw an unexpected error
func Test_ParseUserTwtxt(t *testing.T) {
var buf = make([]byte, 256)
// read random data into case 4
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
parseTwtxtCases[4].data = buf
for _, tt := range parseTwtxtCases {
if tt.localOnly {
t.Skipf("Local-only test: Skipping ... \n")
}
t.Run(tt.name, func(t *testing.T) {
timemap, errs := ParseUserTwtxt(tt.data, "testuser", "testurl")
if errs == nil && tt.wantErr {
t.Errorf("Expected error(s), received none.\n")
}
if !tt.wantErr {
if errs != nil {
t.Errorf("Unexpected error: %v\n", errs)
}
for k, v := range timemap {
if k == (time.Time{}) || v == "" {
t.Errorf("Empty status or empty timestamp: %v, %v\n", k, v)
}
}
}
})
}
}
func Benchmark_ParseUserTwtxt(b *testing.B) {
var buf = make([]byte, 256)
// read random data into case 4
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
b.Errorf("Couldn't set up benchmark: %v\n", err)
}
parseTwtxtCases[3].data = buf
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range parseTwtxtCases {
_, _ = ParseUserTwtxt(tt.data, "testuser", "testurl")
}
}
}
var timestampCases = []struct {
name string
orig string
expected string
}{
{
name: "Timezone appended",
orig: "2020-01-13T16:08:25.544735+00:00",
expected: "2020-01-13T16:08:25.544735Z",
},
{
name: "It's fine already",
orig: "2020-01-14T00:19:45.092344Z",
expected: "2020-01-14T00:19:45.092344Z",
},
}
func Test_fixTimestamp(t *testing.T) {
for _, tt := range timestampCases {
t.Run(tt.name, func(t *testing.T) {
tsout := fixTimestamp(tt.orig)
if tsout != tt.expected {
t.Errorf("Failed :: %s :: got %s expected %s", tt.name, tsout, tt.expected)
}
})
}
}

93
registry/init_test.go Normal file
View File

@ -0,0 +1,93 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry //import "git.sr.ht/~gbmor/getwtxt/registry"
import (
"fmt"
"log"
"os"
"time"
)
func quickErr(err error) {
if err != nil {
fmt.Printf("%v\n", err)
}
}
// Sets up mock users and statuses
func initTestEnv() *Registry {
hush, err := os.Open("/dev/null")
quickErr(err)
log.SetOutput(hush)
// this is a bit tedious, but set up fake dates
// for the mock users' join and status timestamps
timeMonthPrev := time.Now().AddDate(0, -1, 0)
timeMonthPrevRFC := timeMonthPrev.Format(time.RFC3339)
timeTwoMonthsPrev := time.Now().AddDate(0, -2, 0)
timeTwoMonthsPrevRFC := timeTwoMonthsPrev.Format(time.RFC3339)
timeThreeMonthsPrev := time.Now().AddDate(0, -3, 0)
timeThreeMonthsPrevRFC := timeThreeMonthsPrev.Format(time.RFC3339)
timeFourMonthsPrev := time.Now().AddDate(0, -4, 0)
timeFourMonthsPrevRFC := timeFourMonthsPrev.Format(time.RFC3339)
var mockusers = []struct {
url string
nick string
date string
apidate []byte
status TimeMap
}{
{
url: "https://example3.com/twtxt.txt",
nick: "foo_barrington",
date: timeTwoMonthsPrevRFC,
status: TimeMap{
timeTwoMonthsPrev: "foo_barrington\thttps://example3.com/twtxt.txt\t" + timeTwoMonthsPrevRFC + "\tJust got started with #twtxt!",
timeMonthPrev: "foo_barrington\thttps://example3.com/twtxt.txt\t" + timeMonthPrevRFC + "\tHey <@foo https://example.com/twtxt.txt>, I love programming. Just FYI.",
},
},
{
url: "https://example.com/twtxt.txt",
nick: "foo",
date: timeFourMonthsPrevRFC,
status: TimeMap{
timeFourMonthsPrev: "foo\thttps://example.com/twtxt.txt\t" + timeFourMonthsPrevRFC + "\tThis is so much better than #twitter",
timeThreeMonthsPrev: "foo\thttps://example.com/twtxt.txt\t" + timeThreeMonthsPrevRFC + "\tI can't wait to start on my next programming #project with <@foo_barrington https://example3.com/twtxt.txt>",
},
},
}
registry := New(nil)
// fill the test registry with the mock users
for _, e := range mockusers {
data := &User{}
data.Nick = e.nick
data.Date = e.date
data.Status = e.status
registry.Users[e.url] = data
}
return registry
}

98
registry/integ_test.go Normal file
View File

@ -0,0 +1,98 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry
import (
"strings"
"testing"
)
// This tests all the operations on an registry.
func Test_Integration(t *testing.T) {
var integration = func(t *testing.T) {
t.Logf("Creating registry object ...\n")
registry := New(nil)
t.Logf("Fetching remote twtxt file ...\n")
mainregistry, _, err := GetTwtxt("https://gbmor.dev/twtxt.txt", nil)
if err != nil {
t.Errorf("%v\n", err)
}
t.Logf("Parsing remote twtxt file ...\n")
parsed, errz := ParseUserTwtxt(mainregistry, "gbmor", "https://gbmor.dev/twtxt.txt")
if errz != nil {
t.Errorf("%v\n", errz)
}
t.Logf("Adding new user to registry ...\n")
err = registry.AddUser("TestRegistry", "https://gbmor.dev/twtxt.txt", nil, parsed)
if err != nil {
t.Errorf("%v\n", err)
}
t.Logf("Querying user statuses ...\n")
queryuser, err := registry.QueryUser("TestRegistry")
if err != nil {
t.Errorf("%v\n", err)
}
for _, e := range queryuser {
if !strings.Contains(e, "TestRegistry") {
t.Errorf("QueryUser() returned incorrect data\n")
}
}
t.Logf("Querying for keyword in statuses ...\n")
querystatus, err := registry.QueryInStatus("morning")
if err != nil {
t.Errorf("%v\n", err)
}
for _, e := range querystatus {
if !strings.Contains(e, "morning") {
t.Errorf("QueryInStatus() returned incorrect data\n")
}
}
t.Logf("Querying for all statuses ...\n")
allstatus, err := registry.QueryAllStatuses()
if err != nil {
t.Errorf("%v\n", err)
}
if len(allstatus) == 0 || allstatus == nil {
t.Errorf("Got nil/zero from QueryAllStatuses")
}
t.Logf("Querying for all users ...\n")
allusers, err := registry.QueryUser("")
if err != nil {
t.Errorf("%v\n", err)
}
if len(allusers) == 0 || allusers == nil {
t.Errorf("Got nil/zero users on empty QueryUser() query")
}
t.Logf("Deleting user ...\n")
err = registry.DelUser("https://gbmor.dev/twtxt.txt")
if err != nil {
t.Errorf("%v\n", err)
}
}
t.Run("Integration Test", integration)
}

196
registry/query.go Normal file
View File

@ -0,0 +1,196 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry // import "git.sr.ht/~gbmor/getwtxt/registry"
import (
"fmt"
"sort"
"strings"
"time"
)
// QueryUser checks the Registry for usernames
// or user URLs that contain the term provided as an argument. Entries
// are returned sorted by the date they were added to the Registry. If
// the argument provided is blank, return all users.
func (registry *Registry) QueryUser(term string) ([]string, error) {
if registry == nil {
return nil, fmt.Errorf("can't query empty registry for user")
}
term = strings.ToLower(term)
timekey := NewTimeMap()
keys := make(TimeSlice, 0)
var users []string
registry.Mu.RLock()
defer registry.Mu.RUnlock()
for k, v := range registry.Users {
if registry.Users[k] == nil {
continue
}
v.Mu.RLock()
if strings.Contains(strings.ToLower(v.Nick), term) || strings.Contains(strings.ToLower(k), term) {
thetime, err := time.Parse(time.RFC3339, v.Date)
if err != nil {
v.Mu.RUnlock()
continue
}
timekey[thetime] = v.Nick + "\t" + k + "\t" + v.Date + "\n"
keys = append(keys, thetime)
}
v.Mu.RUnlock()
}
sort.Sort(keys)
for _, e := range keys {
users = append(users, timekey[e])
}
return users, nil
}
// QueryInStatus returns all statuses in the Registry
// that contain the provided substring (tag, mention URL, etc).
func (registry *Registry) QueryInStatus(substring string) ([]string, error) {
if substring == "" {
return nil, fmt.Errorf("cannot query for empty tag")
} else if registry == nil {
return nil, fmt.Errorf("can't query statuses of empty registry")
}
statusmap := make([]TimeMap, 0)
registry.Mu.RLock()
defer registry.Mu.RUnlock()
for _, v := range registry.Users {
statusmap = append(statusmap, v.FindInStatus(substring))
}
sorted, err := SortByTime(statusmap...)
if err != nil {
return nil, err
}
return sorted, nil
}
// QueryAllStatuses returns all statuses in the Registry
// as a slice of strings sorted by timestamp.
func (registry *Registry) QueryAllStatuses() ([]string, error) {
if registry == nil {
return nil, fmt.Errorf("can't get latest statuses from empty registry")
}
statusmap, err := registry.GetStatuses()
if err != nil {
return nil, err
}
sorted, err := SortByTime(statusmap)
if err != nil {
return nil, err
}
if sorted == nil {
sorted = make([]string, 1)
}
return sorted, nil
}
// ReduceToPage returns the passed 'page' worth of output.
// One page is twenty items. For example, if 2 is passed,
// it will return data[20:40]. According to the twtxt
// registry specification, queries should accept a "page"
// value.
func ReduceToPage(page int, data []string) []string {
end := 20 * page
if end > len(data) || end < 1 {
end = len(data)
}
beg := end - 20
if beg > len(data)-1 || beg < 0 {
beg = 0
}
return data[beg:end]
}
// FindInStatus takes a user's statuses and looks for a given substring.
// Returns the statuses that include the substring as a TimeMap.
func (userdata *User) FindInStatus(substring string) TimeMap {
if userdata == nil {
return nil
} else if len(substring) > 140 {
return nil
}
substring = strings.ToLower(substring)
statuses := NewTimeMap()
userdata.Mu.RLock()
defer userdata.Mu.RUnlock()
for k, e := range userdata.Status {
if _, ok := userdata.Status[k]; !ok {
continue
}
parts := strings.Split(strings.ToLower(e), "\t")
if strings.Contains(parts[3], substring) {
statuses[k] = e
}
}
return statuses
}
// SortByTime returns a string slice of the query results,
// sorted by timestamp in descending order (newest first).
func SortByTime(tm ...TimeMap) ([]string, error) {
if tm == nil {
return nil, fmt.Errorf("can't sort nil TimeMaps")
}
var times = make(TimeSlice, 0)
var data []string
for _, e := range tm {
for k := range e {
times = append(times, k)
}
}
sort.Sort(times)
for k := range tm {
for _, e := range times {
if _, ok := tm[k][e]; ok {
data = append(data, tm[k][e])
}
}
}
return data, nil
}

459
registry/query_test.go Normal file
View File

@ -0,0 +1,459 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry
import (
"bufio"
"os"
"strings"
"testing"
"time"
)
var queryUserCases = []struct {
name string
term string
wantErr bool
}{
{
name: "Valid User",
term: "foo",
wantErr: false,
},
{
name: "Empty Query",
term: "",
wantErr: false,
},
{
name: "Nonexistent User",
term: "doesntexist",
wantErr: true,
},
{
name: "Garbage Data",
term: "will be replaced with garbage data",
wantErr: true,
},
}
// Checks if Registry.QueryUser() returns users that
// match the provided substring.
func Test_Registry_QueryUser(t *testing.T) {
registry := initTestEnv()
var buf = make([]byte, 256)
// read random data into case 8
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
queryUserCases[3].term = string(buf)
for n, tt := range queryUserCases {
t.Run(tt.name, func(t *testing.T) {
out, err := registry.QueryUser(tt.term)
if out == nil && err != nil && !tt.wantErr {
t.Errorf("Received nil output or an error when unexpected. Case %v, %v, %v\n", n, tt.term, err)
}
if out != nil && tt.wantErr {
t.Errorf("Received unexpected nil output when an error was expected. Case %v, %v\n", n, tt.term)
}
for _, e := range out {
one := strings.Split(e, "\t")
if !strings.Contains(one[0], tt.term) && !strings.Contains(one[1], tt.term) {
t.Errorf("Received incorrect output: %v != %v\n", tt.term, e)
}
}
})
}
}
func Benchmark_Registry_QueryUser(b *testing.B) {
registry := initTestEnv()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range queryUserCases {
_, err := registry.QueryUser(tt.term)
if err != nil {
b.Errorf("%v\n", err)
}
}
}
}
var queryInStatusCases = []struct {
name string
substr string
wantNil bool
wantErr bool
}{
{
name: "Tag in Status",
substr: "twtxt",
wantNil: false,
wantErr: false,
},
{
name: "Valid URL",
substr: "https://example.com/twtxt.txt",
wantNil: false,
wantErr: false,
},
{
name: "Multiple Words in Status",
substr: "next programming",
wantNil: false,
wantErr: false,
},
{
name: "Multiple Words, Not in Status",
substr: "explosive bananas from antarctica",
wantNil: true,
wantErr: false,
},
{
name: "Empty Query",
substr: "",
wantNil: true,
wantErr: true,
},
{
name: "Nonsense",
substr: "ahfiurrenkhfkajdhfao",
wantNil: true,
wantErr: false,
},
{
name: "Invalid URL",
substr: "https://doesnt.exist/twtxt.txt",
wantNil: true,
wantErr: false,
},
{
name: "Garbage Data",
substr: "will be replaced with garbage data",
wantNil: true,
wantErr: false,
},
}
// This tests whether we can find a substring in all of
// the known status messages, disregarding the metadata
// stored with each status.
func Test_Registry_QueryInStatus(t *testing.T) {
registry := initTestEnv()
var buf = make([]byte, 256)
// read random data into case 8
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
queryInStatusCases[7].substr = string(buf)
for _, tt := range queryInStatusCases {
t.Run(tt.name, func(t *testing.T) {
out, err := registry.QueryInStatus(tt.substr)
if err != nil && !tt.wantErr {
t.Errorf("Caught unexpected error: %v\n", err)
}
if !tt.wantErr && out == nil && !tt.wantNil {
t.Errorf("Got nil when expecting output\n")
}
if err == nil && tt.wantErr {
t.Errorf("Expecting error, got nil.\n")
}
for _, e := range out {
split := strings.Split(strings.ToLower(e), "\t")
if e != "" {
if !strings.Contains(split[3], strings.ToLower(tt.substr)) {
t.Errorf("Status without substring returned\n")
}
}
}
})
}
}
func Benchmark_Registry_QueryInStatus(b *testing.B) {
registry := initTestEnv()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range queryInStatusCases {
_, err := registry.QueryInStatus(tt.substr)
if err != nil {
continue
}
}
}
}
// Tests whether we can retrieve the 20 most
// recent statuses in the registry
func Test_QueryAllStatuses(t *testing.T) {
registry := initTestEnv()
t.Run("Latest Statuses", func(t *testing.T) {
out, err := registry.QueryAllStatuses()
if out == nil || err != nil {
t.Errorf("Got no statuses, or more than 20: %v, %v\n", len(out), err)
}
})
}
func Benchmark_QueryAllStatuses(b *testing.B) {
registry := initTestEnv()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := registry.QueryAllStatuses()
if err != nil {
continue
}
}
}
var get20cases = []struct {
name string
page int
wantErr bool
}{
{
name: "First Page",
page: 1,
wantErr: false,
},
{
name: "High Page Number",
page: 256,
wantErr: false,
},
{
name: "Illegal Page Number",
page: -23,
wantErr: false,
},
}
func Test_ReduceToPage(t *testing.T) {
registry := initTestEnv()
for _, tt := range get20cases {
t.Run(tt.name, func(t *testing.T) {
out, err := registry.QueryAllStatuses()
if err != nil && !tt.wantErr {
t.Errorf("%v\n", err.Error())
}
out = ReduceToPage(tt.page, out)
if len(out) > 20 || len(out) == 0 {
t.Errorf("Page-Reduce Malfunction: length of data %v\n", len(out))
}
})
}
}
func Benchmark_ReduceToPage(b *testing.B) {
registry := initTestEnv()
out, _ := registry.QueryAllStatuses()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range get20cases {
ReduceToPage(tt.page, out)
}
}
}
// This tests whether we can find a substring in the
// given user's status messages, disregarding the metadata
// stored with each status.
func Test_User_FindInStatus(t *testing.T) {
registry := initTestEnv()
var buf = make([]byte, 256)
// read random data into case 8
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
queryInStatusCases[7].substr = string(buf)
data := make([]*User, 0)
for _, v := range registry.Users {
data = append(data, v)
}
for _, tt := range queryInStatusCases {
t.Run(tt.name, func(t *testing.T) {
for _, e := range data {
tag := e.FindInStatus(tt.substr)
if tag == nil && !tt.wantNil {
t.Errorf("Got nil tag\n")
}
}
})
}
}
func Benchmark_User_FindInStatus(b *testing.B) {
registry := initTestEnv()
data := make([]*User, 0)
for _, v := range registry.Users {
data = append(data, v)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range data {
for _, v := range queryInStatusCases {
tt.FindInStatus(v.substr)
}
}
}
}
func Test_SortByTime_Slice(t *testing.T) {
registry := initTestEnv()
statusmap, err := registry.GetStatuses()
if err != nil {
t.Errorf("Failed to finish test initialization: %v\n", err)
}
t.Run("Sort By Time ([]TimeMap)", func(t *testing.T) {
sorted, err := SortByTime(statusmap)
if err != nil {
t.Errorf("%v\n", err)
}
split := strings.Split(sorted[0], "\t")
firsttime, _ := time.Parse("RFC3339", split[0])
for i := range sorted {
if i < len(sorted)-1 {
nextsplit := strings.Split(sorted[i+1], "\t")
nexttime, _ := time.Parse("RFC3339", nextsplit[0])
if firsttime.Before(nexttime) {
t.Errorf("Timestamps out of order: %v\n", sorted)
}
firsttime = nexttime
}
}
})
}
// Benchmarking a sort of 1000000 statuses by timestamp.
// Right now it's at roughly 2000ns per 2 statuses.
// Set sortMultiplier to be the number of desired
// statuses divided by four.
func Benchmark_SortByTime_Slice(b *testing.B) {
// I set this to 250,000,000 and it hard-locked
// my laptop. Oops.
sortMultiplier := 250
b.Logf("Benchmarking SortByTime with a constructed slice of %v statuses ...\n", sortMultiplier*4)
registry := initTestEnv()
statusmap, err := registry.GetStatuses()
if err != nil {
b.Errorf("Failed to finish benchmark initialization: %v\n", err)
}
// Constructed registry has four statuses. This
// makes a TimeMapSlice of 1000000 statuses.
statusmaps := make([]TimeMap, sortMultiplier*4)
for i := 0; i < sortMultiplier; i++ {
statusmaps = append(statusmaps, statusmap)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := SortByTime(statusmaps...)
if err != nil {
b.Errorf("%v\n", err)
}
}
}
func Test_SortByTime_Single(t *testing.T) {
registry := initTestEnv()
statusmap, err := registry.GetStatuses()
if err != nil {
t.Errorf("Failed to finish test initialization: %v\n", err)
}
t.Run("Sort By Time (TimeMap)", func(t *testing.T) {
sorted, err := SortByTime(statusmap)
if err != nil {
t.Errorf("%v\n", err)
}
split := strings.Split(sorted[0], "\t")
firsttime, _ := time.Parse("RFC3339", split[0])
for i := range sorted {
if i < len(sorted)-1 {
nextsplit := strings.Split(sorted[i+1], "\t")
nexttime, _ := time.Parse("RFC3339", nextsplit[0])
if firsttime.Before(nexttime) {
t.Errorf("Timestamps out of order: %v\n", sorted)
}
firsttime = nexttime
}
}
})
}
func Benchmark_SortByTime_Single(b *testing.B) {
registry := initTestEnv()
statusmap, err := registry.GetStatuses()
if err != nil {
b.Errorf("Failed to finish benchmark initialization: %v\n", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := SortByTime(statusmap)
if err != nil {
b.Errorf("%v\n", err)
}
}
}

30
registry/revive.toml Normal file
View File

@ -0,0 +1,30 @@
ignoreGeneratedHeader = false
severity = "warning"
confidence = 0.8
errorCode = 0
warningCode = 0
[rule.blank-imports]
[rule.context-as-argument]
[rule.context-keys-type]
[rule.dot-imports]
[rule.error-return]
[rule.error-strings]
[rule.error-naming]
[rule.exported]
[rule.if-return]
[rule.increment-decrement]
[rule.var-naming]
[rule.var-declaration]
[rule.package-comments]
[rule.range]
[rule.receiver-naming]
[rule.time-naming]
[rule.unexported-return]
[rule.indent-error-flow]
[rule.errorf]
[rule.empty-block]
[rule.superfluous-else]
[rule.unused-parameter]
[rule.unreachable-code]
[rule.redefines-builtin-id]

148
registry/types.go Normal file
View File

@ -0,0 +1,148 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
// Package registry implements functions and types that assist
// in the creation and management of a twtxt registry.
package registry // import "git.sr.ht/~gbmor/getwtxt/registry"
import (
"net"
"net/http"
"sync"
"time"
)
// Registrar implements the minimum amount of methods
// for a functioning Registry.
type Registrar interface {
Put(user *User) error
Get(urlKey string) (*User, error)
DelUser(urlKey string) error
UpdateUser(urlKey string) error
GetUserStatuses(urlKey string) (TimeMap, error)
GetStatuses() (TimeMap, error)
}
// User holds a given user's information
// and statuses.
type User struct {
// Provided to aid in concurrency-safe
// reads and writes. In most cases, the
// mutex in the associated Index should be
// used instead. This mutex is provided
// should the library user need to access
// a User independently of an Index.
Mu sync.RWMutex
// Nick is the user-specified nickname.
Nick string
// The URL of the user's twtxt file
URL string
// The reported last modification date
// of the user's twtxt.txt file.
LastModified string
// The IP address of the user is optionally
// recorded when submitted via POST.
IP net.IP
// The timestamp, in RFC3339 format,
// reflecting when the user was added.
Date string
// A TimeMap of the user's statuses
// from their twtxt file.
Status TimeMap
}
// Registry enables the bulk of a registry's
// user data storage and access.
type Registry struct {
// Provided to aid in concurrency-safe
// reads and writes to a given registry
// Users map.
Mu sync.RWMutex
// The registry's user data is contained
// in this map. The functions within this
// library expect the key to be the URL of
// a given user's twtxt file.
Users map[string]*User
// The client to use for HTTP requests.
// If nil is passed to NewIndex(), a
// client with a 10 second timeout
// and all other values as default is
// used.
HTTPClient *http.Client
}
// TimeMap holds extracted and processed user data as a
// string. A time.Time value is used as the key.
type TimeMap map[time.Time]string
// TimeSlice is a slice of time.Time used for sorting
// a TimeMap by timestamp.
type TimeSlice []time.Time
// NewUser returns a pointer to an initialized User
func NewUser() *User {
return &User{
Mu: sync.RWMutex{},
Status: NewTimeMap(),
}
}
// New returns an initialized Registry instance.
func New(client *http.Client) *Registry {
return &Registry{
Mu: sync.RWMutex{},
Users: make(map[string]*User),
HTTPClient: client,
}
}
// NewTimeMap returns an initialized TimeMap.
func NewTimeMap() TimeMap {
return make(TimeMap)
}
// Len returns the length of the TimeSlice to be sorted.
// This helps satisfy sort.Interface.
func (t TimeSlice) Len() int {
return len(t)
}
// Less returns true if the timestamp at index i is after
// the timestamp at index j in a given TimeSlice. This results
// in a descending (reversed) sort order for timestamps rather
// than ascending.
// This helps satisfy sort.Interface.
func (t TimeSlice) Less(i, j int) bool {
return t[i].After(t[j])
}
// Swap transposes the timestamps at the two given indices
// for the TimeSlice receiver.
// This helps satisfy sort.Interface.
func (t TimeSlice) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}

270
registry/user.go Normal file
View File

@ -0,0 +1,270 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry // import "git.sr.ht/~gbmor/getwtxt/registry"
import (
"fmt"
"net"
"strings"
"sync"
"time"
)
// AddUser inserts a new user into the Registry.
func (registry *Registry) AddUser(nickname, urlKey string, ipAddress net.IP, statuses TimeMap) error {
if registry == nil {
return fmt.Errorf("can't add user to uninitialized registry")
} else if nickname == "" || urlKey == "" {
return fmt.Errorf("both URL and Nick must be specified")
} else if !strings.HasPrefix(urlKey, "http") {
return fmt.Errorf("invalid URL: %v", urlKey)
}
registry.Mu.Lock()
defer registry.Mu.Unlock()
if _, ok := registry.Users[urlKey]; ok {
return fmt.Errorf("user %v already exists", urlKey)
}
registry.Users[urlKey] = &User{
Mu: sync.RWMutex{},
Nick: nickname,
URL: urlKey,
LastModified: "",
IP: ipAddress,
Date: time.Now().Format(time.RFC3339),
Status: statuses}
return nil
}
// Put inserts a given User into an Registry. The User
// being pushed need only have the URL field filled.
// All other fields may be empty.
// This can be destructive: an existing User in the
// Registry will be overwritten if its User.URL is the
// same as the User.URL being pushed.
func (registry *Registry) Put(user *User) error {
if user == nil {
return fmt.Errorf("can't push nil data to registry")
}
if registry == nil || registry.Users == nil {
return fmt.Errorf("can't push data to registry: registry uninitialized")
}
user.Mu.RLock()
if user.URL == "" {
user.Mu.RUnlock()
return fmt.Errorf("can't push data to registry: missing URL for key")
}
urlKey := user.URL
registry.Mu.Lock()
registry.Users[urlKey] = user
registry.Mu.Unlock()
user.Mu.RUnlock()
return nil
}
// Get returns the User associated with the
// provided URL key in the Registry.
func (registry *Registry) Get(urlKey string) (*User, error) {
if registry == nil {
return nil, fmt.Errorf("can't pop from nil registry")
}
if urlKey == "" {
return nil, fmt.Errorf("can't pop unless provided a key")
}
registry.Mu.RLock()
defer registry.Mu.RUnlock()
if _, ok := registry.Users[urlKey]; !ok {
return nil, fmt.Errorf("provided url key doesn't exist in registry")
}
registry.Users[urlKey].Mu.RLock()
userGot := registry.Users[urlKey]
registry.Users[urlKey].Mu.RUnlock()
return userGot, nil
}
// DelUser removes a user and all associated data from
// the Registry.
func (registry *Registry) DelUser(urlKey string) error {
if registry == nil {
return fmt.Errorf("can't delete user from empty registry")
} else if urlKey == "" {
return fmt.Errorf("can't delete blank user")
} else if !strings.HasPrefix(urlKey, "http") {
return fmt.Errorf("invalid URL: %v", urlKey)
}
registry.Mu.Lock()
defer registry.Mu.Unlock()
if _, ok := registry.Users[urlKey]; !ok {
return fmt.Errorf("can't delete user %v, user doesn't exist", urlKey)
}
delete(registry.Users, urlKey)
return nil
}
// UpdateUser scrapes an existing user's remote twtxt.txt
// file. Any new statuses are added to the user's entry
// in the Registry. If the remote twtxt data's reported
// Content-Length does not differ from what is stored,
// an error is returned.
func (registry *Registry) UpdateUser(urlKey string) error {
if urlKey == "" || !strings.HasPrefix(urlKey, "http") {
return fmt.Errorf("invalid URL: %v", urlKey)
}
diff, err := registry.DiffTwtxt(urlKey)
if err != nil {
return err
} else if !diff {
return fmt.Errorf("no new statuses available for %v", urlKey)
}
out, isRemoteRegistry, err := GetTwtxt(urlKey, registry.HTTPClient)
if err != nil {
return err
}
if isRemoteRegistry {
return fmt.Errorf("attempting to update registry URL - users should be updated individually")
}
registry.Mu.Lock()
defer registry.Mu.Unlock()
user := registry.Users[urlKey]
user.Mu.Lock()
defer user.Mu.Unlock()
nick := user.Nick
data, err := ParseUserTwtxt(out, nick, urlKey)
if err != nil {
return err
}
for i, e := range data {
user.Status[i] = e
}
registry.Users[urlKey] = user
return nil
}
// CrawlRemoteRegistry scrapes all nicknames and user URLs
// from a provided registry. The urlKey passed to this function
// must be in the form of https://registry.example.com/api/plain/users
func (registry *Registry) CrawlRemoteRegistry(urlKey string) error {
if urlKey == "" || !strings.HasPrefix(urlKey, "http") {
return fmt.Errorf("invalid URL: %v", urlKey)
}
out, isRemoteRegistry, err := GetTwtxt(urlKey, registry.HTTPClient)
if err != nil {
return err
}
if !isRemoteRegistry {
return fmt.Errorf("can't add single user via call to CrawlRemoteRegistry")
}
users, err := ParseRegistryTwtxt(out)
if err != nil {
return err
}
// only add new users so we don't overwrite data
// we already have (and lose statuses, etc)
registry.Mu.Lock()
defer registry.Mu.Unlock()
for _, e := range users {
if _, ok := registry.Users[e.URL]; !ok {
registry.Users[e.URL] = e
}
}
return nil
}
// GetUserStatuses returns a TimeMap containing single user's statuses
func (registry *Registry) GetUserStatuses(urlKey string) (TimeMap, error) {
if registry == nil {
return nil, fmt.Errorf("can't get statuses from an empty registry")
} else if urlKey == "" || !strings.HasPrefix(urlKey, "http") {
return nil, fmt.Errorf("invalid URL: %v", urlKey)
}
registry.Mu.RLock()
defer registry.Mu.RUnlock()
if _, ok := registry.Users[urlKey]; !ok {
return nil, fmt.Errorf("can't retrieve statuses of nonexistent user")
}
registry.Users[urlKey].Mu.RLock()
status := registry.Users[urlKey].Status
registry.Users[urlKey].Mu.RUnlock()
return status, nil
}
// GetStatuses returns a TimeMap containing all statuses
// from all users in the Registry.
func (registry *Registry) GetStatuses() (TimeMap, error) {
if registry == nil {
return nil, fmt.Errorf("can't get statuses from an empty registry")
}
statuses := NewTimeMap()
registry.Mu.RLock()
defer registry.Mu.RUnlock()
for _, v := range registry.Users {
v.Mu.RLock()
if v.Status == nil || len(v.Status) == 0 {
v.Mu.RUnlock()
continue
}
for a, b := range v.Status {
if _, ok := v.Status[a]; ok {
statuses[a] = b
}
}
v.Mu.RUnlock()
}
return statuses, nil
}

349
registry/user_test.go Normal file
View File

@ -0,0 +1,349 @@
/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry // import "git.sr.ht/~gbmor/getwtxt/registry"
import (
"bufio"
"fmt"
"net/http"
"os"
"reflect"
"testing"
)
var addUserCases = []struct {
name string
nick string
url string
wantErr bool
localOnly bool
}{
{
name: "Legitimate User (Local Only)",
nick: "testuser1",
url: "http://localhost:8080/twtxt.txt",
wantErr: false,
localOnly: true,
},
{
name: "Empty Query",
nick: "",
url: "",
wantErr: true,
localOnly: false,
},
{
name: "Invalid URL",
nick: "foo",
url: "foobarringtons",
wantErr: true,
localOnly: false,
},
{
name: "Garbage Data",
nick: "",
url: "",
wantErr: true,
localOnly: false,
},
}
// Tests if we can successfully add a user to the registry
func Test_Registry_AddUser(t *testing.T) {
registry := initTestEnv()
if !addUserCases[0].localOnly {
http.Handle("/twtxt.txt", http.HandlerFunc(twtxtHandler))
go fmt.Println(http.ListenAndServe(":8080", nil))
}
var buf = make([]byte, 256)
// read random data into case 5
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
addUserCases[3].nick = string(buf)
addUserCases[3].url = string(buf)
statuses, err := registry.GetStatuses()
if err != nil {
t.Errorf("Error setting up test: %v\n", err)
}
for n, tt := range addUserCases {
t.Run(tt.name, func(t *testing.T) {
if tt.localOnly {
t.Skipf("Local-only test. Skipping ... ")
}
err := registry.AddUser(tt.nick, tt.url, nil, statuses)
// only run some checks if we don't want an error
if !tt.wantErr {
if err != nil {
t.Errorf("Got error: %v\n", err)
}
// make sure we have *something* in the registry
if reflect.ValueOf(registry.Users[tt.url]).IsNil() {
t.Errorf("Failed to add user %v registry.\n", tt.url)
}
// see if the nick in the registry is the same
// as the test case. verifies the URL and the nick
// since the URL is used as the key
data := registry.Users[tt.url]
if data.Nick != tt.nick {
t.Errorf("Incorrect user data added to registry for user %v.\n", tt.url)
}
}
// check for the cases that should throw an error
if tt.wantErr && err == nil {
t.Errorf("Expected error for case %v, got nil\n", n)
}
})
}
}
func Benchmark_Registry_AddUser(b *testing.B) {
registry := initTestEnv()
statuses, err := registry.GetStatuses()
if err != nil {
b.Errorf("Error setting up test: %v\n", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range addUserCases {
err := registry.AddUser(tt.nick, tt.url, nil, statuses)
if err != nil {
continue
}
registry.Users[tt.url] = &User{}
}
}
}
var delUserCases = []struct {
name string
url string
wantErr bool
}{
{
name: "Valid User",
url: "https://example.com/twtxt.txt",
wantErr: false,
},
{
name: "Valid User",
url: "https://example3.com/twtxt.txt",
wantErr: false,
},
{
name: "Already Deleted User",
url: "https://example3.com/twtxt.txt",
wantErr: true,
},
{
name: "Empty Query",
url: "",
wantErr: true,
},
{
name: "Garbage Data",
url: "",
wantErr: true,
},
}
// Tests if we can successfully delete a user from the registry
func Test_Registry_DelUser(t *testing.T) {
registry := initTestEnv()
var buf = make([]byte, 256)
// read random data into case 5
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
delUserCases[4].url = string(buf)
for n, tt := range delUserCases {
t.Run(tt.name, func(t *testing.T) {
err := registry.DelUser(tt.url)
if !reflect.ValueOf(registry.Users[tt.url]).IsNil() {
t.Errorf("Failed to delete user %v from registry.\n", tt.url)
}
if tt.wantErr && err == nil {
t.Errorf("Expected error but did not receive. Case %v\n", n)
}
if !tt.wantErr && err != nil {
t.Errorf("Unexpected error for case %v: %v\n", n, err)
}
})
}
}
func Benchmark_Registry_DelUser(b *testing.B) {
registry := initTestEnv()
data1 := &User{
Nick: registry.Users[delUserCases[0].url].Nick,
Date: registry.Users[delUserCases[0].url].Date,
Status: registry.Users[delUserCases[0].url].Status,
}
data2 := &User{
Nick: registry.Users[delUserCases[1].url].Nick,
Date: registry.Users[delUserCases[1].url].Date,
Status: registry.Users[delUserCases[1].url].Status,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range delUserCases {
err := registry.DelUser(tt.url)
if err != nil {
continue
}
}
registry.Users[delUserCases[0].url] = data1
registry.Users[delUserCases[1].url] = data2
}
}
var getUserStatusCases = []struct {
name string
url string
wantErr bool
}{
{
name: "Valid User",
url: "https://example.com/twtxt.txt",
wantErr: false,
},
{
name: "Valid User",
url: "https://example3.com/twtxt.txt",
wantErr: false,
},
{
name: "Nonexistent User",
url: "https://doesn't.exist/twtxt.txt",
wantErr: true,
},
{
name: "Empty Query",
url: "",
wantErr: true,
},
{
name: "Garbage Data",
url: "",
wantErr: true,
},
}
// Checks if we can retrieve a single user's statuses
func Test_Registry_GetUserStatuses(t *testing.T) {
registry := initTestEnv()
var buf = make([]byte, 256)
// read random data into case 5
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
getUserStatusCases[4].url = string(buf)
for n, tt := range getUserStatusCases {
t.Run(tt.name, func(t *testing.T) {
statuses, err := registry.GetUserStatuses(tt.url)
if !tt.wantErr {
if reflect.ValueOf(statuses).IsNil() {
t.Errorf("Failed to pull statuses for user %v\n", tt.url)
}
// see if the function returns the same data
// that we already have
data := registry.Users[tt.url]
if !reflect.DeepEqual(data.Status, statuses) {
t.Errorf("Incorrect data retrieved as statuses for user %v.\n", tt.url)
}
}
if tt.wantErr && err == nil {
t.Errorf("Expected error, received nil for case %v: %v\n", n, tt.url)
}
})
}
}
func Benchmark_Registry_GetUserStatuses(b *testing.B) {
registry := initTestEnv()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range getUserStatusCases {
_, err := registry.GetUserStatuses(tt.url)
if err != nil {
continue
}
}
}
}
// Tests if we can retrieve all user statuses at once
func Test_Registry_GetStatuses(t *testing.T) {
registry := initTestEnv()
t.Run("Registry.GetStatuses()", func(t *testing.T) {
statuses, err := registry.GetStatuses()
if reflect.ValueOf(statuses).IsNil() || err != nil {
t.Errorf("Failed to pull all statuses. %v\n", err)
}
// Now do the same query manually to see
// if we get the same result
unionmap := NewTimeMap()
for _, v := range registry.Users {
for i, e := range v.Status {
unionmap[i] = e
}
}
if !reflect.DeepEqual(statuses, unionmap) {
t.Errorf("Incorrect data retrieved as statuses.\n")
}
})
}
func Benchmark_Registry_GetStatuses(b *testing.B) {
registry := initTestEnv()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := registry.GetStatuses()
if err != nil {
continue
}
}
}

View File

@ -1,8 +0,0 @@
# `getwtxt|svc`
This is an internal package created for code organization
purposes.
The file `getwtxt.go` in the parent directory calls
`svc.go::Start()`, which starts the registry server.

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"bytes"

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"bytes"
@ -27,7 +27,7 @@ import (
"reflect"
"testing"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
)
func Test_initTemplates(t *testing.T) {
@ -49,7 +49,7 @@ func Test_cacheUpdate(t *testing.T) {
killStatuses()
cacheUpdate()
urls := "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt"
urls := testTwtxtURL
newStatus := twtxtCache.Users[urls].Status
t.Run("Checking for any data", func(t *testing.T) {

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"log"

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"log"

View File

@ -17,30 +17,30 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"net"
"testing"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
)
func Test_pushpullDatabase(t *testing.T) {
initTestConf()
initTestDB()
out, _, err := registry.GetTwtxt("https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt", nil)
out, _, err := registry.GetTwtxt(testTwtxtURL, nil)
if err != nil {
t.Errorf("Couldn't set up test: %v\n", err)
}
statusmap, err := registry.ParseUserTwtxt(out, "getwtxttest", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt")
statusmap, err := registry.ParseUserTwtxt(out, "getwtxttest", testTwtxtURL)
if err != nil {
t.Errorf("Couldn't set up test: %v\n", err)
}
twtxtCache.AddUser("getwtxttest", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt", net.ParseIP("127.0.0.1"), statusmap)
twtxtCache.AddUser("getwtxttest", testTwtxtURL, net.ParseIP("127.0.0.1"), statusmap)
remoteRegistries.List = append(remoteRegistries.List, "https://twtxt.tilde.institute/api/plain/users")
@ -52,7 +52,7 @@ func Test_pushpullDatabase(t *testing.T) {
})
t.Run("Clearing Registry", func(t *testing.T) {
err := twtxtCache.DelUser("https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt")
err := twtxtCache.DelUser(testTwtxtURL)
if err != nil {
t.Errorf("%v", err)
}
@ -62,7 +62,7 @@ func Test_pushpullDatabase(t *testing.T) {
pullDB()
twtxtCache.Mu.RLock()
if _, ok := twtxtCache.Users["https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt"]; !ok {
if _, ok := twtxtCache.Users[testTwtxtURL]; !ok {
t.Errorf("Missing user previously pushed to database\n")
}
twtxtCache.Mu.RUnlock()
@ -73,18 +73,18 @@ func Benchmark_pushDatabase(b *testing.B) {
initTestConf()
initTestDB()
if _, ok := twtxtCache.Users["https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt"]; !ok {
out, _, err := registry.GetTwtxt("https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt", nil)
if _, ok := twtxtCache.Users[testTwtxtURL]; !ok {
out, _, err := registry.GetTwtxt(testTwtxtURL, nil)
if err != nil {
b.Errorf("Couldn't set up benchmark: %v\n", err)
}
statusmap, err := registry.ParseUserTwtxt(out, "getwtxttest", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt")
statusmap, err := registry.ParseUserTwtxt(out, "getwtxttest", testTwtxtURL)
if err != nil {
b.Errorf("Couldn't set up benchmark: %v\n", err)
}
twtxtCache.AddUser("getwtxttest", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt", net.ParseIP("127.0.0.1"), statusmap)
twtxtCache.AddUser("getwtxttest", testTwtxtURL, net.ParseIP("127.0.0.1"), statusmap)
}
b.ResetTimer()

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"fmt"
@ -27,7 +27,7 @@ import (
"strings"
"time"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
"github.com/gorilla/mux"
)

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"bytes"

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import "fmt"
@ -31,7 +31,7 @@ func titleScreen() {
\__, |\___|\__| \_/\_/ \__/_/\_\\__|
|___/
version ` + Vers + `
github.com/getwtxt/getwtxt
git.sr.ht/~gbmor/getwtxt
GPL v3
`)
@ -284,7 +284,7 @@ func manualScreen() {
Adding a user:
curl -X POST 'http://localhost:9001/api/plain/users\
?url=https://gbmor.dev/twtxt.txt&nickname=gbmor'
?url=https://example.org/twtxt.txt&nickname=somebody'
Retrieve user list:
curl 'http://localhost:9001/api/plain/users'

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"context"

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"html/template"
@ -26,7 +26,7 @@ import (
"os/signal"
"time"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
"github.com/spf13/pflag"
)

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"bytes"
@ -29,10 +29,12 @@ import (
"sync"
"testing"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
"github.com/spf13/viper"
)
const testTwtxtURL = "https://git.sr.ht/~gbmor/getwtxt/blob/master/testdata/twtxt.txt"
var (
testport string
initTestOnce sync.Once
@ -111,21 +113,21 @@ func testConfig() {
// user and their statuses, for testing.
func mockRegistry() {
twtxtCache = registry.New(nil)
statuses, _, _ := registry.GetTwtxt("https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt", nil)
parsed, _ := registry.ParseUserTwtxt(statuses, "getwtxttest", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt")
_ = twtxtCache.AddUser("getwtxttest", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt", net.ParseIP("127.0.0.1"), parsed)
statuses, _, _ := registry.GetTwtxt(testTwtxtURL, nil)
parsed, _ := registry.ParseUserTwtxt(statuses, "getwtxttest", testTwtxtURL)
_ = twtxtCache.AddUser("getwtxttest", testTwtxtURL, net.ParseIP("127.0.0.1"), parsed)
}
// Empties the mock registry's user of statuses
// for functions that test status modifications
func killStatuses() {
twtxtCache.Mu.Lock()
user := twtxtCache.Users["https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt"]
user := twtxtCache.Users[testTwtxtURL]
user.Mu.Lock()
user.Status = registry.NewTimeMap()
user.LastModified = "0"
twtxtCache.Users["https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt"] = user
twtxtCache.Users[testTwtxtURL] = user
user.Mu.Unlock()
twtxtCache.Mu.Unlock()

View File

@ -17,14 +17,14 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"net"
"strings"
"time"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
"github.com/syndtr/goleveldb/leveldb"
)

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"log"

View File

@ -17,14 +17,14 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"fmt"
"net/http"
"strings"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
)
// Requests to apiEndpointPOSTHandler are passed off to this

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"fmt"
@ -27,7 +27,7 @@ import (
"strings"
"testing"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
)
var apiPostUserCases = []struct {
@ -39,7 +39,7 @@ var apiPostUserCases = []struct {
{
name: "Known Good User",
nick: "getwtxttest",
uri: "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt",
uri: testTwtxtURL,
wantErr: false,
},
{
@ -100,7 +100,7 @@ func Benchmark_apiPostUser(b *testing.B) {
twtxtCache = registry.New(nil)
params := url.Values{}
params.Set("url", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt")
params.Set("url", testTwtxtURL)
params.Set("nickname", "gbmor")
req, _ := http.NewRequest("POST", "https://localhost"+portnum+"/api/plain/users", strings.NewReader(params.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"crypto/sha256"
@ -28,7 +28,7 @@ import (
"strings"
"sync"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
"github.com/gorilla/mux"
)

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"net"
@ -25,7 +25,7 @@ import (
"strings"
"testing"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
)
func Test_dedupe(t *testing.T) {
@ -61,7 +61,7 @@ func Benchmark_dedupe(b *testing.B) {
func Test_parseQueryOut(t *testing.T) {
initTestConf()
urls := "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt"
urls := testTwtxtURL
nick := "getwtxttest"
out, _, err := registry.GetTwtxt(urls, nil)
@ -95,7 +95,7 @@ func Test_parseQueryOut(t *testing.T) {
func Benchmark_parseQueryOut(b *testing.B) {
initTestConf()
urls := "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt"
urls := testTwtxtURL
nick := "getwtxttest"
out, _, err := registry.GetTwtxt(urls, nil)
@ -203,9 +203,9 @@ func Test_compositeStatusQuery(t *testing.T) {
func Benchmark_compositeStatusQuery(b *testing.B) {
initTestConf()
statuses, _, _ := registry.GetTwtxt("https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt", nil)
parsed, _ := registry.ParseUserTwtxt(statuses, "getwtxttest", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt")
_ = twtxtCache.AddUser("getwtxttest", "https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt", net.ParseIP("127.0.0.1"), parsed)
statuses, _, _ := registry.GetTwtxt(testTwtxtURL, nil)
parsed, _ := registry.ParseUserTwtxt(statuses, "getwtxttest", testTwtxtURL)
_ = twtxtCache.AddUser("getwtxttest", testTwtxtURL, net.ParseIP("127.0.0.1"), parsed)
b.ResetTimer()
for i := 0; i < b.N; i++ {

View File

@ -17,14 +17,14 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"database/sql"
"net"
"time"
"github.com/getwtxt/registry"
"git.sr.ht/~gbmor/getwtxt/registry"
_ "github.com/mattn/go-sqlite3" // for the sqlite3 driver
)

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with Getwtxt. If not, see <https://www.gnu.org/licenses/>.
*/
package svc // import "github.com/getwtxt/getwtxt/svc"
package svc // import "git.sr.ht/~gbmor/getwtxt/svc"
import (
"fmt"

2
testdata/twtxt.txt vendored
View File

@ -18,7 +18,7 @@
# == Metadata ==
#
# nick = getwtxttest
# url = https://github.com/getwtxt/getwtxt/raw/master/testdata/twtxt.txt
# url = https://git.sr.ht/~gbmor/getwtxt/blob/master/testdata/twtxt.txt
#
# == Content ==
#