diff --git a/example.http b/example.http index 918c37a..8edbe86 100644 --- a/example.http +++ b/example.http @@ -9,7 +9,7 @@ POST http://localhost:3000/api/v1/users Content-Type: application/json { - "username": "ben4", - "email": "ben4@roese.dev", + "username": "145", + "email": "145@roese.dev", "password": "teksmkamsdkasdmaskdmaskdm" } \ No newline at end of file diff --git a/go.mod b/go.mod index f7eb823..ba464da 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,10 @@ require ( github.com/gofiber/fiber/v2 v2.8.0 github.com/google/uuid v1.2.0 github.com/klauspost/compress v1.12.2 // indirect + github.com/satori/go.uuid v1.2.0 // indirect github.com/valyala/fasthttp v1.24.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + github.com/zhengxiaowai/shortuuid v0.0.0-20181208145701-0c38c76c12b3 // indirect golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 // indirect ) diff --git a/go.sum b/go.sum index 65c58e0..af9e684 100644 --- a/go.sum +++ b/go.sum @@ -424,6 +424,8 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -486,6 +488,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zhengxiaowai/shortuuid v0.0.0-20181208145701-0c38c76c12b3 h1:03r8Xo0XhTGIMuyJ9x3yrYCumC+T4ycXShfW+Le3Sts= +github.com/zhengxiaowai/shortuuid v0.0.0-20181208145701-0c38c76c12b3/go.mod h1:bxh5+Ghwiy6UJxDAPpoOB/L+dq0To9dky7x92nWeSKU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index e3fb4c3..308c6fb 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -3,15 +3,16 @@ package user import ( "database/sql" "fmt" - "math/rand" "regexp" "strings" "time" + "unicode" "git.umbach.dev/app-idea/rest-api/modules/database" "git.umbach.dev/app-idea/rest-api/modules/debug" "github.com/gofiber/fiber/v2" "github.com/google/uuid" + "github.com/zhengxiaowai/shortuuid" "golang.org/x/crypto/bcrypt" ) @@ -42,7 +43,7 @@ func NewUser(c *fiber.Ctx) error { // required: true // - name: hashtag // in: query - // description: hashtag of the client (length 1-6, UPPERCASE) + // description: hashtag of the client (length 2-6, UPPERCASE) // type: string // - name: avatar_url // in: query @@ -58,11 +59,12 @@ func NewUser(c *fiber.Ctx) error { // '400': // description: format is not correct // '422': - // description: username or/and email already already assigned + // description: username, email or/and hashtag already assigned type LoginInput struct { Username string `json:"username"` Email string `json:"email"` Password string `json:"password"` + Hashtag string `json:"hashtag"` } var input LoginInput @@ -83,12 +85,17 @@ func NewUser(c *fiber.Ctx) error { defer db.Close() - if !isValueAvailable(db, "email", input.Email) { + if !isEmailAvailable(db, input.Email) { + return c.SendStatus(fiber.StatusUnprocessableEntity) + } + + if input.Hashtag == "" { + input.Hashtag = RandomHashtag(db, 6) + } else if !isHashtagValid(db, input.Hashtag) { return c.SendStatus(fiber.StatusUnprocessableEntity) } password := []byte(input.Password) - hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) if err != nil { @@ -97,38 +104,29 @@ func NewUser(c *fiber.Ctx) error { } user_id := strings.Replace(uuid.New().String(), "-", "", -1) - user_hashtag := RandomHashtag(db, 6) created := time.Now().Format("2006-02-01 15:04:05") stmt, err := db.Prepare("INSERT INTO users (user_id, user_hashtag, username, email, password, created) VALUES (?, ?, ?, ?, ?, ?);") - stmt.Exec(user_id, user_hashtag, input.Username, input.Email, hashedPassword, created) + stmt.Exec(user_id, input.Hashtag, input.Username, input.Email, hashedPassword, created) stmt.Close() - debug.Msg("user created", user_id, user_hashtag, input.Username, input.Email) + debug.Msg("user created", user_id, input.Hashtag, input.Username, input.Email) return c.SendStatus(fiber.StatusCreated) } func RandomHashtag(db *sql.DB, n int) string { - var letters = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") - - s := make([]rune, n) c := make(chan bool) + var s string for { - // TODO: randomness is very bad -> always the same - for i := range s { - s[i] = letters[rand.Intn(len(letters))] - } - - fmt.Println(string(s)) + su := shortuuid.NewShortUUID() + su.SetAlphabet("ABCDEFGHJKLMNPQRSTUVWXYZ") + s = su.Random(n) go func() { - fmt.Println("conncur", string(s)) - err := db.QueryRow("SELECT user_hashtag FROM users WHERE user_hashtag = ?", string(s)).Scan(&s) - - fmt.Println("ErrNoRows?", err == sql.ErrNoRows) + err := db.QueryRow("SELECT user_hashtag FROM users WHERE user_hashtag = ?", s).Scan(&s) if err == sql.ErrNoRows { c <- true @@ -137,26 +135,36 @@ func RandomHashtag(db *sql.DB, n int) string { } }() - msg := <-c - - fmt.Println("msg", msg) - - if msg { + if msg := <-c; msg { break } } - fmt.Println("r string", string(s)) - return string(s) - /* - err := db.QueryRow("SELECT user_hashtag FROM users WHERE user_hashtag = ?", s).Scan(&s) + return s +} - if err == sql.ErrNoRows { - return true - } +func isHashtagValid(db *sql.DB, h string) bool { + if !isUpper(h) || len(h) < 2 || len(h) > 6 { return false + } - return string(s) */ + err := db.QueryRow("SELECT user_hashtag FROM users WHERE user_hashtag = ?", h).Scan(&h) + + fmt.Println("isHashtagValid", err == sql.ErrNoRows) + + if err == sql.ErrNoRows { + return true + } + return false +} + +func isUpper(s string) bool { + for _, r := range s { + if !unicode.IsUpper(r) && unicode.IsLetter(r) { + return false + } + } + return true } func isValid(s string, min int, max int) bool { @@ -175,56 +183,13 @@ func isEmailValid(e string) bool { return emailRegex.MatchString(e) } -func isValueAvailable(db *sql.DB, t string, v string) bool { - q := fmt.Sprintf("SELECT %s FROM users WHERE %s = ?", t, t) - - err := db.QueryRow(q, v).Scan(&v) - - fmt.Println(err == sql.ErrNoRows) +func isEmailAvailable(db *sql.DB, e string) bool { + err := db.QueryRow("SELECT email FROM users WHERE email = ?", e).Scan(&e) if err == sql.ErrNoRows { return true } return false - - /* - if email == "" { - return true - } - return false */ - - /* - ctx := context.Background() - - tsql := fmt.Sprintf("SELECT email FROM users WHERE email = '%s'", e) - - rows, err := db.QueryContext(ctx, tsql) - - if err != nil { - panic(err) - } - - fmt.Println("rows", rows) - - defer rows.Close() - - var count int - - for rows.Next() { - var email string - - err := rows.Scan(&email) - - if err != nil { - panic(err) - } - - fmt.Printf("Email: %s \n", email) - count++ - } - - fmt.Println(count) - */ } func GetUser(c *fiber.Ctx) error {