From 8ad6b6e6233d4d4b89acb3528b127bf082bf6dc7 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 9 May 2021 21:00:34 +0200 Subject: [PATCH] 09052021 --- example.http | 4 +- main.go | 10 +- {routers => modules}/config/config.go | 0 {routers => modules}/database/database.go | 10 +- {routers => modules}/debug/debug.go | 2 +- modules/mailer/mailer.go | 1 + routers/api/v1/user/user.go | 185 ++++++++++++++++++---- swagger.yaml | 20 +-- 8 files changed, 175 insertions(+), 57 deletions(-) rename {routers => modules}/config/config.go (100%) rename {routers => modules}/database/database.go (77%) rename {routers => modules}/debug/debug.go (75%) create mode 100644 modules/mailer/mailer.go diff --git a/example.http b/example.http index a553092..918c37a 100644 --- a/example.http +++ b/example.http @@ -9,7 +9,7 @@ POST http://localhost:3000/api/v1/users Content-Type: application/json { - "username": "alex", - "email": "restapi@roese.dev", + "username": "ben4", + "email": "ben4@roese.dev", "password": "teksmkamsdkasdmaskdmaskdm" } \ No newline at end of file diff --git a/main.go b/main.go index 9cc8ac5..5110b8d 100644 --- a/main.go +++ b/main.go @@ -22,23 +22,17 @@ package main import ( - "git.umbach.dev/app-idea/rest-api/routers/config" - "git.umbach.dev/app-idea/rest-api/routers/database" + "git.umbach.dev/app-idea/rest-api/modules/config" + "git.umbach.dev/app-idea/rest-api/modules/database" "git.umbach.dev/app-idea/rest-api/routers/router" _ "github.com/go-sql-driver/mysql" "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" ) func main() { app := fiber.New() - app.Use(cors.New(cors.Config{ - AllowOrigins: "*", - AllowHeaders: "Origin, Content-Type, Accept", - })) - router.SetupRoutes(app) config.LoadConfig() diff --git a/routers/config/config.go b/modules/config/config.go similarity index 100% rename from routers/config/config.go rename to modules/config/config.go diff --git a/routers/database/database.go b/modules/database/database.go similarity index 77% rename from routers/database/database.go rename to modules/database/database.go index 178c5b8..0aa64dc 100644 --- a/routers/database/database.go +++ b/modules/database/database.go @@ -5,7 +5,8 @@ import ( "errors" "fmt" - "git.umbach.dev/app-idea/rest-api/routers/config" + "git.umbach.dev/app-idea/rest-api/modules/config" + "git.umbach.dev/app-idea/rest-api/modules/debug" ) func getConnectionString() string { @@ -15,10 +16,11 @@ func getConnectionString() string { } func GetDatabase() (*sql.DB, error) { - db, err := sql.Open("mysql", getConnectionString()) + db, _ := sql.Open("mysql", getConnectionString()) - if err != nil { - return db, errors.New("db error") + if err := db.Ping(); err != nil { + debug.Msg("DB Connection error:", err.Error()) + return nil, errors.New(err.Error()) } return db, nil diff --git a/routers/debug/debug.go b/modules/debug/debug.go similarity index 75% rename from routers/debug/debug.go rename to modules/debug/debug.go index 94fb6e5..57f3962 100644 --- a/routers/debug/debug.go +++ b/modules/debug/debug.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "git.umbach.dev/app-idea/rest-api/routers/config" + "git.umbach.dev/app-idea/rest-api/modules/config" ) func Msg(msg ...interface{}) { diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go new file mode 100644 index 0000000..1c651e7 --- /dev/null +++ b/modules/mailer/mailer.go @@ -0,0 +1 @@ +package mailer diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 4b2466c..e3fb4c3 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -1,19 +1,23 @@ package user import ( + "database/sql" "fmt" "math/rand" + "regexp" "strings" "time" - "git.umbach.dev/app-idea/rest-api/routers/database" - "git.umbach.dev/app-idea/rest-api/routers/debug" + "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" "golang.org/x/crypto/bcrypt" ) -// NewUser +//err = bcrypt.CompareHashAndPassword(hashedPassword, []byte("hello wolrd")) +//fmt.Println(err) + func NewUser(c *fiber.Ctx) error { // swagger:operation POST /users user usersNewUser // --- @@ -28,25 +32,33 @@ func NewUser(c *fiber.Ctx) error { // required: true // - name: email // in: query - // description: email of the user (length max 200) + // description: email of the user (length 3-254) // type: string // required: true // - name: password // in: query - // description: password of the user (length 6-256) + // description: password of the user (length 6-250) // type: string // required: true + // - name: hashtag + // in: query + // description: hashtag of the client (length 1-6, UPPERCASE) + // type: string + // - name: avatar_url + // in: query + // description: avatar url of the client + // type: string + // - name: location + // in: query + // description: location of the client (length: 1-20) (for example: Frankfurt) + // type: string // responses: - // '200': + // '201': // description: user created - // schema: - // type: object - // example: - // user_hastag: XVLBZG - // default: - // description: unexpected error - // schema: - // "$ref": "#/definitions/errorModel" + // '400': + // description: format is not correct + // '422': + // description: username or/and email already already assigned type LoginInput struct { Username string `json:"username"` Email string `json:"email"` @@ -55,17 +67,24 @@ func NewUser(c *fiber.Ctx) error { var input LoginInput - // check if given request is complete if err := c.BodyParser(&input); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "error", "message": "Error on login request", "data": err}) + return c.SendStatus(fiber.StatusBadRequest) } - // check in database if username and email is available + if !isValid(input.Username, 3, 30) || !isEmailValid(input.Email) || !isValid(input.Password, 6, 250) { + return c.SendStatus(fiber.StatusForbidden) + } db, err := database.GetDatabase() if db == nil || err != nil { - return c.SendString(err.Error()) + return c.SendStatus(fiber.StatusInternalServerError) + } + + defer db.Close() + + if !isValueAvailable(db, "email", input.Email) { + return c.SendStatus(fiber.StatusUnprocessableEntity) } password := []byte(input.Password) @@ -73,38 +92,139 @@ func NewUser(c *fiber.Ctx) error { hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) if err != nil { - panic(err) + debug.Msg("bcrypt password error", err) + return c.SendStatus(fiber.StatusInternalServerError) } - //err = bcrypt.CompareHashAndPassword(hashedPassword, []byte("hello wolrd")) - //fmt.Println(err) - user_id := strings.Replace(uuid.New().String(), "-", "", -1) - user_hashtag := RandomHashtag(6) + user_hashtag := RandomHashtag(db, 6) created := time.Now().Format("2006-02-01 15:04:05") - sqlStatement, err := db.Prepare("INSERT INTO users (user_id, user_hashtag, username, email, password, created) VALUES (?, ?, ?, ?, ?, ?);") - sqlStatement.Exec(user_id, user_hashtag, input.Username, input.Email, hashedPassword, created) + 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) - db.Close() + stmt.Close() debug.Msg("user created", user_id, user_hashtag, input.Username, input.Email) - return c.JSON(fiber.Map{"status": 1, "user_hashtag": user_hashtag}) + return c.SendStatus(fiber.StatusCreated) } -func RandomHashtag(n int) string { +func RandomHashtag(db *sql.DB, n int) string { var letters = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") s := make([]rune, n) + c := make(chan bool) - for i := range s { - s[i] = letters[rand.Intn(len(letters))] + for { + // TODO: randomness is very bad -> always the same + for i := range s { + s[i] = letters[rand.Intn(len(letters))] + } + + fmt.Println(string(s)) + + 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) + + if err == sql.ErrNoRows { + c <- true + } else { + c <- false + } + }() + + msg := <-c + + fmt.Println("msg", msg) + + if msg { + break + } } - //fmt.Println(s) - + fmt.Println("r string", string(s)) return string(s) + /* + err := db.QueryRow("SELECT user_hashtag FROM users WHERE user_hashtag = ?", s).Scan(&s) + + if err == sql.ErrNoRows { + return true + } + return false + + return string(s) */ +} + +func isValid(s string, min int, max int) bool { + if len(s) < min || len(s) > max { + return false + } + return true +} + +var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") + +func isEmailValid(e string) bool { + if len(e) < 3 || len(e) > 254 { + return false + } + 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) + + 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 { @@ -127,6 +247,7 @@ func GetUsers(c *fiber.Ctx) error { ) rows, err := db.Query("SELECT username FROM users;") + fmt.Println("err", err) defer rows.Close() fmt.Println("reading data:") for rows.Next() { diff --git a/swagger.yaml b/swagger.yaml index a289c56..14e0f25 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -26,24 +26,24 @@ paths: name: email required: true type: string - - description: password of the user (length 6-256) + - description: password of the user (length 6-250) in: query name: password required: true type: string + - description: hashtag of the client (length 1-6, UPPERCASE) + in: query + name: hashtag + type: string produces: - application/json responses: - "200": + "201": description: user created - schema: - example: - user_hastag: XVLBZG - type: object - default: - description: unexpected error - schema: - $ref: '#/definitions/errorModel' + "400": + description: format is not correct + "422": + description: username or/and email already already assigned summary: Create new user tags: - user