From 626ce005c23f4718df39a5d26f819714246c40af Mon Sep 17 00:00:00 2001 From: James Mills Date: Sat, 2 Sep 2017 12:41:25 -0700 Subject: [PATCH] Added an index of all urls --- Makefile | 4 +- config_test.go | 1 - main.go | 6 +-- models.go | 38 ++++++++++++++++++ models_test.go | 51 +++++++++++++++++++++++ server.go | 60 +++++++++++++++++++++------ templates/index.html | 22 +++++++++- urls.go | 96 -------------------------------------------- urls_test.go | 31 -------------- 9 files changed, 163 insertions(+), 146 deletions(-) create mode 100644 models.go create mode 100644 models_test.go delete mode 100644 urls.go delete mode 100644 urls_test.go diff --git a/Makefile b/Makefile index f075477..43dfadf 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,9 @@ dev: build @./$(APP) deps: - #@go get github.com/GeertJohan/go.rice/rice + @go get github.com/GeertJohan/go.rice/rice @go get ./... - #@rice embed-go + @rice embed-go build: clean deps @echo " -> Building $(TAG)$(BUILD)" diff --git a/config_test.go b/config_test.go index 55e4d11..60981fd 100644 --- a/config_test.go +++ b/config_test.go @@ -2,7 +2,6 @@ package main import ( "testing" - "time" "github.com/stretchr/testify/assert" ) diff --git a/main.go b/main.go index 9258fa5..c743d95 100644 --- a/main.go +++ b/main.go @@ -7,13 +7,13 @@ import ( "log" "os" - "github.com/boltdb/bolt" + "github.com/asdine/storm" "github.com/namsral/flag" ) var ( cfg Config - db *bolt.DB + db *storm.DB ) func main() { @@ -38,7 +38,7 @@ func main() { } var err error - db, err = bolt.Open(dbpath, 0600, nil) + db, err = storm.Open(dbpath) if err != nil { log.Fatal(err) } diff --git a/models.go b/models.go new file mode 100644 index 0000000..f661e05 --- /dev/null +++ b/models.go @@ -0,0 +1,38 @@ +package main + +import ( + "time" +) + +// 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"` +} + +func GenerateID() string { + for { + // TODO: Make length (5) configurable + id := RandomString(5) + err := db.One("ID", id, nil) + if err != nil { + return id + } + } +} + +func NewURL(target string) (url *URL, err error) { + url = &URL{ID: GenerateID(), URL: target, CreatedAt: time.Now()} + err = db.Save(url) + return +} + +// SetName ... +func (u *URL) SetName(name string) error { + u.Name = name + u.UpdatedAt = time.Now() + return db.Save(&u) +} diff --git a/models_test.go b/models_test.go new file mode 100644 index 0000000..e6cc9ea --- /dev/null +++ b/models_test.go @@ -0,0 +1,51 @@ +package main + +import ( + "io/ioutil" + "log" + "os" + "testing" + "time" + + "github.com/asdine/storm" + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + tmpfile, err := ioutil.TempFile("", "shorturl") + if err != nil { + log.Fatal(err) + } + + defer os.Remove(tmpfile.Name()) + + db, err = storm.Open(tmpfile.Name()) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + os.Exit(m.Run()) +} + +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{}) +} + +func TestNewURL(t *testing.T) { + assert := assert.New(t) + + u, err := 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, "") +} diff --git a/server.go b/server.go index 250a59c..9421dbb 100644 --- a/server.go +++ b/server.go @@ -17,6 +17,7 @@ import ( "github.com/thoas/stats" "github.com/GeertJohan/go.rice" + "github.com/asdine/storm" "github.com/julienschmidt/httprouter" ) @@ -66,21 +67,39 @@ type Server struct { func (s *Server) render(name string, w http.ResponseWriter, ctx interface{}) { buf, err := s.templates.Exec(name, ctx) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + log.Printf("error rendering template %s: %s", name, err) + http.Error(w, "Internal Error", http.StatusInternalServerError) } _, err = buf.WriteTo(w) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + log.Printf("error writing template buffer: %s", err) + http.Error(w, "Internal Error", http.StatusInternalServerError) } } +type IndexContext struct { + URLList []*URL +} + // IndexHandler ... func (s *Server) IndexHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { s.counters.Inc("n_index") - s.render("index", w, nil) + 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 + } + + ctx := &IndexContext{ + URLList: urlList, + } + + s.render("index", w, ctx) } } @@ -91,13 +110,15 @@ func (s *Server) ShortenHandler() httprouter.Handle { u, err := NewURL(r.FormValue("url")) if err != nil { + log.Printf("error creating new url: %s", err) http.Error(w, "Internal Error", http.StatusInternalServerError) return } - redirectURL, err := url.Parse(fmt.Sprintf("./u/%s", u.ID())) + redirectURL, err := url.Parse(fmt.Sprintf("./u/%s", u.ID)) if err != nil { - http.Error(w, "Internal Error", http.StatusInternalServerError) + log.Printf("error parsing redirect url ./u/%s: %s", u.ID, err) + http.Error(w, err.Error(), http.StatusInternalServerError) } http.Redirect(w, r, redirectURL.String(), http.StatusFound) @@ -107,6 +128,8 @@ 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") id := p.ByName("id") @@ -115,19 +138,25 @@ func (s *Server) ViewHandler() httprouter.Handle { return } - u, ok := LookupURL(id) - if !ok { + err := db.One("ID", id, &u) + if err != nil && err == storm.ErrNotFound { 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) if err != nil { + log.Printf("error parsing config.baseURL: %s", s.config.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", u.ID)) if err != nil { + log.Printf("error parsing redirect url ./r/%s: %s", u.ID, err) http.Error(w, "Internal Error", http.StatusInternalServerError) } @@ -139,7 +168,7 @@ func (s *Server) ViewHandler() httprouter.Handle { ID string URL string }{ - ID: u.ID(), + ID: u.ID, URL: fullURL.String(), }, ) @@ -149,6 +178,8 @@ 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") @@ -157,13 +188,17 @@ func (s *Server) RedirectHandler() httprouter.Handle { return } - u, ok := LookupURL(id) - if !ok { + err := db.One("ID", id, &u) + if err != nil && err == storm.ErrNotFound { http.Error(w, "Not Found", http.StatusNotFound) return + } else if err != nil { + log.Printf("error looking up %s for redirect: %s", id, err) + http.Error(w, "Iternal Error", http.StatusInternalServerError) + return } - http.Redirect(w, r, u.url, http.StatusFound) + http.Redirect(w, r, u.URL, http.StatusFound) } } @@ -173,6 +208,7 @@ func (s *Server) StatsHandler() httprouter.Handle { w.Header().Set("Content-Type", "application/json; charset=utf-8") bs, err := json.Marshal(s.stats.Data()) if err != nil { + log.Printf("error marshalling stats: %s", err) http.Error(w, err.Error(), http.StatusInternalServerError) } w.Write(bs) diff --git a/templates/index.html b/templates/index.html index 6e88dbf..d2731dd 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,7 +2,27 @@
-
+ {{ range $URL := .URLList }} +
+ + {{ $URL.ID }} + + + + {{ $URL.URL }} + + + View + + + Edit + + + Delete + +
+ {{end}} +
diff --git a/urls.go b/urls.go deleted file mode 100644 index 110eaaf..0000000 --- a/urls.go +++ /dev/null @@ -1,96 +0,0 @@ -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 -} diff --git a/urls_test.go b/urls_test.go deleted file mode 100644 index 9918949..0000000 --- a/urls_test.go +++ /dev/null @@ -1,31 +0,0 @@ -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") -}