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:
parent
8199cf8bc9
commit
626ce005c2
4
Makefile
4
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)"
|
||||
|
@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
6
main.go
6
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)
|
||||
}
|
||||
|
38
models.go
Normal file
38
models.go
Normal 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
51
models_test.go
Normal 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, "")
|
||||
}
|
60
server.go
60
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)
|
||||
|
@ -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
96
urls.go
@ -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
|
||||
}
|
31
urls_test.go
31
urls_test.go
@ -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")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user