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

Added an index of all urls

This commit is contained in:
James Mills 2017-09-02 12:41:25 -07:00
parent 8199cf8bc9
commit 626ce005c2
No known key found for this signature in database
GPG Key ID: AC4C014F1440EBD6
9 changed files with 163 additions and 146 deletions

View File

@ -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)"

View File

@ -2,7 +2,6 @@ package main
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)

View File

@ -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)
}

38
models.go Normal file
View File

@ -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)
}

51
models_test.go Normal file
View File

@ -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, "")
}

View File

@ -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)

View File

@ -2,7 +2,27 @@
<section class="container">
<div class="columns">
<div class="column">
<form action="" method="POST">
{{ 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}}">
<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>
{{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...">

96
urls.go
View File

@ -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
}

View File

@ -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")
}