mirror of
				https://github.com/taigrr/shorturl
				synced 2025-01-18 04:03:16 -08:00 
			
		
		
		
	Initial Commit
This commit is contained in:
		
						commit
						3380bc5e65
					
				
							
								
								
									
										17
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | FROM golang:alpine | ||||||
|  | 
 | ||||||
|  | EXPOSE 8000/tcp | ||||||
|  | 
 | ||||||
|  | ENTRYPOINT ["shorturl"] | ||||||
|  | 
 | ||||||
|  | RUN \ | ||||||
|  |     apk add --update git && \ | ||||||
|  |     rm -rf /var/cache/apk/* | ||||||
|  | 
 | ||||||
|  | RUN mkdir -p /go/src/shorturl | ||||||
|  | WORKDIR /go/src/shorturl | ||||||
|  | 
 | ||||||
|  | COPY . /go/src/shorturl | ||||||
|  | 
 | ||||||
|  | RUN go get -v -d | ||||||
|  | RUN go install -v | ||||||
							
								
								
									
										22
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | Copyright (C) 2017 James Mills | ||||||
|  | 
 | ||||||
|  | httpfs is covered by the MIT license:: | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person | ||||||
|  | obtaining a copy of this software and associated documentation | ||||||
|  | files (the "Software"), to deal in the Software without restriction, | ||||||
|  | including without limitation the rights to use, copy, modify, merge, | ||||||
|  | publish, distribute, sublicense, and/or sell copies of the Software, | ||||||
|  | and to permit persons to whom the Software is furnished to do so, | ||||||
|  | subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included | ||||||
|  | in all copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||||
|  | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||||
|  | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||||||
|  | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||||||
|  | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||||||
|  | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE | ||||||
|  | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||||
							
								
								
									
										13
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | .PHONY: dev build clean | ||||||
|  | 
 | ||||||
|  | all: dev | ||||||
|  | 
 | ||||||
|  | dev: build | ||||||
|  | 	./shorturl -bind 127.0.0.1:8000 | ||||||
|  | 
 | ||||||
|  | build: clean | ||||||
|  | 	go get ./... | ||||||
|  | 	go build -o ./shorturl . | ||||||
|  | 
 | ||||||
|  | clean: | ||||||
|  | 	rm -rf bin shorturl | ||||||
							
								
								
									
										49
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | # shorturl | ||||||
|  | [](https://travis-ci.org/prologic/shorturl) | ||||||
|  | [](https://godoc.org/github.com/prologic/shorturl) | ||||||
|  | [](https://github.com/prologic/shorturl/wiki) | ||||||
|  | [](https://goreportcard.com/report/github.com/prologic/shorturl) | ||||||
|  | [](https://coveralls.io/r/prologic/shorturl) | ||||||
|  | 
 | ||||||
|  | shorturl is a web app that allows you to create smart bookmarks, commands and aliases by pointing your web browser's default search engine at a running instance. Similar to bunny1 or yubnub. | ||||||
|  | 
 | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  | ### Source | ||||||
|  | 
 | ||||||
|  | ```#!bash | ||||||
|  | $ go install github.com/prologic/shorturl/... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### OS X Homebrew | ||||||
|  | 
 | ||||||
|  | There is a formula provided that you can tap and install from | ||||||
|  | [prologic/homebrew-shorturl](https://github.com/prologic/homebrew-shorturl): | ||||||
|  | 
 | ||||||
|  | ```#!bash | ||||||
|  | $ brew tap prologic/shorturl | ||||||
|  | $ brew install shorturl | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | **NB:** This installs the latest released binary; so if you want a more | ||||||
|  | recent unreleased version from master you'll have to clone the repository | ||||||
|  | and build yourself. | ||||||
|  | 
 | ||||||
|  | shorturl is still early days so contributions, ideas and expertise are | ||||||
|  | much appreciated and highly welcome! | ||||||
|  | 
 | ||||||
|  | ## Usage | ||||||
|  | 
 | ||||||
|  | Run shorturl: | ||||||
|  | 
 | ||||||
|  | ```#!bash | ||||||
|  | $ shorturl -bind 127.0.0.1:8000 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Set your browser's default shorturl engine to http://localhost:8000/?q=%s | ||||||
|  | 
 | ||||||
|  | Then type `help` to view the main help page, `g foo bar` to perform a [Google](https://google.com) search for "foo bar" or `list` to list all available commands. | ||||||
|  | 
 | ||||||
|  | ## License | ||||||
|  | 
 | ||||||
|  | MIT | ||||||
							
								
								
									
										6
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | // Config ... | ||||||
|  | type Config struct { | ||||||
|  | 	FQDN string | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								config_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestZeroConfig(t *testing.T) { | ||||||
|  | 	assert := assert.New(t) | ||||||
|  | 
 | ||||||
|  | 	cfg := Config{} | ||||||
|  | 	assert.Equal(cfg.FQDN, "") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestConfig(t *testing.T) { | ||||||
|  | 	assert := assert.New(t) | ||||||
|  | 
 | ||||||
|  | 	cfg := Config{FQDN: "bar.com"} | ||||||
|  | 	assert.Equal(cfg.FQDN, "bar.com") | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 
 | ||||||
|  | 	"github.com/boltdb/bolt" | ||||||
|  | 	"github.com/namsral/flag" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	db  *bolt.DB | ||||||
|  | 	cfg Config | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	var ( | ||||||
|  | 		config string | ||||||
|  | 		dbpath string | ||||||
|  | 		bind   string | ||||||
|  | 		fqdn   string | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	flag.StringVar(&config, "config", "", "config file") | ||||||
|  | 	flag.StringVar(&dbpath, "dbpth", "urls.db", "Database path") | ||||||
|  | 	flag.StringVar(&bind, "bind", "0.0.0.0:8000", "[int]:<port> to bind to") | ||||||
|  | 	flag.StringVar(&fqdn, "fqdn", "localhost", "FQDN for public access") | ||||||
|  | 	flag.Parse() | ||||||
|  | 
 | ||||||
|  | 	// TODO: Abstract the Config and Handlers better | ||||||
|  | 	cfg.FQDN = fqdn | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	db, err = bolt.Open(dbpath, 0600, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	defer db.Close() | ||||||
|  | 
 | ||||||
|  | 	NewServer(bind, cfg).ListenAndServe() | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								scripts/release.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										43
									
								
								scripts/release.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | #!/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" | ||||||
							
								
								
									
										101
									
								
								server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								server.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"github.com/GeertJohan/go.rice" | ||||||
|  | 	"github.com/julienschmidt/httprouter" | ||||||
|  | 	"go.iondynamics.net/templice" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Server ... | ||||||
|  | type Server struct { | ||||||
|  | 	bind      string | ||||||
|  | 	config    Config | ||||||
|  | 	templates *templice.Template | ||||||
|  | 	router    *httprouter.Router | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) render(w http.ResponseWriter, tmpl string, data interface{}) { | ||||||
|  | 	err := s.templates.ExecuteTemplate(w, tmpl+".html", data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		http.Error(w, err.Error(), http.StatusInternalServerError) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IndexHandler ... | ||||||
|  | func (s *Server) IndexHandler() httprouter.Handle { | ||||||
|  | 	return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | ||||||
|  | 		s.render(w, "index", nil) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ShortenHandler ... | ||||||
|  | func (s *Server) ShortenHandler() httprouter.Handle { | ||||||
|  | 	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { | ||||||
|  | 		err := r.ParseForm() | ||||||
|  | 		if err != nil { | ||||||
|  | 			http.Error(w, "Internal Error", http.StatusInternalServerError) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		url := r.Form.Get("url") | ||||||
|  | 		u, err := NewURL(url) | ||||||
|  | 		if err != nil { | ||||||
|  | 			http.Error(w, "Internal Error", http.StatusInternalServerError) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		s.render(w, "url", struct{ ID string }{ID: u.ID()}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RedirectHandler ... | ||||||
|  | func (s *Server) RedirectHandler() 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 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		u, ok := LookupURL(id) | ||||||
|  | 		if !ok { | ||||||
|  | 			http.Error(w, "Not Found", http.StatusNotFound) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		http.Redirect(w, r, u.url, http.StatusFound) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ListenAndServe ... | ||||||
|  | func (s *Server) ListenAndServe() { | ||||||
|  | 	log.Fatal(http.ListenAndServe(s.bind, s.router)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) initRoutes() { | ||||||
|  | 	s.router.GET("/", s.IndexHandler()) | ||||||
|  | 	s.router.POST("/", s.ShortenHandler()) | ||||||
|  | 	s.router.GET("/:id", s.RedirectHandler()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewServer ... | ||||||
|  | func NewServer(bind string, config Config) *Server { | ||||||
|  | 	server := &Server{ | ||||||
|  | 		bind:      bind, | ||||||
|  | 		config:    config, | ||||||
|  | 		router:    httprouter.New(), | ||||||
|  | 		templates: templice.New(rice.MustFindBox("templates")), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err := server.templates.Load() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Panicf("error loading templates: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	server.initRoutes() | ||||||
|  | 
 | ||||||
|  | 	return server | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | <html> | ||||||
|  | 	<head> | ||||||
|  | 		<title>shorturl</title> | ||||||
|  | 	</head> | ||||||
|  | 	 | ||||||
|  | 	<body> | ||||||
|  | 		<p>Enter your long URL here:</p> | ||||||
|  | 		<form method="POST" action=""> | ||||||
|  | 			<input type="text" name="url"> | ||||||
|  | 			<input type="submit"> | ||||||
|  | 		</form> | ||||||
|  | 	</body> | ||||||
|  | 
 | ||||||
|  | </html> | ||||||
							
								
								
									
										10
									
								
								templates/url.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								templates/url.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | <html> | ||||||
|  | 	<head> | ||||||
|  | 		<title>shorturl</title> | ||||||
|  | 	</head> | ||||||
|  | 	 | ||||||
|  | 	<body> | ||||||
|  | 		<p>Your short url is: <a href="/{{.ID}}">{{.ID}}</a></p> | ||||||
|  | 	</body> | ||||||
|  | 
 | ||||||
|  | </html> | ||||||
							
								
								
									
										96
									
								
								urls.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								urls.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 
 | ||||||
|  | 	"github.com/boltdb/bolt" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // URL ... | ||||||
|  | type URL struct { | ||||||
|  | 	id  string | ||||||
|  | 	url string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ID ... | ||||||
|  | func (u URL) ID() string { | ||||||
|  | 	return u.id | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // URL ... | ||||||
|  | func (u URL) URL() string { | ||||||
|  | 	return u.url | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Save ... | ||||||
|  | func (u URL) Save() error { | ||||||
|  | 	log.Printf("u: %v", u) | ||||||
|  | 	if u.id == "" || u.url == "" { | ||||||
|  | 		log.Printf("u is nil :/") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err := db.Update(func(tx *bolt.Tx) error { | ||||||
|  | 		b, err := tx.CreateBucketIfNotExists([]byte("urls")) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Printf("create bucket failed: %s", err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		err = b.Put([]byte(u.id), []byte(u.url)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Printf("put key failed: %s", err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // LookupURL ... | ||||||
|  | func LookupURL(id string) (u URL, ok bool) { | ||||||
|  | 	err := db.View(func(tx *bolt.Tx) error { | ||||||
|  | 		b := tx.Bucket([]byte("urls")) | ||||||
|  | 		if b == nil { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		v := b.Get([]byte(id)) | ||||||
|  | 		if v != nil { | ||||||
|  | 			u = URL{id: id, url: string(v)} | ||||||
|  | 			ok = true | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("error looking up url for %s: %s", id, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewURL ... | ||||||
|  | func NewURL(url string) (u URL, err error) { | ||||||
|  | 	log.Printf("NewURL: %v", url) | ||||||
|  | 	var id string | ||||||
|  | 
 | ||||||
|  | 	for { | ||||||
|  | 		// TODO: Make length (5) configurable | ||||||
|  | 		id = RandomString(5) | ||||||
|  | 		_, ok := LookupURL(id) | ||||||
|  | 		if ok { | ||||||
|  | 			continue | ||||||
|  | 		} else { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	u = URL{id: id, url: url} | ||||||
|  | 	err = u.Save() | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								urls_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								urls_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/boltdb/bolt" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestZeroURL(t *testing.T) { | ||||||
|  | 	assert := assert.New(t) | ||||||
|  | 
 | ||||||
|  | 	u := URL{} | ||||||
|  | 	assert.Equal(u.ID(), "") | ||||||
|  | 	assert.Equal(u.URL(), "") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestURLSaveLookup(t *testing.T) { | ||||||
|  | 	assert := assert.New(t) | ||||||
|  | 
 | ||||||
|  | 	db, _ = bolt.Open("test.db", 0600, nil) | ||||||
|  | 	defer db.Close() | ||||||
|  | 
 | ||||||
|  | 	URL{id: "asdf", url: "https://localhost"}.Save() | ||||||
|  | 
 | ||||||
|  | 	u, ok := LookupURL("asdf") | ||||||
|  | 	assert.True(ok) | ||||||
|  | 	assert.Equal(u.id, "asdf") | ||||||
|  | 	assert.Equal(u.url, "https://localhost") | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								utils.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | 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) | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user