diff --git a/api.go b/api.go new file mode 100644 index 0000000..ce07eb2 --- /dev/null +++ b/api.go @@ -0,0 +1,18 @@ +package main + +import ( + "net/http" +) + +type response struct { + Action string `json:"action"` + Status string `json:"status_code"` + Result interface{} `json:"result"` +} + +// GET: http://example.com/api/action/shorten?key=API_KEY_HERE&url=https://google.com&custom_ending=CUSTOM_ENDING +// Response: {"action": "shorten","result": "https://example.com/5kq"} +func APIRequest(w http.ResponseWriter, r *http.Request) { + + +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..65da9dd --- /dev/null +++ b/config.go @@ -0,0 +1,64 @@ +package main + +import ( + "flag" + "github.com/caarlos0/env" + "os" +) + +func DefaultConfig() Config { + return Config{ + DB: "./test.db", + APIkey: "thisIsNotASecretTokenNow", + Host: "localhost", + Port: "6889", + Title: "Sgot", + } +} + +type Config struct { + DB string `env:"SHRT_DB_FILE"` + APIkey string `env:"SHRT_API_KEY"` + Host string `env:"SHRT_HOST"` + Port string `env:"SHRT_PORT"` + Domain string `env:"SHRT_DOMAIN"` + Title string `env:"SHRT_TITLE"` +} + +func (c Config) HostPort() string { + return c.Host + ":" + c.Port +} + +func ReadConfig() *Config { + c, err := readConfig(flag.NewFlagSet(os.Args[0], flag.ExitOnError), os.Args[1:]) + if err != nil { + // sould never happen, because of flag default policy ExitOnError + panic(err) + } + return c +} + +func readConfig(f *flag.FlagSet, args []string) (*Config, error){ + config := DefaultConfig() + + // Environment variables + err := env.Parse(&config) + if err != nil { + return nil, err + } + + f.StringVar(&config.Host, "host", config.Host, "The host to listen on") + f.StringVar(&config.Port, "port", config.Port, "The port to listen on") + f.StringVar(&config.APIkey, "apikey", config.APIkey, "The Key to connect to the API") + f.StringVar(&config.DB, "db-file", config.DB, "The db file to use") + f.StringVar(&config.Domain, "domain", config.Domain, "The domain for redirect links") + f.StringVar(&config.Title, "title", config.Title, "The title on the Front") + + // Arguments variables + err = f.Parse(args) + if err != nil { + return nil, err + } + + return &config, err +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..1c19224 --- /dev/null +++ b/config_test.go @@ -0,0 +1,69 @@ +package main + +import ( + "testing" + "os" + "github.com/stretchr/testify/assert" + "flag" +) + +func TestConfig_ReadConfigDefaults(t *testing.T){ + originalArgs := os.Args + os.Args = []string{"shrt"} + defer func(){ os.Args = originalArgs }() + + d := DefaultConfig() + assert.Equal(t, &d, ReadConfig()) +} + +func TestConfig_ReadConfig(t *testing.T){ + input := []string{ + "--host=host", + "--port=port", + "--db-file=db", + "--title=title", + "--apikey=apikey", + "--domain=domain", + } + + expected := &Config{ + Host: "host", + Port: "port", + DB: "db", + Title: "title", + APIkey: "apikey", + Domain: "domain", + } + + cfg, err := readConfig(flag.NewFlagSet("", flag.ContinueOnError), input) + assert.NoError(t, err) + assert.Equal(t, expected, cfg) +} + +func TestConfig_ReadConfigFromEnv(t *testing.T) { + assert.NoError(t, os.Setenv("SHRT_DB_FILE", "db")) + defer os.Unsetenv("SHRT_DB_FILE") + assert.NoError(t, os.Setenv("SHRT_API_KEY", "apikey")) + defer os.Unsetenv("SHRT_API_KEY") + assert.NoError(t, os.Setenv("SHRT_HOST", "host")) + defer os.Unsetenv("SHRT_HOST") + assert.NoError(t, os.Setenv("SHRT_PORT", "port")) + defer os.Unsetenv("SHRT_PORT") + assert.NoError(t, os.Setenv("SHRT_DOMAIN", "domain")) + defer os.Unsetenv("SHRT_DOMAIN") + assert.NoError(t, os.Setenv("SHRT_TITLE", "title")) + defer os.Unsetenv("SHRT_TITLE") + + expected := &Config{ + Host: "host", + Port: "port", + DB: "db", + Title: "title", + APIkey: "apikey", + Domain: "domain", + } + + cfg, err := readConfig(flag.NewFlagSet("", flag.ContinueOnError), []string{}) + assert.NoError(t, err) + assert.Equal(t, expected, cfg) +} \ No newline at end of file diff --git a/handlers.go b/handlers.go index f430f0c..eaf5c51 100644 --- a/handlers.go +++ b/handlers.go @@ -6,7 +6,7 @@ import ( "fmt" "text/template" "net/url" - shorty "github.com/kreativmonkey/shrt/short" + "github.com/kreativmonkey/shrt/shrty" ) type Page struct { @@ -15,15 +15,13 @@ type Page struct { Body string } -type response struct { - Action string `json:"action"` - Result interface{} `json:"result"` -} - // Load the main Page with an Imput field for the URL to short. func index(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("template/index.gohtml") - t.Execute(w, Page{Title: "Shrt"}) + t.Execute(w, Page{ + Host: config.Host, + Title: config.Title, + }) } // Redirect to the URL behind the requested token. @@ -38,8 +36,8 @@ func redirect(w http.ResponseWriter, r *http.Request){ // func shorten(w http.ResponseWriter, r *http.Request){ - var test string - token, err := short.Add(r.FormValue("url"), &test) + var token string + err := short.Short(r.FormValue("url"), &token) if err != nil { panic(err) } @@ -55,13 +53,13 @@ func shortenJSON(w http.ResponseWriter, r *http.Request){ queryu, err := url.QueryUnescape(query.Get("url")) check(err) - var test string - err = short.Short(queryu, &test) + var token string + err = short.Short(queryu, &token) check(err) - respond := response{ + respond := response{3 Action: "shorten", - Result: r.Host + "/" + test, + Result: r.Host + "/" + token, } w.Header().Set("Content-Type", "application/json; charset=UTF-8") @@ -79,7 +77,7 @@ func lookupJSON(w http.ResponseWriter, r *http.Request){ queryu, err := url.QueryUnescape(query.Get("url_ending")) check(err) - var result shorty.Shrt + var result shrty.Data var respond response if ok := short.GetAPI(queryu, &result); ok { respond := response{ diff --git a/main.go b/main.go index c7152a2..a17df6e 100644 --- a/main.go +++ b/main.go @@ -3,23 +3,23 @@ package main import ( "net/http" - //"github.com/spf13/viper" - shorty "github.com/kreativmonkey/shrt/short" + shrty "github.com/kreativmonkey/shrt/shrty" "log" "fmt" ) const version = "0.02" -const port = "9090" -const db = "./test.db" var ( - short *shorty.Storage + short *shrty.Storage + + // Global configuration Variable + config = ReadConfig() ) func init() { var err error - short, err = shorty.Open(db) + short, err = shrty.Open(config.DB) if err != nil { panic(err) } @@ -56,10 +56,9 @@ func main() { http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("template/css")))) http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("template/img")))) */ - fmt.Printf("Shrt %s started on Port: %s \n", version, port) + fmt.Printf("Shrty %s is listen on Port: %s \n", version, config.Port) + fmt.Printf("For the API use following key: %s \n", version, config.APIkey) http.Handle("/", router) - log.Fatal(http.ListenAndServe(":"+port, router)) - + log.Fatal(http.ListenAndServe(":"+ config.Port, router)) } - diff --git a/short/shrt.go b/shrty/shrty.go similarity index 67% rename from short/shrt.go rename to shrty/shrty.go index e3c6596..c0ab890 100644 --- a/short/shrt.go +++ b/shrty/shrty.go @@ -1,4 +1,4 @@ -package shrt +package shrty import ( "bytes" @@ -13,13 +13,20 @@ import ( ) type Storage struct { - Token map[string]*Shrt `json:"token"` + Token map[string]*Data `json:"token"` Url map[string]string `json:"url"` } -type Shrt struct { +type Data struct { URL string `json:"url"` - Date string `json:"datum"` + URLFetched string `json:"url_fetched"` + CanonicalURL string `json:"canonical_url"` + OriginalURL string `json:"original_url"` + Domain string `json:"domain"` + FavIconLink string `json:"favicon_url"` + HTTPStatusCode string `json:"http_code"` + Category string `json:"category"` + Created string `json:"created_at"` Clicks int64 `json:"clicks"` } @@ -31,7 +38,7 @@ var ( // Open up func Open(path string) (*Storage, error) { - s := Storage{Token: make(map[string]*Shrt), Url: make(map[string]string)} + s := Storage{Token: make(map[string]*Data), Url: make(map[string]string)} // Open db ore create if not exist! if db, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644); err == nil { json.Unmarshal(StreamToByte(db), &s) @@ -64,9 +71,9 @@ func (s *Storage) Short(URL string, value *string) error { if _, ok := s.Token[hash[:hashShortestLen]]; !ok { token := hash[:hashShortestLen] t := time.Now() - s.Token[token] = &Shrt{ + s.Token[token] = &Data{ URL: URL, - Date: t.String(), + Created: t.String(), } s.Url[hash] = token *value = s.Url[hash] @@ -78,41 +85,6 @@ func (s *Storage) Short(URL string, value *string) error { return ErrCreateToken } -func (s *Storage) Add(URL string, value *string) (string, error) { - // Check if it is a valide Url found on: - // http://stackoverflow.com/questions/31480710/validate-url-with-standard-package-in-go - _, err := url.ParseRequestURI(URL) - if err != nil { - return "", ErrNoUrl - } - - // Create a sha256 Hash from the URL - hash := fmt.Sprintf("%x", sha256.Sum256([]byte(URL))) - - // Test if the URL alraedy exist and return the key - if val, ok := s.Url[hash]; ok { - return val, nil - } - // Iterate to the length of hash to get the shortest output - for hashShortestLen := 1; hashShortestLen <= 32; hashShortestLen++ { - // Test if the Token not exist and return the new generated token - if _, ok := s.Token[hash[:hashShortestLen]]; !ok { - token := hash[:hashShortestLen] - t := time.Now() - s.Token[token] = &Shrt{ - URL: URL, - Date: t.String(), - } - s.Url[hash] = token - *value = s.Url[hash] - s.Save() - return token, nil - } - } - - return "", ErrCreateToken -} - func (s *Storage) Remove(URL string) error { return nil } @@ -129,7 +101,7 @@ func (s *Storage) Get(token string) (string, bool) { } // Get returns the URL for the given token -func (s *Storage) GetAPI(token string, value *Shrt) bool { +func (s *Storage) GetAPI(token string, value *Data) bool { if shrt, ok := s.Token[token]; ok { *value = *shrt return true