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