diff --git a/.gitignore b/.gitignore index daf913b..0d412bf 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ _testmain.go *.exe *.test *.prof + +# exclude our compiled binary +gophurl diff --git a/data/json_shorturl_repo.go b/data/json_shorturl_repo.go index bf7cf86..804032e 100644 --- a/data/json_shorturl_repo.go +++ b/data/json_shorturl_repo.go @@ -1,43 +1,102 @@ package data import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "github.com/BelfastGophers/gophurl/models" ) -type JsonShortURLRepository struct { +type JSONShortURLRepository struct { Filepath string URLList *models.URLList } -func NewJsonShortURLRepository(filePath string) (*JsonShortURLRepository, error) { - repo := &JsonShortURLRepository{} +func NewJSONShortURLRepository(filePath string) (*JSONShortURLRepository, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + list := &models.URLList{} + + if err := json.Unmarshal(data, &list); err != nil { + return nil, err + } + + repo := &JSONShortURLRepository{ + Filepath: filePath, + URLList: list, + } + return repo, nil } -func (repo *JsonShortURLRepository) Get(code string) (*models.ShortURL, error) { - return nil, nil +func (repo *JSONShortURLRepository) Get(code string) (*models.ShortURL, error) { + shortUrls := repo.URLList.GetShortUrls() + + for _, shortUrl := range shortUrls { + if code == shortUrl.Code { + return shortUrl, nil + } + } + return nil, errors.New(fmt.Sprintf("Unable to locate url for '%v'", code)) } -func (repo *JsonShortURLRepository) Add(url *models.ShortURL) error { +func (repo *JSONShortURLRepository) Add(url *models.ShortURL) error { return nil } -func (repo *JsonShortURLRepository) Delete(url *models.ShortURL) error { +func (repo *JSONShortURLRepository) Delete(url *models.ShortURL) error { return nil } -func (repo *JsonShortURLRepository) Update(url *models.ShortURL) error { +func (repo *JSONShortURLRepository) Update(url *models.ShortURL) error { return nil } -func (repo *JsonShortURLRepository) All() (map[string]*models.ShortURL, error) { - return map[string]*models.ShortURL{}, nil +func (repo *JSONShortURLRepository) All() (map[string]*models.ShortURL, error) { + return repo.URLList.ShortURLs, nil } -func (repo *JsonShortURLRepository) IncrementAccessed(code string) error { - return nil +func (repo *JSONShortURLRepository) IncrementAccessed(code string) error { + shortUrls := repo.URLList.GetShortUrls() + + for _, shortUrl := range shortUrls { + if code == shortUrl.Code { + shortUrl.AccessCount++ + err := repo.write() + + if err != nil { + return err + } + + return nil + } + } + return errors.New(fmt.Sprintf("Unable to locate ShortUrl with code '%v'", code)) } -func (repo *JsonShortURLRepository) write() error { +func (repo *JSONShortURLRepository) write() error { + list := repo.URLList + + jsonList, err := json.MarshalIndent(list, "", " ") + if err != nil { + return err + } + + err = ioutil.WriteFile(repo.Filepath, jsonList, 0644) + if err != nil { + return err + } + return nil } diff --git a/demo.json b/demo.json index 5ae1640..329fc16 100644 --- a/demo.json +++ b/demo.json @@ -1,15 +1,15 @@ { "updated": "2016-08-25T09:39:48+00:00", "urls": { - "asdf43": { - "accessed": 7, - "code": "asdf43", + "google": { + "accessed": 0, + "code": "goog", "created": "2016-08-25T09:39:48+00:00", "url": "https://www.google.com/" }, "belfast-gophers": { - "accessed": 1, - "code": "gitgo", + "accessed": 0, + "code": "belgo", "created": "2016-08-25T09:43:48+00:00", "url": "https://twitter.com/belfast_gopher" } diff --git a/handlers/html/index.go b/handlers/html/index.go index 97da5a1..d9f69a2 100644 --- a/handlers/html/index.go +++ b/handlers/html/index.go @@ -13,8 +13,9 @@ const ( {{ range $key, $url := . }} {{ $key }} - {{ $url.Url }} - {{ $url.Accessed }} + {{ $url.URL }} + {{ $url.Code }} + {{ $url.AccessCount }} {{ $url.Created }} {{ end }} diff --git a/handlers/web.go b/handlers/web.go index 0bff675..9aa83ec 100644 --- a/handlers/web.go +++ b/handlers/web.go @@ -13,7 +13,9 @@ import ( // with a collection of ShortURLs. The implmentation of the interactor is // where all business rules can be implemented type ShortURLInteractor interface { - GetAll() ([]*models.ShortURL, error) + GetAll() (map[string]*models.ShortURL, error) + GetUrl(string) (string, error) + IncrementAccessCount(string) error } // WebShortener provides a number of HTTP Handlers. @@ -27,19 +29,39 @@ type WebShortener struct { // * If a path is provided, try to redirect to it func (handler *WebShortener) Index(w http.ResponseWriter, req *http.Request) { // TODO: Check if a path is provided. If it is follow it + shortUrlCode := req.URL.Path[1:] // Get all of the URL's from the Repository - _, err := handler.ShortURLInteractor.GetAll() + urls, err := handler.ShortURLInteractor.GetAll() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } + // if a shortUrlCode is present in the URL, redirect to it + if shortUrlCode != "" { + redirectUrl, err := handler.GetRedirectUrl(shortUrlCode) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + err = handler.ShortURLInteractor.IncrementAccessCount(shortUrlCode) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + http.Redirect(w, req, redirectUrl, http.StatusFound) + } + tmpl, err := template.New("name").Parse(html.Index) - if err = tmpl.Execute(w, nil); err != nil { + if err = tmpl.Execute(w, urls); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } +func (handler *WebShortener) GetRedirectUrl(urlCode string) (string, error) { + return handler.ShortURLInteractor.GetUrl(urlCode) +} + // NewShortenerWeb returns a struct which implements the WebShortener interface. It // populates fields to usable values func NewWebShortener(urlRepo models.ShortURLRepository) *WebShortener { diff --git a/main.go b/main.go index 6a8dc77..b5ffe22 100644 --- a/main.go +++ b/main.go @@ -9,13 +9,13 @@ import ( const ( // Path to a JSON file - pathToJsonFile = "demo.json" // Eeewwwwwww, code smell + pathToJSONFile = "demo.json" // Eeewwwwwww, code smell ) // run is called by main so we can actually test this logic // TODO: Write tests.... func run() error { - repo, err := data.NewJsonShortURLRepository(pathToJsonFile) + repo, err := data.NewJSONShortURLRepository(pathToJSONFile) if err != nil { return err } diff --git a/models/url.go b/models/url.go index 142cc7f..1e8d1b5 100644 --- a/models/url.go +++ b/models/url.go @@ -10,11 +10,15 @@ type URLList struct { ShortURLs map[string]*ShortURL `json:"urls"` } +func (list *URLList) GetShortUrls() map[string]*ShortURL { + return list.ShortURLs +} + // A ShortUrl is a structure which captures the type ShortURL struct { URL string `json:"url"` - Code string `json:"short_code"` - AccessCount int `json:"access_count"` + Code string `json:"code"` + AccessCount int `json:"accessed"` Created *time.Time `json:"created"` } diff --git a/services/shortener.go b/services/shortener.go index 773456e..e801e81 100644 --- a/services/shortener.go +++ b/services/shortener.go @@ -11,6 +11,7 @@ import ( // Shortener provides a collection of web handlers type WebShortener interface { Index(http.ResponseWriter, *http.Request) + GetRedirectUrl(string) (string, error) } // Shortener is a service which provides a web accessible URL shortener. diff --git a/usecases/shorturl_interactor.go b/usecases/shorturl_interactor.go index 5916cfd..e8a1cd6 100644 --- a/usecases/shorturl_interactor.go +++ b/usecases/shorturl_interactor.go @@ -1,13 +1,25 @@ package usecases -import ( - "github.com/BelfastGophers/gophurl/models" -) +import "github.com/BelfastGophers/gophurl/models" type ShortURLInteractor struct { URLRepository models.ShortURLRepository } -func (interactor *ShortURLInteractor) GetAll() ([]*models.ShortURL, error) { - return []*models.ShortURL{}, nil +func (interactor *ShortURLInteractor) GetAll() (map[string]*models.ShortURL, error) { + return interactor.URLRepository.All() +} + +func (interactor *ShortURLInteractor) GetUrl(code string) (string, error) { + result, err := interactor.URLRepository.Get(code) + + if err != nil { + return "", err + } + return result.URL, nil +} + +func (interactor *ShortURLInteractor) IncrementAccessCount(code string) error { + err := interactor.URLRepository.IncrementAccessed(code) + return err }