From cfe88cbdcea360ebf884053e1815fd29653847b1 Mon Sep 17 00:00:00 2001 From: Francesc Campoy Flores Date: Fri, 22 Sep 2017 18:29:02 -0700 Subject: [PATCH 1/8] remove unnecessary channel for graceful shutdown --- main.go | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/main.go b/main.go index 1a6d50f..90e9132 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "log" "net/http" @@ -34,46 +35,31 @@ func main() { http.Handle("/", handlers.RedirectHandler(storage)) http.Handle("/info/", handlers.DecodeHandler(storage)) - // Graceful shutdown - sigquit := make(chan os.Signal, 1) - signal.Notify(sigquit, os.Interrupt, os.Kill) - - // Wait signal - close := make(chan bool, 1) - // Create a server server := &http.Server{Addr: fmt.Sprintf("%s:%s", config.Server.Host, config.Server.Port)} - // Start server - go func() { - log.Printf("Starting HTTP Server. Listening at %q", server.Addr) - if err := server.ListenAndServe(); err != nil { - if err := server.ListenAndServe(); err != nil { - if err != http.ErrServerClosed { - log.Println(err.Error()) - } else { - log.Println("Server closed!") - } - close <- true - } - } - - }() - // Check for a closing signal go func() { + // Graceful shutdown + sigquit := make(chan os.Signal, 1) + signal.Notify(sigquit, os.Interrupt, os.Kill) + sig := <-sigquit log.Printf("caught sig: %+v", sig) log.Printf("Gracefully shutting down server...") - if err := server.Shutdown(nil); err != nil { - log.Println("Unable to shut down server: " + err.Error()) - close <- true + if err := server.Shutdown(context.Background()); err != nil { + log.Printf("Unable to shut down server: %v", err) } else { log.Println("Server stopped") - close <- true } }() - <-close + // Start server + log.Printf("Starting HTTP Server. Listening at %q", server.Addr) + if err := server.ListenAndServe(); err != http.ErrServerClosed { + log.Printf("%v", err) + } else { + log.Println("Server closed!") + } } From d46abeda92cda35eebf8c50a9f29588da457aebf Mon Sep 17 00:00:00 2001 From: Francesc Campoy Flores Date: Fri, 22 Sep 2017 18:34:25 -0700 Subject: [PATCH 2/8] simplified config package --- config/config.go | 75 +++++++++++++++++------------------------------- main.go | 7 ++++- 2 files changed, 33 insertions(+), 49 deletions(-) diff --git a/config/config.go b/config/config.go index 8e52833..a376128 100644 --- a/config/config.go +++ b/config/config.go @@ -2,63 +2,42 @@ package config import ( "encoding/json" - "os" - "io" - "bytes" + "io/ioutil" ) +// Config contains the configuration of the url shortener. type Config struct { - Server Server `json:"server"` - Redis Redis `json:"redis"` - Postgres Postgres `json:"postgres"` - Options Options `json:"options"` + Server struct { + Host string `json:"host"` + Port string `json:"port"` + } `json:"server"` + Redis struct { + Host string `json:"host"` + Password string `json:"password"` + DB string `json:"db"` + } `json:"redis"` + Postgres struct { + Host string `json:"host"` + User string `json:"user"` + Password string `json:"password"` + DB string `json:"db"` + } `json:"postgres"` + Options struct { + Prefix string `json:"prefix"` + } `json:"options"` } -type Server struct { - Host string `json:"host"` - Port string `json:"port"` -} - -type Redis struct { - Host string `json:"host"` - Password string `json:"password"` - DB string `json:"db"` -} - -type Postgres struct { - Host string `json:"host"` - User string `json:"user"` - Password string `json:"password"` - DB string `json:"db"` -} - -type Options struct { - Prefix string `json:"prefix"` -} - -func ReadConfig() (*Config, error) { - var objectConfig *Config - var buf bytes.Buffer - - // open input file - file, err := os.Open("./config/config.json") +// FromFile returns a configuration parsed from the given file. +func FromFile(path string) (*Config, error) { + b, err := ioutil.ReadFile(path) if err != nil { return nil, err } - // close file - defer file.Close() - - // copy to buffer - _, err = io.Copy(&buf, file) - if err != nil { + var cfg Config + if err := json.Unmarshal(b, &cfg); err != nil { return nil, err } - // Unmarshal data - if err := json.Unmarshal(buf.Bytes(), &objectConfig); err != nil { - return nil, err - } - - return objectConfig, nil -} \ No newline at end of file + return &cfg, nil +} diff --git a/main.go b/main.go index 90e9132..f56b1f9 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "flag" "fmt" "log" "net/http" @@ -14,11 +15,15 @@ import ( ) func main() { + configPath := flag.String("config", "./config/config.json", "path of the config file") + + flag.Parse() + // Set use storage, select [Postgres, Filesystem, Redis ...] storage := &storages.Postgres{} // Read config - config, err := config.ReadConfig() + config, err := config.FromFile(*configPath) if err != nil { log.Fatal(err) } From c4270c5fcae70d596d05977286e3a2ce61e03fb1 Mon Sep 17 00:00:00 2001 From: Francesc Campoy Flores Date: Fri, 22 Sep 2017 18:36:11 -0700 Subject: [PATCH 3/8] pass prefix to handler --- handlers/base.go | 19 ------------------- handlers/handlers.go | 2 +- main.go | 2 +- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/handlers/base.go b/handlers/base.go index 38f622d..fdfc7bc 100644 --- a/handlers/base.go +++ b/handlers/base.go @@ -2,8 +2,6 @@ package handlers import ( "encoding/json" - "github.com/douglasmakey/ursho/config" - "log" "net/http" ) @@ -12,23 +10,6 @@ type Response struct { Data interface{} `json:"response"` } - -var prefix string - -func init() { - c, err := config.ReadConfig() - if err != nil { - log.Fatal(err) - } - - if c.Options.Prefix == "" { - prefix = "" - } else { - prefix = c.Options.Prefix - } -} - - func createResponse(w http.ResponseWriter, r Response) { d, err := json.Marshal(r) if err != nil { diff --git a/handlers/handlers.go b/handlers/handlers.go index aee4070..151ce8a 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -13,7 +13,7 @@ type bodyRequest struct { URL string } -func EncodeHandler(storage storages.IFStorage) http.Handler { +func EncodeHandler(prefix string, storage storages.IFStorage) http.Handler { handleFunc := func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { diff --git a/main.go b/main.go index f56b1f9..8ca5da6 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func main() { defer storage.Close() // Handlers - http.Handle("/encode/", handlers.EncodeHandler(storage)) + http.Handle("/encode/", handlers.EncodeHandler(config.Options.Prefix, storage)) http.Handle("/", handlers.RedirectHandler(storage)) http.Handle("/info/", handlers.DecodeHandler(storage)) From 355cceaba33935a34960a1574eeb6bb4731bcffd Mon Sep 17 00:00:00 2001 From: Francesc Campoy Flores Date: Fri, 22 Sep 2017 18:47:19 -0700 Subject: [PATCH 4/8] encapsulate handlers package better --- handlers/handlers.go | 147 +++++++++++++++++++++---------------------- main.go | 6 +- 2 files changed, 72 insertions(+), 81 deletions(-) diff --git a/handlers/handlers.go b/handlers/handlers.go index 151ce8a..c80f128 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -9,100 +9,95 @@ import ( "github.com/douglasmakey/ursho/storages" ) +func New(prefix string, storage storages.IFStorage) http.Handler { + mux := http.NewServeMux() + h := handler{prefix, storage} + mux.HandleFunc("/encode/", h.encode) + mux.HandleFunc("/", h.redirect) + mux.HandleFunc("/info/", h.decode) + return mux +} + type bodyRequest struct { URL string } -func EncodeHandler(prefix string, storage storages.IFStorage) http.Handler { - handleFunc := func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - - w.Header().Set("Content-Type", "application/json") - - var b bodyRequest - if err := json.NewDecoder(r.Body).Decode(&b); err != nil { - w.WriteHeader(http.StatusInternalServerError) - e := Response{Data: "Unable to decode JSON request body: " + err.Error(), Success: false} - createResponse(w, e) - return - } - - b.URL = strings.TrimSpace(b.URL) - - if b.URL == "" { - w.WriteHeader(http.StatusBadRequest) - e := Response{Data: "URL is Empty", Success: false} - createResponse(w, e) - return - } - - c, err := storage.Save(b.URL) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - e := Response{Data: err.Error(), Success: false} - createResponse(w, e) - return - } - - response := Response{Data: prefix + c, Success: true} - createResponse(w, response) - - } else { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - } - - return http.HandlerFunc(handleFunc) +type handler struct { + prefix string + storage storages.IFStorage } -func DecodeHandler(storage storages.IFStorage) http.Handler { - handleFunc := func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - code := r.URL.Path[len("/info/"):] +func (h handler) encode(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } - model, err := storage.LoadInfo(code) - if err != nil { - w.WriteHeader(http.StatusNotFound) - e := Response{Data: "URL Not Found", Success: false} - createResponse(w, e) - return - } + w.Header().Set("Content-Type", "application/json") - response := Response{Data: model, Success: true} - createResponse(w, response) + var b bodyRequest + if err := json.NewDecoder(r.Body).Decode(&b); err != nil { + w.WriteHeader(http.StatusInternalServerError) + e := Response{Data: "Unable to decode JSON request body: " + err.Error(), Success: false} + createResponse(w, e) + return + } - } else { + b.URL = strings.TrimSpace(b.URL) - w.WriteHeader(http.StatusMethodNotAllowed) - return - } + if b.URL == "" { + w.WriteHeader(http.StatusBadRequest) + e := Response{Data: "URL is Empty", Success: false} + createResponse(w, e) + return } - return http.HandlerFunc(handleFunc) + c, err := h.storage.Save(b.URL) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + e := Response{Data: err.Error(), Success: false} + createResponse(w, e) + return + } + + response := Response{Data: h.prefix + c, Success: true} + createResponse(w, response) } -func RedirectHandler(storage storages.IFStorage) http.Handler { - handleFunc := func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - code := r.URL.Path[len("/"):] +func (h handler) decode(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + w.Header().Set("Content-Type", "application/json") + code := r.URL.Path[len("/info/"):] - model, err := storage.Load(code) - if err != nil { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte("URL Not Found")) - return - } + model, err := h.storage.LoadInfo(code) + if err != nil { + w.WriteHeader(http.StatusNotFound) + e := Response{Data: "URL Not Found", Success: false} + createResponse(w, e) + return + } - http.Redirect(w, r, string(model.Url), 301) + response := Response{Data: model, Success: true} + createResponse(w, response) +} - } else { +func (h handler) redirect(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + code := r.URL.Path[len("/"):] - w.WriteHeader(http.StatusMethodNotAllowed) - return - } + model, err := h.storage.Load(code) + if err != nil { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("URL Not Found")) + return } - return http.HandlerFunc(handleFunc) + http.Redirect(w, r, string(model.Url), 301) } diff --git a/main.go b/main.go index 8ca5da6..1c2f10b 100644 --- a/main.go +++ b/main.go @@ -35,12 +35,8 @@ func main() { // Defers defer storage.Close() - // Handlers - http.Handle("/encode/", handlers.EncodeHandler(config.Options.Prefix, storage)) - http.Handle("/", handlers.RedirectHandler(storage)) - http.Handle("/info/", handlers.DecodeHandler(storage)) - // Create a server + http.Handle("/", handlers.New(config.Options.Prefix, storage)) server := &http.Server{Addr: fmt.Sprintf("%s:%s", config.Server.Host, config.Server.Port)} // Check for a closing signal From a2dcfb1c1765aa2a5db84c045bccb13c5023c08d Mon Sep 17 00:00:00 2001 From: Francesc Campoy Flores Date: Fri, 22 Sep 2017 19:02:43 -0700 Subject: [PATCH 5/8] refactoring of the handlers package --- handlers/base.go | 20 ------------ handlers/handlers.go | 78 ++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 59 deletions(-) delete mode 100644 handlers/base.go diff --git a/handlers/base.go b/handlers/base.go deleted file mode 100644 index fdfc7bc..0000000 --- a/handlers/base.go +++ /dev/null @@ -1,20 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" -) - -type Response struct { - Success bool `json:"success"` - Data interface{} `json:"response"` -} - -func createResponse(w http.ResponseWriter, r Response) { - d, err := json.Marshal(r) - if err != nil { - panic(err) - } - - w.Write(d) -} diff --git a/handlers/handlers.go b/handlers/handlers.go index c80f128..cfd37a4 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -3,23 +3,28 @@ package handlers import ( "encoding/json" + "fmt" + "io" + "log" "net/http" "strings" "github.com/douglasmakey/ursho/storages" ) +// New returns an http handler for the url shortener. func New(prefix string, storage storages.IFStorage) http.Handler { mux := http.NewServeMux() h := handler{prefix, storage} - mux.HandleFunc("/encode/", h.encode) + mux.HandleFunc("/encode/", responseHandler(h.encode)) mux.HandleFunc("/", h.redirect) - mux.HandleFunc("/info/", h.decode) + mux.HandleFunc("/info/", responseHandler(h.decode)) return mux } -type bodyRequest struct { - URL string +type response struct { + Success bool `json:"success"` + Data interface{} `json:"response"` } type handler struct { @@ -27,62 +32,57 @@ type handler struct { storage storages.IFStorage } -func (h handler) encode(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - w.WriteHeader(http.StatusMethodNotAllowed) - return +func responseHandler(h func(io.Writer, *http.Request) (interface{}, int, error)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + data, status, err := h(w, r) + if err != nil { + data = err.Error() + } + w.WriteHeader(status) + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response{Data: data, Success: err == nil}) + if err != nil { + log.Printf("could not encode response to output: %v", err) + } } +} - w.Header().Set("Content-Type", "application/json") - - var b bodyRequest - if err := json.NewDecoder(r.Body).Decode(&b); err != nil { - w.WriteHeader(http.StatusInternalServerError) - e := Response{Data: "Unable to decode JSON request body: " + err.Error(), Success: false} - createResponse(w, e) - return +func (h handler) encode(w io.Writer, r *http.Request) (interface{}, int, error) { + if r.Method != http.MethodPost { + return nil, http.StatusMethodNotAllowed, fmt.Errorf("method %s not allowed", r.Method) } - b.URL = strings.TrimSpace(b.URL) + var input struct{ URL string } + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + return nil, http.StatusBadRequest, fmt.Errorf("Unable to decode JSON request body: %v", err) + } - if b.URL == "" { - w.WriteHeader(http.StatusBadRequest) - e := Response{Data: "URL is Empty", Success: false} - createResponse(w, e) - return + url := strings.TrimSpace(input.URL) + if url == "" { + return nil, http.StatusBadRequest, fmt.Errorf("URL is empty") } - c, err := h.storage.Save(b.URL) + c, err := h.storage.Save(url) if err != nil { - w.WriteHeader(http.StatusBadRequest) - e := Response{Data: err.Error(), Success: false} - createResponse(w, e) - return + return nil, http.StatusBadRequest, fmt.Errorf("Could not store in database: %v", err) } - response := Response{Data: h.prefix + c, Success: true} - createResponse(w, response) + return h.prefix + c, http.StatusCreated, nil } -func (h handler) decode(w http.ResponseWriter, r *http.Request) { +func (h handler) decode(w io.Writer, r *http.Request) (interface{}, int, error) { if r.Method != http.MethodGet { - w.WriteHeader(http.StatusMethodNotAllowed) - return + return nil, http.StatusMethodNotAllowed, fmt.Errorf("Method %s not allowed", r.Method) } - w.Header().Set("Content-Type", "application/json") code := r.URL.Path[len("/info/"):] model, err := h.storage.LoadInfo(code) if err != nil { - w.WriteHeader(http.StatusNotFound) - e := Response{Data: "URL Not Found", Success: false} - createResponse(w, e) - return + return nil, http.StatusNotFound, fmt.Errorf("URL not found") } - response := Response{Data: model, Success: true} - createResponse(w, response) + return model, http.StatusOK, nil } func (h handler) redirect(w http.ResponseWriter, r *http.Request) { From e68407891f781de19d9ccfbab0b1e389293fac75 Mon Sep 17 00:00:00 2001 From: Francesc Campoy Flores Date: Fri, 22 Sep 2017 19:04:29 -0700 Subject: [PATCH 6/8] use singular names for packages --- handlers/handlers.go => handler/handler.go | 8 ++++---- main.go | 8 ++++---- {storages => storage}/base.go | 2 +- {storages => storage}/postgres.go | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename handlers/handlers.go => handler/handler.go (94%) rename {storages => storage}/base.go (93%) rename {storages => storage}/postgres.go (99%) diff --git a/handlers/handlers.go b/handler/handler.go similarity index 94% rename from handlers/handlers.go rename to handler/handler.go index cfd37a4..deb27b1 100644 --- a/handlers/handlers.go +++ b/handler/handler.go @@ -1,5 +1,5 @@ // Package handlers provides HTTP request handlers. -package handlers +package handler import ( "encoding/json" @@ -9,11 +9,11 @@ import ( "net/http" "strings" - "github.com/douglasmakey/ursho/storages" + "github.com/douglasmakey/ursho/storage" ) // New returns an http handler for the url shortener. -func New(prefix string, storage storages.IFStorage) http.Handler { +func New(prefix string, storage storage.IFStorage) http.Handler { mux := http.NewServeMux() h := handler{prefix, storage} mux.HandleFunc("/encode/", responseHandler(h.encode)) @@ -29,7 +29,7 @@ type response struct { type handler struct { prefix string - storage storages.IFStorage + storage storage.IFStorage } func responseHandler(h func(io.Writer, *http.Request) (interface{}, int, error)) http.HandlerFunc { diff --git a/main.go b/main.go index 1c2f10b..b869c03 100644 --- a/main.go +++ b/main.go @@ -10,8 +10,8 @@ import ( "os/signal" "github.com/douglasmakey/ursho/config" - "github.com/douglasmakey/ursho/handlers" - "github.com/douglasmakey/ursho/storages" + "github.com/douglasmakey/ursho/handler" + "github.com/douglasmakey/ursho/storage" ) func main() { @@ -20,7 +20,7 @@ func main() { flag.Parse() // Set use storage, select [Postgres, Filesystem, Redis ...] - storage := &storages.Postgres{} + storage := &storage.Postgres{} // Read config config, err := config.FromFile(*configPath) @@ -36,7 +36,7 @@ func main() { defer storage.Close() // Create a server - http.Handle("/", handlers.New(config.Options.Prefix, storage)) + http.Handle("/", handler.New(config.Options.Prefix, storage)) server := &http.Server{Addr: fmt.Sprintf("%s:%s", config.Server.Host, config.Server.Port)} // Check for a closing signal diff --git a/storages/base.go b/storage/base.go similarity index 93% rename from storages/base.go rename to storage/base.go index be8225a..ab669fa 100644 --- a/storages/base.go +++ b/storage/base.go @@ -1,4 +1,4 @@ -package storages +package storage type IFStorage interface { Save(string) (string, error) diff --git a/storages/postgres.go b/storage/postgres.go similarity index 99% rename from storages/postgres.go rename to storage/postgres.go index 05b2a09..0499e22 100644 --- a/storages/postgres.go +++ b/storage/postgres.go @@ -1,4 +1,4 @@ -package storages +package storage import ( "database/sql" @@ -92,4 +92,4 @@ func (p *Postgres) LoadInfo(code string) (*Model, error) { func (p *Postgres) Close() { p.DB.Close() -} \ No newline at end of file +} From ce3b5fe980fdb1bf506fdb978f20ad08953369ec Mon Sep 17 00:00:00 2001 From: Francesc Campoy Flores Date: Fri, 22 Sep 2017 19:19:45 -0700 Subject: [PATCH 7/8] refactor storage package --- handler/handler.go | 6 +-- main.go | 17 +++---- storage/base.go | 14 ------ storage/postgres.go | 95 ------------------------------------ storage/postgres/postgres.go | 80 ++++++++++++++++++++++++++++++ storage/storage.go | 14 ++++++ 6 files changed, 104 insertions(+), 122 deletions(-) delete mode 100644 storage/base.go delete mode 100644 storage/postgres.go create mode 100644 storage/postgres/postgres.go create mode 100644 storage/storage.go diff --git a/handler/handler.go b/handler/handler.go index deb27b1..449f1f4 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -13,7 +13,7 @@ import ( ) // New returns an http handler for the url shortener. -func New(prefix string, storage storage.IFStorage) http.Handler { +func New(prefix string, storage storage.Service) http.Handler { mux := http.NewServeMux() h := handler{prefix, storage} mux.HandleFunc("/encode/", responseHandler(h.encode)) @@ -29,7 +29,7 @@ type response struct { type handler struct { prefix string - storage storage.IFStorage + storage storage.Service } func responseHandler(h func(io.Writer, *http.Request) (interface{}, int, error)) http.HandlerFunc { @@ -99,5 +99,5 @@ func (h handler) redirect(w http.ResponseWriter, r *http.Request) { return } - http.Redirect(w, r, string(model.Url), 301) + http.Redirect(w, r, string(model.URL), http.StatusMovedPermanently) } diff --git a/main.go b/main.go index b869c03..a128e71 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ import ( "github.com/douglasmakey/ursho/config" "github.com/douglasmakey/ursho/handler" - "github.com/douglasmakey/ursho/storage" + "github.com/douglasmakey/ursho/storage/postgres" ) func main() { @@ -19,24 +19,21 @@ func main() { flag.Parse() - // Set use storage, select [Postgres, Filesystem, Redis ...] - storage := &storage.Postgres{} - // Read config config, err := config.FromFile(*configPath) if err != nil { log.Fatal(err) } - // Init storage - if err = storage.Init(config); err != nil { + + // Set use storage, select [Postgres, Filesystem, Redis ...] + svc, err := postgres.New(config.Postgres.User, config.Postgres.Password, config.Postgres.DB) + if err != nil { log.Fatal(err) } - - // Defers - defer storage.Close() + defer svc.Close() // Create a server - http.Handle("/", handler.New(config.Options.Prefix, storage)) + http.Handle("/", handler.New(config.Options.Prefix, svc)) server := &http.Server{Addr: fmt.Sprintf("%s:%s", config.Server.Host, config.Server.Port)} // Check for a closing signal diff --git a/storage/base.go b/storage/base.go deleted file mode 100644 index ab669fa..0000000 --- a/storage/base.go +++ /dev/null @@ -1,14 +0,0 @@ -package storage - -type IFStorage interface { - Save(string) (string, error) - Load(string) (*Model, error) - LoadInfo(string) (*Model, error) - Close() -} - -type Model struct { - Url string `json:"url"` - Visited bool `json:"visited"` - Count int `json:"count"` -} diff --git a/storage/postgres.go b/storage/postgres.go deleted file mode 100644 index 0499e22..0000000 --- a/storage/postgres.go +++ /dev/null @@ -1,95 +0,0 @@ -package storage - -import ( - "database/sql" - "fmt" - - "github.com/douglasmakey/ursho/config" - _ "github.com/lib/pq" - - "github.com/douglasmakey/ursho/enconding" -) - -type Postgres struct { - DB *sql.DB - model Model -} - -func (p *Postgres) Init(config *config.Config) error { - // Coonect postgres - strConnect := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", - config.Postgres.User, config.Postgres.Password, config.Postgres.DB) - db, err := sql.Open("postgres", strConnect) - if err != nil { - return err - } - - // Ping to connection - err = db.Ping() - if err != nil { - return err - } - - // Create table if not exists - strQuery := "CREATE TABLE IF NOT EXISTS shortener (uid serial NOT NULL, url VARCHAR not NULL, " + - "visited boolean DEFAULT FALSE, count INTEGER DEFAULT 0);" - - _, err = db.Exec(strQuery) - if err != nil { - return err - } - // Set db in Model - p.DB = db - - return nil -} - -func (p *Postgres) Save(url string) (string, error) { - - var lastInsertId int - err := p.DB.QueryRow("INSERT INTO shortener(url,visited,count) VALUES($1,$2,$3) returning uid;", url, false, 0).Scan(&lastInsertId) - if err != nil { - return "", err - } - fmt.Println("last inserted id =", lastInsertId) - - return enconding.Encode(lastInsertId), nil -} - -func (p *Postgres) Load(code string) (*Model, error) { - // Decode code - decodeID := enconding.Decode(code) - - // Query select - err := p.DB.QueryRow("SELECT url, visited, count FROM shortener where uid=$1 limit 1", - decodeID).Scan(&p.model.Url, &p.model.Visited, &p.model.Count) - if err != nil { - return nil, err - } - - // Query update - stmt, err := p.DB.Prepare("update shortener set visited=$1, count=$2 where uid=$3") - if err != nil { - return nil, err - } - _, err = stmt.Exec(true, p.model.Count+1, decodeID) - return &p.model, err -} - -func (p *Postgres) LoadInfo(code string) (*Model, error) { - // Decode code - decodeID := enconding.Decode(code) - - // Query select - err := p.DB.QueryRow("SELECT url, visited, count FROM shortener where uid=$1 limit 1", - decodeID).Scan(&p.model.Url, &p.model.Visited, &p.model.Count) - if err != nil { - return nil, err - } - - return &p.model, err -} - -func (p *Postgres) Close() { - p.DB.Close() -} diff --git a/storage/postgres/postgres.go b/storage/postgres/postgres.go new file mode 100644 index 0000000..77934da --- /dev/null +++ b/storage/postgres/postgres.go @@ -0,0 +1,80 @@ +package postgres + +import ( + "database/sql" + "fmt" + + // This loads the postgres drivers. + _ "github.com/lib/pq" + + "github.com/douglasmakey/ursho/enconding" + "github.com/douglasmakey/ursho/storage" +) + +// New returns a postgres backed storage service. +func New(user, password, dbName string) (storage.Service, error) { + // Coonect postgres + connect := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", + user, password, dbName) + db, err := sql.Open("postgres", connect) + if err != nil { + return nil, err + } + + // Ping to connection + err = db.Ping() + if err != nil { + return nil, err + } + + // Create table if not exists + strQuery := "CREATE TABLE IF NOT EXISTS shortener (uid serial NOT NULL, url VARCHAR not NULL, " + + "visited boolean DEFAULT FALSE, count INTEGER DEFAULT 0);" + + _, err = db.Exec(strQuery) + if err != nil { + return nil, err + } + return &postgres{db}, nil +} + +type postgres struct{ db *sql.DB } + +func (p *postgres) Save(url string) (string, error) { + var id int + err := p.db.QueryRow("INSERT INTO shortener(url,visited,count) VALUES($1,$2,$3) returning uid;", url, false, 0).Scan(&id) + if err != nil { + return "", err + } + return enconding.Encode(id), nil +} + +func (p *postgres) Load(code string) (*storage.Item, error) { + id := enconding.Decode(code) + + item, err := p.LoadInfo(code) + if err != nil { + return nil, err + } + + _, err = p.db.Exec("update shortener set visited=$1, count=$2 where uid=$3", true, item.Count+1, id) + if err != nil { + return nil, err + } + return item, nil +} + +func (p *postgres) LoadInfo(code string) (*storage.Item, error) { + id := enconding.Decode(code) + + var item storage.Item + err := p.db.QueryRow("SELECT url, visited, count FROM shortener where uid=$1 limit 1", id). + Scan(&item.URL, &item.Visited, &item.Count) + if err != nil { + return nil, err + } + + return &item, nil +} + +func (p *postgres) Close() error { return p.db.Close() } diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000..a761561 --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,14 @@ +package storage + +type Service interface { + Save(string) (string, error) + Load(string) (*Item, error) + LoadInfo(string) (*Item, error) + Close() error +} + +type Item struct { + URL string `json:"url"` + Visited bool `json:"visited"` + Count int `json:"count"` +} From 5c744cfa534b9a978e246b660926e5c8d4db856c Mon Sep 17 00:00:00 2001 From: Francesc Campoy Flores Date: Sat, 23 Sep 2017 17:10:37 -0700 Subject: [PATCH 8/8] simplify encoding and rename to base62 --- base62/base62.go | 38 ++++++++++++++++++++++++++ enconding/base62.go | 52 ------------------------------------ storage/postgres/postgres.go | 18 ++++++++----- 3 files changed, 50 insertions(+), 58 deletions(-) create mode 100644 base62/base62.go delete mode 100644 enconding/base62.go diff --git a/base62/base62.go b/base62/base62.go new file mode 100644 index 0000000..feb60bb --- /dev/null +++ b/base62/base62.go @@ -0,0 +1,38 @@ +package base62 + +import ( + "fmt" + "strings" +) + +// All characters +const ( + alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + length = int64(len(alphabet)) +) + +// Encode number to base62. +func Encode(n int64) string { + if n == 0 { + return string(alphabet[0]) + } + + s := "" + for ; n > 0; n = n / length { + s = string(alphabet[n%length]) + s + } + return s +} + +// Decode converts a base62 token to int. +func Decode(key string) (int64, error) { + var n int64 + for _, c := range []byte(key) { + i := strings.IndexByte(alphabet, c) + if i < 0 { + return 0, fmt.Errorf("unexpected character %c in base62 literal", c) + } + n = length*n + int64(i) + } + return n, nil +} diff --git a/enconding/base62.go b/enconding/base62.go deleted file mode 100644 index 1448fc2..0000000 --- a/enconding/base62.go +++ /dev/null @@ -1,52 +0,0 @@ -package enconding - -import ( - "bytes" - "math" -) - -// All characters -const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - -// Convert number to base62 -func Encode(n int) string { - if n == 0 { - return string(alphabet[0]) - } - - chars := make([]byte, 0) - - length := len(alphabet) - - for n > 0 { - r := n / length - remainder := n % length - chars = append(chars, alphabet[remainder]) - n = r - } - - for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 { - chars[i], chars[j] = chars[j], chars[i] - } - - return string(chars) -} - -// convert base62 token to int -func Decode(t string) int { - n := 0 - idx := 0.0 - chars := []byte(alphabet) - - charsLength := float64(len(chars)) - tokenLength := float64(len(t)) - - for _, c := range []byte(t) { - power := tokenLength - (idx + 1) - ind := bytes.IndexByte(chars, c) - n += ind * int(math.Pow(charsLength, power)) - idx++ - } - - return n -} diff --git a/storage/postgres/postgres.go b/storage/postgres/postgres.go index 77934da..4392999 100644 --- a/storage/postgres/postgres.go +++ b/storage/postgres/postgres.go @@ -7,7 +7,7 @@ import ( // This loads the postgres drivers. _ "github.com/lib/pq" - "github.com/douglasmakey/ursho/enconding" + "github.com/douglasmakey/ursho/base62" "github.com/douglasmakey/ursho/storage" ) @@ -41,16 +41,19 @@ func New(user, password, dbName string) (storage.Service, error) { type postgres struct{ db *sql.DB } func (p *postgres) Save(url string) (string, error) { - var id int + var id int64 err := p.db.QueryRow("INSERT INTO shortener(url,visited,count) VALUES($1,$2,$3) returning uid;", url, false, 0).Scan(&id) if err != nil { return "", err } - return enconding.Encode(id), nil + return base62.Encode(id), nil } func (p *postgres) Load(code string) (*storage.Item, error) { - id := enconding.Decode(code) + id, err := base62.Decode(code) + if err != nil { + return nil, err + } item, err := p.LoadInfo(code) if err != nil { @@ -65,10 +68,13 @@ func (p *postgres) Load(code string) (*storage.Item, error) { } func (p *postgres) LoadInfo(code string) (*storage.Item, error) { - id := enconding.Decode(code) + id, err := base62.Decode(code) + if err != nil { + return nil, err + } var item storage.Item - err := p.db.QueryRow("SELECT url, visited, count FROM shortener where uid=$1 limit 1", id). + err = p.db.QueryRow("SELECT url, visited, count FROM shortener where uid=$1 limit 1", id). Scan(&item.URL, &item.Visited, &item.Count) if err != nil { return nil, err