1
0
mirror of https://github.com/taigrr/shorturl synced 2025-01-18 04:03:16 -08:00

Compare commits

...

19 Commits

Author SHA1 Message Date
FileGo
a3409454c0
Moved database to Bitcask (#22)
* Changed URL format to shortuuid

* Fixed drone CI failure

* Moved database to Bitcask and some minor changes

* Moved database to Bitcask and some minor changes

* Delete coverage.txt
2021-05-10 23:58:17 +10:00
FileGo
801777279b
Changed URL format to shortuuid (#21)
* Changed URL format to shortuuid

* Fixed drone CI failure
2021-05-02 17:06:06 +10:00
srxr
6dfe1f83f0
Improve layouts
1. Validate input url is empty for index and view page
2. Display origin full url in view page
3. Do not show edit and delete button on small screen
4. Add tooltip for view button
5. Pad domain on view page
2021-03-30 03:30:41 +00:00
srxr
31c239e486
Support edit ID 2021-03-27 14:51:02 +00:00
srxr
f65255f6eb
Handle delete URL 2021-03-27 14:48:46 +00:00
srxr
e984fce524
Handle edit URL 2021-03-27 14:48:46 +00:00
srxr
a55db041cc
Validate URL before shorten 2021-03-27 14:48:46 +00:00
srxr
36983ae9f7
Update dependencies 2021-03-27 08:41:12 +00:00
James Mills
9f228cd6e1
Update README.md
Closes #15
2019-09-01 11:55:51 +10:00
James Mills
c282c99112 Set theme jekyll-theme-hacker 2019-04-13 13:09:50 +10:00
Michał Sidor
78d6b7557a Fix repository name in badges (#11) 2019-04-13 13:09:09 +10:00
58ecfd1841
Fixed coverage collection 2018-12-31 19:10:39 +10:00
5b5c76551a
Add goreleaser config 2018-12-31 19:01:04 +10:00
fca6b94d77
Migrate to Drone CI, Codecov and Go11Modules 2018-12-31 18:54:07 +10:00
ba829169cc
Merge pull request #10 from JKomoroski/master
Fixing Expose Port in Dockerfile
2018-02-10 08:47:30 -08:00
jkomoroski
e4362a6069 Fixing Expose Port in Dockerfile 2018-02-09 16:51:50 -07:00
12464efb70
Cleanup index UI view 2017-09-06 23:27:13 -07:00
0c92587504 Update README.md 2017-09-02 14:02:38 -07:00
c9286c40e0
Add imagelayers badge to README 2017-09-02 13:50:41 -07:00
34 changed files with 984 additions and 217 deletions

24
.drone.yml Normal file
View File

@ -0,0 +1,24 @@
kind: pipeline
type: docker
name: default
steps:
- name: build
image: golang:latest
commands:
- make test
- name: coverage
image: plugins/codecov
settings:
token:
from_secret: codecov-token
- name: notify
image: plugins/webhook
settings:
urls: https://msgbus.mills.io/ci.mills.io
when:
status:
- success
- failure

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
bin
*.db
db/*
shorturl
rice-box.go

33
.gitmodules vendored
View File

@ -1,33 +0,0 @@
[submodule "vendor/github.com/GeertJohan/go.rice"]
path = vendor/github.com/GeertJohan/go.rice
url = https://github.com/GeertJohan/go.rice
[submodule "vendor/github.com/daaku/go.zipexe"]
path = vendor/github.com/daaku/go.zipexe
url = https://github.com/daaku/go.zipexe
[submodule "vendor/github.com/kardianos/osext"]
path = vendor/github.com/kardianos/osext
url = https://github.com/kardianos/osext
[submodule "vendor/github.com/asdine/storm"]
path = vendor/github.com/asdine/storm
url = https://github.com/asdine/storm
[submodule "vendor/github.com/boltdb/bolt"]
path = vendor/github.com/boltdb/bolt
url = https://github.com/boltdb/bolt
[submodule "vendor/github.com/julienschmidt/httprouter"]
path = vendor/github.com/julienschmidt/httprouter
url = https://github.com/julienschmidt/httprouter
[submodule "vendor/github.com/namsral/flag"]
path = vendor/github.com/namsral/flag
url = https://github.com/namsral/flag
[submodule "vendor/github.com/rcrowley/go-metrics"]
path = vendor/github.com/rcrowley/go-metrics
url = https://github.com/rcrowley/go-metrics
[submodule "vendor/github.com/thoas/stats"]
path = vendor/github.com/thoas/stats
url = https://github.com/thoas/stats
[submodule "vendor/github.com/unrolled/logger"]
path = vendor/github.com/unrolled/logger
url = https://github.com/unrolled/logger
[submodule "vendor/github.com/stretchr/testify"]
path = vendor/github.com/stretchr/testify
url = https://github.com/stretchr/testify

21
.goreleaser.yml Normal file
View File

@ -0,0 +1,21 @@
builds:
- main: .
binary: shorturl
flags: -tags "static_build"
ldflags: -w -X shorturl.Version={{.Version}} -X shorturl.Commit={{.Commit}}
env:
- CGO_ENABLED=0
goos:
- darwin
- freebsd
- linux
goarch:
- i386
- amd64
- arm
- amd64
goarm:
- 6
- 7
sign:
artifacts: checksum

View File

@ -1,8 +0,0 @@
language: go
sudo: false
go:
- tip
before_install:
- go get github.com/mattn/goveralls
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

View File

@ -24,7 +24,7 @@ LABEL shorturl.app main
COPY --from=build /go/src/github.com/${REPO}/${APP} /${APP}
EXPOSE 8080/tcp
EXPOSE 8000/tcp
ENTRYPOINT ["/shorturl"]
CMD []

View File

@ -27,8 +27,14 @@ image:
@docker build --build-arg TAG=$(TAG) --build-arg BUILD=$(BUILD) -t $(REPO):$(TAG) .
@echo "Image created: $(REPO):$(TAG)"
profile:
@go test -cpuprofile cpu.prof -memprofile mem.prof -v -bench .
bench:
@go test -v -bench .
test:
@go test -v -cover -race $(TEST_ARGS)
@go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic .
clean:
@rm -rf $(APP)
@git clean -f -d -X

View File

@ -1,10 +1,10 @@
# shorturl
# shorturl - A self-hosted URL shortener web app and service
[![Build Status](https://travis-ci.org/prologic/shorturl.svg)](https://travis-ci.org/prologic/shorturl)
[![GoDoc](https://godoc.org/github.com/prologic/shorturl?status.svg)](https://godoc.org/github.com/prologic/shorturl)
[![Wiki](https://img.shields.io/badge/docs-wiki-blue.svg)](https://github.com/prologic/shorturl/wiki)
[![Go Report Card](https://goreportcard.com/badge/github.com/prologic/shorturl)](https://goreportcard.com/report/github.com/prologic/shorturl)
[![Coverage](https://coveralls.io/repos/prologic/shorturl/badge.svg)](https://coveralls.io/r/prologic/shorturl)
[![Build Status](https://cloud.drone.io/api/badges/prologic/shorturl/status.svg)](https://cloud.drone.io/prologic/shorturl)
[![CodeCov](https://codecov.io/gh/prologic/shorturl/branch/master/graph/badge.svg)](https://codecov.io/gh/prologic/shorturl)
[![Go Report Card](https://goreportcard.com/badge/prologic/shorturl)](https://goreportcard.com/report/prologic/shorturl)
[![GoDoc](https://godoc.org/github.com/prologic/shorturl?status.svg)](https://godoc.org/github.com/prologic/shorturl)
[![Sourcegraph](https://sourcegraph.com/github.com/prologic/shorturl/-/badge.svg)](https://sourcegraph.com/github.com/prologic/shorturl?badge)
shorturl is a web app that allows you to create short urls of much longer more
complex urls for easier sharing or embedding.
@ -35,13 +35,17 @@ Then visit: http://localhost:8000/
## Configuration
By default shorturl stores urls in `urls.db` in the local directory. This can
be configured with the `-dbpath /path/to/urls.db` option.
By default shorturl stores urls in `db` subdirectory of the local directory. This can
be configured with the `-dbpath /path/to/db/` option.
shorturl also displays an absolute url after creating and uses the value of
`-baseurl` (*default: `""`*) for display purposes. This is useful for copying
and pasting the shorturl.
## Data Storage
Data is currently stored in a [Bitcask](https://github.com/prologic/bitcask) key/value store configured via the `-dbpath` option.
## License
MIT

1
_config.yml Normal file
View File

@ -0,0 +1 @@
theme: jekyll-theme-hacker

View File

@ -1,6 +1,15 @@
package main
import "github.com/prologic/bitcask"
// Config ...
type Config struct {
baseURL string
}
// Env ...
type Env struct {
cfg Config
db *bitcask.Bitcask
dbpath string
}

22
go.mod Normal file
View File

@ -0,0 +1,22 @@
module github.com/prologic/shorturl
go 1.16
require (
github.com/GeertJohan/go.rice v1.0.2
github.com/daaku/go.zipexe v1.0.1 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/julienschmidt/httprouter v1.3.0
github.com/kr/pretty v0.2.0 // indirect
github.com/lithammer/shortuuid v3.0.0+incompatible
github.com/namsral/flag v1.7.4-pre
github.com/prologic/bitcask v0.3.10
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
github.com/stretchr/testify v1.7.0
github.com/thoas/stats v0.0.0-20190407194641-965cb2de1678
github.com/unrolled/logger v0.0.0-20201216141554-31a3694fe979
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

368
go.sum Normal file
View File

@ -0,0 +1,368 @@
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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.2 h1:PtRw+Tg3oa3HYwiDBZyvOJ8LdIyf6lAovJJtr7YOAYk=
github.com/GeertJohan/go.rice v1.0.2/go.mod h1:af5vUNlDNkCjOZeSGFgIJxDje9qdjsO6hshx0gTmZt4=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
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/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
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/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/daaku/go.zipexe v1.0.1 h1:wV4zMsDOI2SZ2m7Tdz1Ps96Zrx+TzaK15VbUaGozw0M=
github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
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=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
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/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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
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/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w=
github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
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/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-homedir v1.1.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/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/namsral/flag v1.7.4-pre h1:b2ScHhoCUkbsq0d2C15Mv+VU8bl8hAXV8arnWiOHNZs=
github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo=
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/plar/go-adaptive-radix-tree v1.0.4 h1:Ucd8R6RH2E7RW8ZtDKrsWyOD3paG2qqJO0I20WQ8oWQ=
github.com/plar/go-adaptive-radix-tree v1.0.4/go.mod h1:Ot8d28EII3i7Lv4PSvBlF8ejiD/CtRYDuPsySJbSaK8=
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/prologic/bitcask v0.3.10 h1:HXygU8zCvW5gLpZ8aQECPk5iV/YQ3hcqdg/zVeES6s0=
github.com/prologic/bitcask v0.3.10/go.mod h1:8RKJdbHLE7HFGLYSGu9slnYXSV7DMIucwVkaIYOk9GY=
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=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
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/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.1/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/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/thoas/stats v0.0.0-20190407194641-965cb2de1678 h1:kFej3rMKjbzysHYvLmv5iOlbRymDMkNJxbovYb/iP0c=
github.com/thoas/stats v0.0.0-20190407194641-965cb2de1678/go.mod h1:GkZsNBOco11YY68OnXUARbSl26IOXXAeYf6ZKmSZR2M=
github.com/tidwall/btree v0.2.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/redcon v1.4.0/go.mod h1:IGzxyoKE3Ea5AWIXo/ZHP+hzY8sWXaMKr7KlFgcWSZU=
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/unrolled/logger v0.0.0-20201216141554-31a3694fe979 h1:47+K4wN0S8L3fUwgZtPEBIfNqtAE3tUvBfvHVZJAXfg=
github.com/unrolled/logger v0.0.0-20201216141554-31a3694fe979/go.mod h1:X5DBNY1yIVkuLwJP3BXlCoQCa5mGg7hPJPIA0Blwc44=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
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/crypto v0.0.0-20191011191535-87dc89f01550/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/exp v0.0.0-20200228211341-fcea875c7e85 h1:jqhIzSw5SQNkbu5hOGpgMHhkfXxrbsLJdkIRcX19gCY=
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
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/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
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-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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.53.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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=

42
main.go
View File

@ -6,14 +6,10 @@ import (
"fmt"
"log"
"os"
"os/signal"
"github.com/asdine/storm"
"github.com/namsral/flag"
)
var (
cfg Config
db *storm.DB
"github.com/prologic/bitcask"
)
func main() {
@ -27,7 +23,7 @@ func main() {
flag.BoolVar(&version, "v", false, "display version information")
flag.StringVar(&config, "config", "", "config file")
flag.StringVar(&dbpath, "dbpath", "urls.db", "Database path")
flag.StringVar(&dbpath, "dbpath", "./db/", "Database path")
flag.StringVar(&baseurl, "baseurl", "", "Base URL for display purposes")
flag.StringVar(&bind, "bind", "0.0.0.0:8000", "[int]:<port> to bind to")
flag.Parse()
@ -37,15 +33,39 @@ func main() {
os.Exit(0)
}
// Create dbpath if required
if _, err := os.Stat(dbpath); os.IsNotExist(err) {
err = os.Mkdir(dbpath, os.ModePerm)
if err != nil {
log.Fatal(err)
}
}
// Set up environment
env := Env{}
var err error
db, err = storm.Open(dbpath)
env.db, err = bitcask.Open(dbpath)
if err != nil {
log.Fatal(err)
}
defer db.Close()
defer env.db.Close()
// TODO: Abstract the Config and Handlers better
cfg.baseURL = baseurl
env.cfg.baseURL = baseurl
// Catch Ctrl+C to prevent Bitcask "key/value size is invalid" error on next start
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
if sig == os.Kill || sig == os.Interrupt {
env.db.Close()
os.Exit(0)
}
}
}()
env.NewServer(bind).ListenAndServe()
NewServer(bind, cfg).ListenAndServe()
}

33
main_test.go Normal file
View File

@ -0,0 +1,33 @@
package main
import (
"io/ioutil"
"log"
"os"
"testing"
"github.com/prologic/bitcask"
)
func TestMain(m *testing.M) {
tmpdir, err := ioutil.TempDir("", "shorturl")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpdir)
db, err := bitcask.Open(tmpdir)
if err != nil {
log.Fatal(err)
}
defer db.Close()
ret := m.Run()
err = os.RemoveAll(tmpdir)
if err != nil {
log.Fatal(err)
}
os.Exit(ret)
}

102
models.go
View File

@ -1,38 +1,104 @@
package main
import (
"encoding/json"
"errors"
"fmt"
nurl "net/url"
"strings"
"time"
"github.com/lithammer/shortuuid"
"github.com/prologic/bitcask"
)
// URL ...
type URL struct {
ID string `storm:"id"`
URL string `storm:"index"`
Name string `storm:"index"`
CreatedAt time.Time `storm:"index"`
UpdatedAt time.Time `storm:"index"`
ID string
URL string
Name string
CreatedAt time.Time
UpdatedAt time.Time
}
func GenerateID() string {
func (env *Env) GenerateID() string {
for {
// TODO: Make length (5) configurable
id := RandomString(5)
err := db.One("ID", id, nil)
if err != nil {
id := shortuuid.New()
if !env.db.Has([]byte(id)) {
return id
}
}
}
func NewURL(target string) (url *URL, err error) {
url = &URL{ID: GenerateID(), URL: target, CreatedAt: time.Now()}
err = db.Save(url)
func (url *URL) save(db *bitcask.Bitcask) (err error) {
if url.ID == "" {
return errors.New("no URL ID specified")
}
data, err := json.Marshal(url)
if err != nil {
return
}
err = db.Put([]byte(url.ID), data)
return
}
// SetName ...
func (u *URL) SetName(name string) error {
u.Name = name
u.UpdatedAt = time.Now()
return db.Save(&u)
func (env *Env) NewURL(target string) (url *URL, err error) {
u, err := parse(target)
if err != nil {
return
}
url = &URL{ID: env.GenerateID(), URL: u.String(), CreatedAt: time.Now()}
err = url.save(env.db)
return
}
func parse(target string) (u *nurl.URL, err error) {
u, err = nurl.Parse(strings.TrimSpace(target))
if err != nil {
return nil, fmt.Errorf("URL (%s) no satisfied", target)
}
if u.Scheme == "" || u.Host == "" {
return nil, fmt.Errorf("URL (%s) without scheme or host", target)
}
return u, nil
}
// SetName ...
func (u *URL) SetName(db *bitcask.Bitcask, name string) error {
u.Name = name
return db.Put([]byte(u.ID), []byte(u.Name))
}
func (u *URL) update(db *bitcask.Bitcask, target string) error {
url, err := parse(target)
if err != nil {
return err
}
if err := del(db, u.ID); err != nil {
return err
}
u.URL = url.String()
u.UpdatedAt = time.Now()
return u.save(db)
}
func del(db *bitcask.Bitcask, id string) error {
return db.Delete([]byte(id))
}
func get(db *bitcask.Bitcask, id string) (u URL, err error) {
val, err := db.Get([]byte(id))
if err != nil {
return
}
err = json.Unmarshal(val, &u)
return
}

View File

@ -3,49 +3,180 @@ package main
import (
"io/ioutil"
"log"
"math/rand"
"os"
"strconv"
"testing"
"time"
"github.com/asdine/storm"
"github.com/prologic/bitcask"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
tmpfile, err := ioutil.TempFile("", "shorturl")
func setupTest() *Env {
var err error
env := &Env{}
env.dbpath, err = ioutil.TempDir("", "shorturl")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpfile.Name())
db, err = storm.Open(tmpfile.Name())
env.db, err = bitcask.Open(env.dbpath)
if err != nil {
log.Fatal(err)
}
defer db.Close()
os.Exit(m.Run())
return env
}
func (env *Env) finishTest() {
err := env.db.Close()
if err != nil {
log.Fatal(err)
}
err = os.RemoveAll(env.dbpath)
if err != nil {
log.Fatal(err)
}
}
func TestZeroURL(t *testing.T) {
assert := assert.New(t)
u := URL{}
assert.Equal(u.ID, "")
assert.Equal(u.URL, "")
assert.Equal(u.Name, "")
assert.Equal(u.CreatedAt, time.Time{})
assert.Equal(u.UpdatedAt, time.Time{})
assert.Equal("", u.ID)
assert.Equal("", u.URL)
assert.Equal("", u.Name)
}
func TestNewURL(t *testing.T) {
assert := assert.New(t)
env := setupTest()
u, err := NewURL("https://www.google.com")
u, err := env.NewURL("https://www.google.com")
assert.Nil(err, nil)
assert.NotEqual(u.ID, "")
assert.Equal(u.URL, "https://www.google.com")
assert.Equal(u.Name, "")
_, err = env.NewURL("www.google.com")
assert.NotNil(t, err)
env.finishTest()
}
func TestSetName(t *testing.T) {
assert := assert.New(t)
env := setupTest()
u := URL{}
u.ID = "test"
name := "testname"
err := u.SetName(env.db, name)
assert.Nil(err)
val, err := env.db.Get([]byte(u.ID))
if err != nil {
log.Fatal(err)
}
assert.Equal(name, string(val))
env.finishTest()
}
func TestURLSave(t *testing.T) {
assert := assert.New(t)
env := setupTest()
u := URL{Name: "test", CreatedAt: time.Now()}
err := u.save(env.db)
if assert.NotNil(err) {
assert.Contains(err.Error(), "ID")
}
env.finishTest()
}
func TestURLDelete(t *testing.T) {
assert := assert.New(t)
env := setupTest()
// Insert a test item
id := "test"
err := env.db.Put([]byte(id), []byte("test"))
if err != nil {
t.Fatal(err)
}
// Test
assert.Nil(del(env.db, id))
assert.False(env.db.Has([]byte(id)))
env.finishTest()
}
func TestURLGet(t *testing.T) {
assert := assert.New(t)
env := setupTest()
t.Run("pass", func(t *testing.T) {
// Insert a test item
testUrl := URL{ID: "Test", Name: "Test", URL: "http://example.com", CreatedAt: time.Now()}
err := testUrl.save(env.db)
if err != nil {
t.Fatal(err)
}
// Test
u, err := get(env.db, testUrl.ID)
if assert.Nil(err) {
assert.Equal(testUrl.ID, u.ID)
assert.Equal(testUrl.Name, u.Name)
assert.Equal(testUrl.URL, u.URL)
}
})
t.Run("not_exist", func(t *testing.T) {
// Test
_, err := get(env.db, strconv.Itoa(rand.Intn(1e6)))
assert.NotNil(err)
})
env.finishTest()
}
func TestURLUpdate(t *testing.T) {
assert := assert.New(t)
env := setupTest()
t.Run("pass", func(t *testing.T) {
// Insert a test item
testUrl := URL{ID: "Test", Name: "Test", URL: "http://example.com", CreatedAt: time.Now()}
err := testUrl.save(env.db)
if err != nil {
t.Fatal(err)
}
// Test
newUrlString := "http://example.com/test/"
err = testUrl.update(env.db, newUrlString)
if assert.Nil(err) {
newUrl, err := get(env.db, testUrl.ID)
assert.Nil(err)
assert.Equal(newUrlString, newUrl.URL)
}
})
t.Run("parse_error", func(t *testing.T) {
testUrl := URL{ID: "test"}
err := testUrl.update(env.db, "th1s#isN0tanURL$")
assert.NotNil(err)
})
env.finishTest()
}

View File

@ -1,43 +0,0 @@
#!/bin/bash
echo -n "Version to tag: "
read TAG
echo -n "Name of release: "
read NAME
echo -n "Desc of release: "
read DESC
git tag ${TAG}
git push --tags
if [ ! -d ./bin ]; then
mkdir bin
else
rm -rf ./bin/*
fi
echo -n "Building binaries ... "
rice embed-go
GOOS=linux GOARCH=amd64 go build -o ./bin/shorturl-Linux-x86_64 .
GOOS=linux GOARCH=arm64 go build -o ./bin/shorturl-Linux-x86_64 .
GOOS=darwin GOARCH=amd64 go build -o ./bin/shorturl-Darwin-x86_64 .
GOOS=windows GOARCH=amd64 go build -o ./bin/shorturl-Windows-x86_64.exe .
echo "DONE"
echo -n "Uploading binaries ... "
github-release release \
-u prologic -p -r shorturl \
-t ${TAG} -n "${NAME}" -d "${DESC}"
for file in bin/*; do
name="$(echo $file | sed -e 's|bin/||g')"
github-release upload -u prologic -r shorturl -t ${TAG} -n $name -f $file
done
echo "DONE"

167
server.go
View File

@ -9,6 +9,7 @@ import (
"net/url"
// Logging
"github.com/unrolled/logger"
// Stats/Metrics
@ -16,8 +17,7 @@ import (
"github.com/rcrowley/go-metrics/exp"
"github.com/thoas/stats"
"github.com/GeertJohan/go.rice"
"github.com/asdine/storm"
rice "github.com/GeertJohan/go.rice"
"github.com/julienschmidt/httprouter"
)
@ -52,7 +52,7 @@ func (c *Counters) DecBy(name string, n int64) {
// Server ...
type Server struct {
bind string
config Config
env *Env
templates *Templates
router *httprouter.Router
@ -88,11 +88,15 @@ func (s *Server) IndexHandler() httprouter.Handle {
s.counters.Inc("n_index")
var urlList []*URL
err := db.All(&urlList)
if err != nil {
log.Printf("error querying urls index: %s", err)
http.Error(w, "Internal Error", http.StatusInternalServerError)
return
for id := range s.env.db.Keys() {
u, err := get(s.env.db, string(id))
if err != nil {
log.Printf("error querying urls index: %s", err)
http.Error(w, "Internal Error", http.StatusInternalServerError)
return
}
urlList = append(urlList, &u)
}
ctx := &IndexContext{
@ -108,7 +112,7 @@ func (s *Server) ShortenHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
s.counters.Inc("n_shorten")
u, err := NewURL(r.FormValue("url"))
u, err := s.env.NewURL(r.FormValue("url"))
if err != nil {
log.Printf("error creating new url: %s", err)
http.Error(w, "Internal Error", http.StatusInternalServerError)
@ -128,7 +132,6 @@ func (s *Server) ShortenHandler() httprouter.Handle {
// ViewHandler ...
func (s *Server) ViewHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var u URL
s.counters.Inc("n_view")
@ -138,25 +141,29 @@ func (s *Server) ViewHandler() httprouter.Handle {
return
}
err := db.One("ID", id, &u)
if err != nil && err == storm.ErrNotFound {
// Check if entry exists
if !s.env.db.Has([]byte(id)) {
http.Error(w, "Not Found", http.StatusNotFound)
return
} else if err != nil {
log.Printf("error looking up %s for viewing: %s", id, err)
http.Error(w, "Iternal Error", http.StatusInternalServerError)
return
}
baseURL, err := url.Parse(s.config.baseURL)
// Retrieve URL
u, err := get(s.env.db, id)
if err != nil {
log.Printf("error parsing config.baseURL: %s", s.config.baseURL)
log.Printf("error looking up %s for redirect: %s", id, err)
http.Error(w, "Internal Error", http.StatusInternalServerError)
return
}
baseURL, err := url.Parse(s.env.cfg.baseURL)
if err != nil {
log.Printf("error parsing config.baseURL: %s", s.env.cfg.baseURL)
http.Error(w, "Internal Error", http.StatusInternalServerError)
}
redirectURL, err := url.Parse(fmt.Sprintf("./r/%s", u.ID))
redirectURL, err := url.Parse(fmt.Sprintf("./r/%s", id))
if err != nil {
log.Printf("error parsing redirect url ./r/%s: %s", u.ID, err)
log.Printf("error parsing redirect url ./r/%s: %s", id, err)
http.Error(w, "Internal Error", http.StatusInternalServerError)
}
@ -167,9 +174,11 @@ func (s *Server) ViewHandler() httprouter.Handle {
struct {
ID string
URL string
Ori string
}{
ID: u.ID,
ID: id,
URL: fullURL.String(),
Ori: u.URL,
},
)
}
@ -178,8 +187,6 @@ func (s *Server) ViewHandler() httprouter.Handle {
// RedirectHandler ...
func (s *Server) RedirectHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var u URL
s.counters.Inc("n_redirect")
id := p.ByName("id")
@ -188,13 +195,17 @@ func (s *Server) RedirectHandler() httprouter.Handle {
return
}
err := db.One("ID", id, &u)
if err != nil && err == storm.ErrNotFound {
// Check if entry exists
if !s.env.db.Has([]byte(id)) {
http.Error(w, "Not Found", http.StatusNotFound)
return
} else if err != nil {
}
// Retrieve URL
u, err := get(s.env.db, id)
if err != nil {
log.Printf("error looking up %s for redirect: %s", id, err)
http.Error(w, "Iternal Error", http.StatusInternalServerError)
http.Error(w, "Internal Error", http.StatusInternalServerError)
return
}
@ -215,6 +226,74 @@ func (s *Server) StatsHandler() httprouter.Handle {
}
}
// EditHandler ...
func (s *Server) EditHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
id := p.ByName("id")
if id == "" {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
// Check if entry exists
if !s.env.db.Has([]byte(id)) {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
// Retrieve URL
u, err := get(s.env.db, id)
if err != nil {
log.Printf("error looking up %s for redirect: %s", id, err)
http.Error(w, "Internal Error", http.StatusInternalServerError)
return
}
s.render(
"edit", w,
struct {
ID string
URL string
}{
ID: u.ID,
URL: string(u.URL),
},
)
}
}
// UpdateHandler ...
func (s *Server) UpdateHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var u URL
id := p.ByName("id")
target := r.FormValue("url")
newID := r.FormValue("id")
if id == "" || target == "" {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
// Check if entry exists
if !s.env.db.Has([]byte(id)) {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
err := u.update(s.env.db, target)
if err != nil {
log.Printf("error updating %s error: %v", id, err)
http.Error(w, "Internal Error", http.StatusInternalServerError)
return
}
redirectURL := fmt.Sprintf("/u/%s", newID)
http.Redirect(w, r, redirectURL, http.StatusFound)
}
}
// ListenAndServe ...
func (s *Server) ListenAndServe() {
log.Fatal(
@ -227,6 +306,28 @@ func (s *Server) ListenAndServe() {
)
}
// DeleteHandler ...
func (s *Server) DeleteHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
id := p.ByName("id")
if id == "" {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
err := del(s.env.db, id)
if err != nil {
log.Printf("error delete id: %s: %v", id, err)
http.Error(w, "Iternal Error", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusFound)
}
}
func (s *Server) initRoutes() {
s.router.Handler("GET", "/debug/metrics", exp.ExpHandler(s.counters.r))
s.router.GET("/debug/stats", s.StatsHandler())
@ -245,13 +346,16 @@ func (s *Server) initRoutes() {
s.router.POST("/", s.ShortenHandler())
s.router.GET("/u/:id", s.ViewHandler())
s.router.GET("/r/:id", s.RedirectHandler())
s.router.GET("/e/:id", s.EditHandler())
s.router.POST("/e/:id", s.UpdateHandler())
s.router.GET("/d/:id", s.DeleteHandler())
}
// NewServer ...
func NewServer(bind string, config Config) *Server {
func (env *Env) NewServer(bind string) *Server {
server := &Server{
bind: bind,
config: config,
env: env,
router: httprouter.New(),
templates: NewTemplates("base"),
@ -278,8 +382,13 @@ func NewServer(bind string, config Config) *Server {
template.Must(viewTemplate.Parse(box.MustString("view.html")))
template.Must(viewTemplate.Parse(box.MustString("base.html")))
editTemplate := template.New("edit")
template.Must(editTemplate.Parse(box.MustString("edit.html")))
template.Must(editTemplate.Parse(box.MustString("base.html")))
server.templates.Add("index", indexTemplate)
server.templates.Add("view", viewTemplate)
server.templates.Add("edit", editTemplate)
server.initRoutes()

View File

@ -4,6 +4,7 @@
<head>
<link rel="stylesheet" href="/css/spectre-icons.min.css">
<link rel="stylesheet" href="/css/spectre.min.css">
<link rel="icon" href="data:,">
{{ template "stylesheets" . }}
<meta name="viewport" content="width=device-width, initial-scale=1" />
{{ template "css" . }}

36
templates/edit.html Normal file
View File

@ -0,0 +1,36 @@
{{define "content"}}
<section class="container">
<div class="columns">
<div class="column">
<p>Your short url is: <a href="/r/{{.ID}}">{{.ID}}</a></p>
<form class="mt-10" action="/e/{{.ID}}" method="POST">
<div class="form-group input-group">
<input class="form-input column col-3" type="text" name="id" value="{{.ID}}" readonly="readonly"/>
<input class="form-input" id="input-url" type="text" name="url" value="{{.URL}}" placeholder="Enter long url here..." />
<button class="btn btn-primary" id="submit" type="submit">OK</button>
<a class="btn btn-action" href="/u/{{.ID}}">
<i class="icon icon-forward">View</i>
</a>
</div>
</form>
</div>
</div>
</section>
{{end}}
{{define "scripts"}}
<script src="/js/jquery.min.js"></script>
<script>
$(document).ready(function() {
$("#input-url").focus(function() {
$("#submit").removeAttr("data-tooltip", "Please enter long url").removeClass("tooltip tooltip-left");
});
$("#submit").click(function() {
if ($("#input-url").val().trim() === "") {
$(this).attr("data-tooltip", "Please enter long url").addClass("tooltip tooltip-left");
return false;
}
});
});
</script>
{{end}}

View File

@ -3,33 +3,51 @@
<div class="columns">
<div class="column">
{{ range $URL := .URLList }}
<div class="input-group">
<span class="input-group-addon">
{{ $URL.ID }}
</span>
<i class="icon icon-forward mt-10"></i>
<span class="input-group-addon">
{{ $URL.URL }}
</span>
<a class="btn btn-action btn-primary" href="/u/{{$URL.ID}}">
<div class="input-group col-12">
<span class="input-group-addon col-8 d-inline-block text-ellipsis">
{{ $URL.ID }}
<i class="icon icon-forward"></i>
{{ $URL.URL -}}
</span>
<div class="popover popover-right">
<a class="btn btn-action btn-primary tooltip tooltip-left" data-tooltip="View Detail" href="/u/{{$URL.ID}}">
<i class="icon icon-forward">View</i>
</a>
<a class="btn btn-action" href="/e/{{$URL.ID}}">
<i class="icon icon-edit">Edit</i>
</a>
<a class="btn btn-action" href="/d/{{$URL.ID}}">
<i class="icon icon-delete">Delete</i>
</a>
</div>
<a class="btn btn-action hide-md" href="/e/{{$URL.ID}}">
<i class="icon icon-edit">Edit</i>
</a>
<a class="btn btn-action hide-lg" href="/d/{{$URL.ID}}">
<i class="icon icon-delete">Delete</i>
</a>
</div>
{{end}}
<form class="mt-10" action="" method="POST">
<div class="form-group input-group">
<label class="form-label" for="input-url"></label>
<input class="form-input" id="input-url" type="text" name="url" placeholder="Enter long url here...">
<button class="btn btn-primary" type="submit">Shorten</button>
<input class="form-input" id="input-url" type="text" name="url" placeholder="Enter long url here..."
autofocus>
<button class="btn btn-primary" id="submit" type="submit">Shorten</button>
</div>
</form>
</div>
</div>
</section>
{{end}}
{{define "scripts"}}
<script src="/js/jquery.min.js"></script>
<script>
$(document).ready(function() {
$("#input-url").focus(function() {
$("#submit").removeAttr("data-tooltip", "Please enter long url").removeClass("tooltip tooltip-left");
});
$("#submit").click(function() {
if ($("#input-url").val().trim() === "") {
$(this).attr("data-tooltip", "Please enter long url").addClass("tooltip tooltip-left");
return false;
}
});
});
</script>
{{end}}

View File

@ -2,10 +2,14 @@
<section class="container">
<div class="columns">
<div class="column">
<p>Your short url is: <a href="/r/{{.ID}}">{{.ID}}</a></p>
<p class="text-ellipsis text-secondary">Your short url is: <a href="/r/{{.ID}}">{{.ID}}</a></p>
<p class="text-ellipsis text-secondary">Your full url is: <a href="{{.Ori}}">{{.Ori}}</a></p>
<div class="form-group input-group">
<input class="form-input" id="input-url" value="{{.URL}}" readonly />
<input class="form-input text-ellipsis" id="input-url" value="{{.URL}}" readonly />
<button class="btn btn-primary hide" id="btn-copy" data-clipboard-target="#input-url">Copy</button>
<a class="btn btn-action" href="/e/{{.ID}}">
<i class="icon icon-edit">Edit</i>
</a>
</div>
</div>
</div>
@ -27,6 +31,12 @@ $(document).ready(function() {
}, timeout || 3000);
};
var padShortURL = function() {
var domain = window.location.origin;
var short = $("#input-url").val().replace(domain, "");
$("#input-url").val(domain + short);
};
clipboard.on("success", function(e) {
e.clearSelection();
displayTooltip("#btn-copy", "Copied!");
@ -37,6 +47,8 @@ $(document).ready(function() {
});
$("#btn-copy").removeClass("hide");
padShortURL();
});
</script>
{{end}}

View File

@ -1,19 +0,0 @@
package main
import (
"crypto/rand"
)
const (
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
)
// RandomString ...
func RandomString(length int) string {
bytes := make([]byte, length)
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphabet[b%byte(len(alphabet))]
}
return string(bytes)
}

@ -1 +0,0 @@
Subproject commit c02ca9a983da5807ddf7d796784928f5be4afd09

1
vendor/github.com/asdine/storm generated vendored

@ -1 +0,0 @@
Subproject commit e35194f772fef1442e9f6c8526a6c115ca729638

1
vendor/github.com/boltdb/bolt generated vendored

@ -1 +0,0 @@
Subproject commit 2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8

1
vendor/github.com/daaku/go.zipexe generated vendored

@ -1 +0,0 @@
Subproject commit a5fe2436ffcb3236e175e5149162b41cd28bd27d

@ -1 +0,0 @@
Subproject commit 975b5c4c7c21c0e3d2764200bf2aa8e34657ae6e

1
vendor/github.com/kardianos/osext generated vendored

@ -1 +0,0 @@
Subproject commit ae77be60afb1dcacde03767a8c37337fad28ac14

1
vendor/github.com/namsral/flag generated vendored

@ -1 +0,0 @@
Subproject commit 67f268f20922975c067ed799e4be6bacf152208c

@ -1 +0,0 @@
Subproject commit 1f30fe9094a513ce4c700b9a54458bbb0c96996c

1
vendor/github.com/stretchr/testify generated vendored

@ -1 +0,0 @@
Subproject commit 890a5c3458b43e6104ff5da8dfa139d013d77544

1
vendor/github.com/thoas/stats generated vendored

@ -1 +0,0 @@
Subproject commit 152b5d051953fdb6e45f14b6826962aadc032324

1
vendor/github.com/unrolled/logger generated vendored

@ -1 +0,0 @@
Subproject commit 96623791abc8e1ca9ad4e8a2753d2a2ff195cf2d