diff --git a/README.md b/README.md
index 65873bec..3c844b8a 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,10 @@ These types of projects need heart and sacrifices to continue offer the best dev
### 📑 Table of contents
+
+
+
+
* [Installation](#-installation)
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-10-july-2017--v800)
* [Learn](#-learn)
@@ -58,7 +62,7 @@ These types of projects need heart and sacrifices to continue offer the best dev
* [Miscellaneous](_examples/#miscellaneous)
* [Typescript Automation Tools](typescript/#table-of-contents)
* [Tutorial: Online Visitors](_examples/tutorial/online-visitors)
- * [Tutorial: URL Shortener using BoltDB](_examples/tutorial/url-shortener)
+ * [Tutorial: URL Shortener using BoltDB](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7)
* [Middleware](middleware/)
* [Dockerize](https://github.com/iris-contrib/cloud-native-go)
* [Philosophy](#-philosophy)
@@ -74,6 +78,7 @@ The only requirement is the [Go Programming Language](https://golang.org/dl/), a
```sh
$ go get -u github.com/kataras/iris
+$ go get -u github.com/iris-contrib/middleware/... # useful handlers, optionally
```
> _iris_ takes advantage of the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature. You get truly reproducible builds, as this method guards against upstream renames and deletes.
diff --git a/_examples/tutorial/url-shortener/factory.go b/_examples/tutorial/url-shortener/factory.go
new file mode 100644
index 00000000..9b893970
--- /dev/null
+++ b/_examples/tutorial/url-shortener/factory.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "github.com/satori/go.uuid"
+ "net/url"
+)
+
+// Generator the type to generate keys(short urls)
+type Generator func() string
+
+// DefaultGenerator is the defautl url generator
+var DefaultGenerator = func() string {
+ return uuid.NewV4().String()
+}
+
+// Factory is responsible to generate keys(short urls)
+type Factory struct {
+ store Store
+ generator Generator
+}
+
+// NewFactory receives a generator and a store and returns a new url Factory.
+func NewFactory(generator Generator, store Store) *Factory {
+ return &Factory{
+ store: store,
+ generator: generator,
+ }
+}
+
+// Gen generates the key.
+func (f *Factory) Gen(uri string) (key string, err error) {
+ // we don't return the parsed url because #hash are converted to uri-compatible
+ // and we don't want to encode/decode all the time, there is no need for that,
+ // we save the url as the user expects if the uri validation passed.
+ _, err = url.ParseRequestURI(uri)
+ if err != nil {
+ return "", err
+ }
+
+ key = f.generator()
+ // Make sure that the key is unique
+ for {
+ if v := f.store.Get(key); v == "" {
+ break
+ }
+ key = f.generator()
+ }
+
+ return key, nil
+}
diff --git a/_examples/tutorial/url-shortener/main.go b/_examples/tutorial/url-shortener/main.go
index 2e247620..0c234588 100644
--- a/_examples/tutorial/url-shortener/main.go
+++ b/_examples/tutorial/url-shortener/main.go
@@ -1,22 +1,19 @@
-// Package main shows how you can create a simple URL SHortener.
+// Package main shows how you can create a simple URL Shortener.
+//
+// Article: https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7
//
// $ go get github.com/boltdb/bolt/...
-// $ go run main.go
-// $ start http://localhost:8080
+// $ go get github.com/satori/go.uuid
+// $ cd $GOPATH/src/github.com/kataras/iris/_examples/tutorial/url-shortener
+// $ go build
+// $ ./url-shortener
package main
import (
- "bytes"
"html/template"
- "net/url"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
- "github.com/kataras/iris/view"
-
- "github.com/boltdb/bolt"
-
- "github.com/satori/go.uuid"
)
func main() {
@@ -39,7 +36,7 @@ func newApp(db *DB) *iris.Application {
factory := NewFactory(DefaultGenerator, db)
// serve the "./templates" directory's "*.html" files with the HTML std view engine.
- tmpl := view.HTML("./templates", ".html").Reload(true)
+ tmpl := iris.HTML("./templates", ".html").Reload(true)
// register any template func(s) here.
//
// Look ./templates/index.html#L16
@@ -117,236 +114,3 @@ func newApp(db *DB) *iris.Application {
return app
}
-
-// +------------------------------------------------------------+
-// | |
-// | Store |
-// | |
-// +------------------------------------------------------------+
-
-// Panic panics, change it if you don't want to panic on critical INITIALIZE-ONLY-ERRORS
-var Panic = func(v interface{}) {
- panic(v)
-}
-
-// Store is the store interface for urls.
-// Note: no Del functionality.
-type Store interface {
- Set(key string, value string) error // error if something went wrong
- Get(key string) string // empty value if not found
- Len() int // should return the number of all the records/tables/buckets
- Close() // release the store or ignore
-}
-
-var (
- tableURLs = []byte("urls")
-)
-
-// DB representation of a Store.
-// Only one table/bucket which contains the urls, so it's not a fully Database,
-// it works only with single bucket because that all we need.
-type DB struct {
- db *bolt.DB
-}
-
-var _ Store = &DB{}
-
-// openDatabase open a new database connection
-// and returns its instance.
-func openDatabase(stumb string) *bolt.DB {
- // Open the data(base) file in the current working directory.
- // It will be created if it doesn't exist.
- db, err := bolt.Open(stumb, 0600, nil)
- if err != nil {
- Panic(err)
- }
-
- // create the buckets here
- var tables = [...][]byte{
- tableURLs,
- }
-
- db.Update(func(tx *bolt.Tx) (err error) {
- for _, table := range tables {
- _, err = tx.CreateBucketIfNotExists(table)
- if err != nil {
- Panic(err)
- }
- }
-
- return
- })
-
- return db
-}
-
-// NewDB returns a new DB instance, its connection is opened.
-// DB implements the Store.
-func NewDB(stumb string) *DB {
- return &DB{
- db: openDatabase(stumb),
- }
-}
-
-// Set sets a shorten url and its key
-// Note: Caller is responsible to generate a key.
-func (d *DB) Set(key string, value string) error {
- return d.db.Update(func(tx *bolt.Tx) error {
- b, err := tx.CreateBucketIfNotExists(tableURLs)
- // Generate ID for the url
- // Note: we could use that instead of a random string key
- // but we want to simulate a real-world url shortener
- // so we skip that.
- // id, _ := b.NextSequence()
- if err != nil {
- return err
- }
-
- k := []byte(key)
- valueB := []byte(value)
- c := b.Cursor()
-
- found := false
- for k, v := c.First(); k != nil; k, v = c.Next() {
- if bytes.Equal(valueB, v) {
- found = true
- break
- }
- }
- // if value already exists don't re-put it.
- if found {
- return nil
- }
-
- return b.Put(k, []byte(value))
- })
-}
-
-// Clear clears all the database entries for the table urls.
-func (d *DB) Clear() error {
- return d.db.Update(func(tx *bolt.Tx) error {
- return tx.DeleteBucket(tableURLs)
- })
-}
-
-// Get returns a url by its key.
-//
-// Returns an empty string if not found.
-func (d *DB) Get(key string) (value string) {
- keyB := []byte(key)
- d.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket(tableURLs)
- if b == nil {
- return nil
- }
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- if bytes.Equal(keyB, k) {
- value = string(v)
- break
- }
- }
-
- return nil
- })
-
- return
-}
-
-// GetByValue returns all keys for a specific (original) url value.
-func (d *DB) GetByValue(value string) (keys []string) {
- valueB := []byte(value)
- d.db.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket(tableURLs)
- if b == nil {
- return nil
- }
- c := b.Cursor()
- // first for the bucket's table "urls"
- for k, v := c.First(); k != nil; k, v = c.Next() {
- if bytes.Equal(valueB, v) {
- keys = append(keys, string(k))
- }
- }
-
- return nil
- })
-
- return
-}
-
-// Len returns all the "shorted" urls length
-func (d *DB) Len() (num int) {
- d.db.View(func(tx *bolt.Tx) error {
-
- // Assume bucket exists and has keys
- b := tx.Bucket(tableURLs)
- if b == nil {
- return nil
- }
-
- b.ForEach(func([]byte, []byte) error {
- num++
- return nil
- })
- return nil
- })
- return
-}
-
-// Close shutdowns the data(base) connection.
-func (d *DB) Close() {
- if err := d.db.Close(); err != nil {
- Panic(err)
- }
-}
-
-// +------------------------------------------------------------+
-// | |
-// | Factory |
-// | |
-// +------------------------------------------------------------+
-
-// Generator the type to generate keys(short urls)
-type Generator func() string
-
-// DefaultGenerator is the defautl url generator
-var DefaultGenerator = func() string {
- return uuid.NewV4().String()
-}
-
-// Factory is responsible to generate keys(short urls)
-type Factory struct {
- store Store
- generator Generator
-}
-
-// NewFactory receives a generator and a store and returns a new url Factory.
-func NewFactory(generator Generator, store Store) *Factory {
- return &Factory{
- store: store,
- generator: generator,
- }
-}
-
-// Gen generates the key.
-func (f *Factory) Gen(uri string) (key string, err error) {
- // we don't return the parsed url because #hash are converted to uri-compatible
- // and we don't want to encode/decode all the time, there is no need for that,
- // we save the url as the user expects if the uri validation passed.
- _, err = url.ParseRequestURI(uri)
- if err != nil {
- return "", err
- }
-
- key = f.generator()
- // Make sure that the key is unique
- for {
- if v := f.store.Get(key); v == "" {
- break
- }
- key = f.generator()
- }
-
- return key, nil
-}
diff --git a/_examples/tutorial/url-shortener/main_test.go b/_examples/tutorial/url-shortener/main_test.go
index b6f0216e..fa8002d5 100644
--- a/_examples/tutorial/url-shortener/main_test.go
+++ b/_examples/tutorial/url-shortener/main_test.go
@@ -3,13 +3,15 @@ package main
import (
"io/ioutil"
"os"
- "time"
-
"testing"
+ "time"
"github.com/kataras/iris/httptest"
)
+// TestURLShortener tests the simple tasks of our url shortener application.
+// Note that it's a pure test.
+// The rest possible checks is up to you, take it as as an exercise!
func TestURLShortener(t *testing.T) {
// temp db file
f, err := ioutil.TempFile("", "shortener")
@@ -41,6 +43,7 @@ func TestURLShortener(t *testing.T) {
e.POST("/shorten").
WithFormField("url", originalURL).Expect().
Status(httptest.StatusOK).Body().Contains("