From 87d4ab04908c12e6d4e5c5fc03d547d428761892 Mon Sep 17 00:00:00 2001 From: Philippe Caseiro Date: Wed, 4 Dec 2019 16:58:38 +0100 Subject: [PATCH] First commit --- cmd/cli/main.go | 60 ++++++++++++++++++++ cmd/server/main.go | 70 ++++++++++++++++++++++++ errors.go | 21 +++++++ namecheck.go | 19 +++++++ twitter/coverprofile.tmp | 22 ++++++++ twitter/go.mod | 3 + twitter/twitter.go | 92 +++++++++++++++++++++++++++++++ twitter/twitter_test.go | 115 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 402 insertions(+) create mode 100644 cmd/cli/main.go create mode 100644 cmd/server/main.go create mode 100644 errors.go create mode 100644 namecheck.go create mode 100644 twitter/coverprofile.tmp create mode 100644 twitter/go.mod create mode 100644 twitter/twitter.go create mode 100644 twitter/twitter_test.go diff --git a/cmd/cli/main.go b/cmd/cli/main.go new file mode 100644 index 0000000..7c9b066 --- /dev/null +++ b/cmd/cli/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "os" + "sync" + + "forge.cadoles.com/pcaseiro/namecheck" + "forge.cadoles.com/pcaseiro/namecheck/twitter" +) + +func checkUsername(ch chan<- string, wg *sync.WaitGroup, sn namecheck.SocialNetwork, username string) (bool, error) { + defer wg.Done() + // s1 := rand.NewSource(time.Now().UnixNano()) + // time.Sleep(rand.New(s1).Intn(100)) + res, err := sn.IsAvailable(username) + if err != nil { + ch <- fmt.Sprintf("Error : {%v}\n", err) + return false, err + } + + if !res { + ch <- fmt.Sprintf("User %s is not available\n", username) + return false, nil + } + ch <- fmt.Sprintf("User %s is available\n", username) + return true, nil +} + +func main() { + var wg sync.WaitGroup + var socialNetworks []namecheck.SocialNetwork + + // Create a channel for result + ch := make(chan string, 20) + + // Create the "social networks" + for i := 0; i < 20; i++ { + socialNetworks = append(socialNetworks, &twitter.Twitter{}) + } + + // Start the routines for check + username := os.Args[1] + for _, sn := range socialNetworks { + wg.Add(1) + go checkUsername(ch, &wg, sn, username) + } + + // Wait in background + go func() { + wg.Wait() + close(ch) + }() + + // Print the result + for li := range ch { + fmt.Printf(li) + } + +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..41a5854 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "sync" + + "forge.cadoles.com/pcaseiro/namecheck" + "forge.cadoles.com/pcaseiro/namecheck/twitter" +) + +func checkUsername(ch chan<- string, wg *sync.WaitGroup, sn namecheck.SocialNetwork, username string) (bool, error) { + defer wg.Done() + // s1 := rand.NewSource(time.Now().UnixNano()) + // time.Sleep(rand.New(s1).Intn(100)) + res, err := sn.IsAvailable(username) + if err != nil { + ch <- fmt.Sprintf("Error : {%v} for %v\n", err, sn.String()) + return false, err + } + + if !res { + ch <- fmt.Sprintf("User %s is not available on %v\n", username, sn.String()) + return false, nil + } + ch <- fmt.Sprintf("User %s is available on %v\n", username, sn.String()) + return true, nil +} + +func msgHandler(w http.ResponseWriter, req *http.Request) { + var wg sync.WaitGroup + var socialNetworks []namecheck.SocialNetwork + + // Create a channel for result + ch := make(chan string, 20) + + // Create the "social networks" + for i := 0; i < 20; i++ { + socialNetworks = append(socialNetworks, &twitter.Twitter{}) + } + + // Start the routines for check + urlQuery := req.URL.Query() //os.Args[1] + usernames := urlQuery["u"] + for _, user := range usernames { + for _, sn := range socialNetworks { + wg.Add(1) + go checkUsername(ch, &wg, sn, user) + } + } + + // Wait in background + go func() { + wg.Wait() + close(ch) + }() + + // Print the result + for li := range ch { + io.WriteString(w, li) + } + +} + +func main() { + http.HandleFunc("/", msgHandler) + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..943b98c --- /dev/null +++ b/errors.go @@ -0,0 +1,21 @@ +package namecheck + +import "fmt" + +type ErrSocialNetwork interface { + Error() error + Unwrap() error +} + +type ErrUnknownAvailability struct { + Username string + Cause error +} + +func (e *ErrUnknownAvailability) Error() string { + return fmt.Sprintf("Error during check for username '%s'\n[%s]\n", e.Username, e.Cause) +} + +func (e *ErrUnknownAvailability) Unwrap() error { + return e.Cause +} diff --git a/namecheck.go b/namecheck.go new file mode 100644 index 0000000..e3d427d --- /dev/null +++ b/namecheck.go @@ -0,0 +1,19 @@ +package namecheck + +import ( + "fmt" +) + +type Validator interface { + IsValid(string) bool +} + +type Provider interface { + IsAvailable(string) (bool, error) +} + +type SocialNetwork interface { + fmt.Stringer + Validator + Provider +} diff --git a/twitter/coverprofile.tmp b/twitter/coverprofile.tmp new file mode 100644 index 0000000..ab59d07 --- /dev/null +++ b/twitter/coverprofile.tmp @@ -0,0 +1,22 @@ +mode: set +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:29.51,31.2 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:33.53,34.65 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:37.2,37.13 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:34.65,36.3 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:40.41,42.2 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:44.42,46.2 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:48.49,49.41 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:52.2,52.39 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:55.2,55.29 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:59.2,59.30 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:62.2,62.13 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:49.41,51.3 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:52.39,54.3 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:55.29,57.3 1 0 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:59.30,61.3 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:66.33,68.2 1 0 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:71.62,74.16 3 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:78.2,81.44 3 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:85.2,85.18 1 1 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:74.16,77.3 2 0 +forge.cadoles.com/pcaseiro/namecheck/twitter/twitter.go:81.44,84.3 2 1 diff --git a/twitter/go.mod b/twitter/go.mod new file mode 100644 index 0000000..f603c84 --- /dev/null +++ b/twitter/go.mod @@ -0,0 +1,3 @@ +module forge.cadoles.com/pcaseiro/namecheck + +go 1.13 diff --git a/twitter/twitter.go b/twitter/twitter.go new file mode 100644 index 0000000..5b374c4 --- /dev/null +++ b/twitter/twitter.go @@ -0,0 +1,92 @@ +package twitter + +import ( + "fmt" + "net/http" + "regexp" + "strings" + "unicode/utf8" + + "forge.cadoles.com/pcaseiro/namecheck" +) + +const ( + sizeMin = 1 + sizeMax = 15 + legalChars = "^[a-zA-Z0-9_]+$" + illegalPattern = "twitter" + twitterUrl = "https://twitter.com" +) + +type Twitter struct { + HttpClient http.Client + Responds bool +} + +var ( + legal = regexp.MustCompile(legalChars) + illegal = regexp.MustCompile(illegalPattern) +) + +func containsOnlyLegalChars(username string) bool { + return legal.MatchString(username) +} + +func containsNoIllegalPattern(username string) bool { + if strings.Contains(strings.ToLower(username), illegalPattern) { + return false + } + return true +} + +func isLongEnough(username string) bool { + return utf8.RuneCountInString(username) > sizeMin +} + +func isShortEnough(username string) bool { + return utf8.RuneCountInString(username) <= sizeMax +} + +func (t *Twitter) IsValid(username string) bool { + if !containsNoIllegalPattern(username) { + return false + } + if !containsOnlyLegalChars(username) { + return false + } + if !isLongEnough(username) { + return false + } + + if !isShortEnough(username) { + return false + } + return true +} + +// Name return a string with the name of the "Social Media" +func (t *Twitter) String() string { + return "Twitter" +} + +// IsAvailable check if a user name is valid and available on twitter +func (t *Twitter) IsAvailable(username string) (bool, error) { + resp := t.IsValid(username) + if resp { + url := fmt.Sprintf("%s/%s", twitterUrl, username) + resp, err := t.HttpClient.Get(url) + if err != nil { + t.Responds = false + nerr := &namecheck.ErrUnknownAvailability{Username: username, Cause: err} + return false, nerr + } + t.Responds = true + defer resp.Body.Close() + + if resp.StatusCode != http.StatusNotFound { + return false, nil + } + return true, nil + } + return false, fmt.Errorf("Username is not valid") +} diff --git a/twitter/twitter_test.go b/twitter/twitter_test.go new file mode 100644 index 0000000..6ebf0cd --- /dev/null +++ b/twitter/twitter_test.go @@ -0,0 +1,115 @@ +package twitter + +import "testing" + +func TestContainsOnlyLegalChars(t *testing.T) { + goodUsername := "cadoles" + badUsername := "cadoles!" + + if !containsOnlyLegalChars(goodUsername) { + t.Errorf("The user is valid but containsOnlyLegalChars says it's not") + } + + if containsOnlyLegalChars(badUsername) { + t.Errorf("The user is bad but containsOnlyLegalChars says it's OK") + } +} + +func TestContainsNoIllegalPattern(t *testing.T) { + goodUsername := "puppetmaster" + if !containsNoIllegalPattern(goodUsername) { + t.Errorf("The username is valid but containsNoIllegalPattern says it's not") + } + + badUsername := "twitterPuppetMaster" + if containsNoIllegalPattern(badUsername) { + t.Errorf("The username is bad but containsNoIllegalPattern says it's OK") + } + +} + +func TestIsLongEnough(t *testing.T) { + goodUsername := "cadoles_test" + if !isLongEnough(goodUsername) { + t.Errorf("The username is long enough but isLongEnough says it's not") + } + + badUsername := "" + if isLongEnough(badUsername) { + t.Errorf("The username is not long enought but isLongEnough says it's OK") + } +} + +func TestIsShortEnough(t *testing.T) { + goodUsername := "cadoles_test" + if !isShortEnough(goodUsername) { + t.Errorf("The username is short enough but isShortEnough says it's not !") + } + + badUsername := "cadoles_cadoles_cadoles_cadoles_cadoles" + if isShortEnough(badUsername) { + t.Errorf("The username is not short enough but isShortEnough says it's OK !") + } +} + +func TestIsValid(t *testing.T) { + var tw Twitter + goodUsername := "cadoles" + if !tw.IsValid(goodUsername) { + t.Errorf("The username is valid and isValid says it's not !") + } +} + +func TestIsToShort(t *testing.T) { + var tw Twitter + toShort := "" + if tw.IsValid(toShort) { + t.Errorf("The is to short but isValid says it's OK !") + } + +} + +func TestIsToLong(t *testing.T) { + var tw Twitter + toLong := "cadoles_cadoles_cadoles_cadoles_cadoles_cadoles" + if tw.IsValid(toLong) { + t.Errorf("The username is to long but isValid says it's OK !") + } +} + +func TestContainsBadPattern(t *testing.T) { + var tw Twitter + twitterBad := "twittercadoles" + if tw.IsValid(twitterBad) { + t.Errorf("The username contains twitter but isValid says it's OK !") + } + +} + +func TestTakenUsername(t *testing.T) { + var tw Twitter + username := "puppetmaster" + res, err := tw.IsAvailable(username) + if err == nil { + t.Errorf("The user is supposed to be taken ! [%s]", err) + } + + if res { + t.Errorf("The user is not supposed to be available !") + } + +} + +func TestFreeUsername(t *testing.T) { + var tw Twitter + username := "caseiro23" + res, err := tw.IsAvailable(username) + if err != nil { + t.Errorf("The user is supposed to be free ! [%s]", err) + } + + if res == false { + t.Errorf("The user is supposed to be available !") + } + +}