/* Available routes: /api/users OPTIONS Expose available actions /api/users GET Return user list, possibly filtered /api/users POST Create user /api/users/123 GET Return user 123 -> not listed in book /api/users/123 PUT Update user 123 /api/users/123 DELETE Delete user 123 POST fields: "user" -> name "mail" -> email "first" "last" */ package main import ( "database/sql" "encoding/json" "fmt" "log" "net/http" _ "github.com/go-sql-driver/mysql" "strconv" "strings" "github.com/gorilla/mux" ) var database *sql.DB type Users struct { Users []User `json:"users"` } type User struct { ID int `json:"id"` Name string `json:"username"` Email string `json:"mail"` First string `json:"first"` Last string `json:"last"` } type CreateResponse struct { Error string `json:"error"` ErrorCode int `json:"code"` } // MySQL error message look like Error nn: description func dbParseEror(raw_error string) (string, int64) { Parts := strings.Split(raw_error, ": ") errorMessage := Parts[1] // After the ":" Code := strings.Split(Parts[0], "Error ") errorCode, _ := strconv.ParseInt(Code[1], 10, 32) return errorMessage, errorCode } type ErrMsg struct { ErrCode int StatusCode int Message string } func ErrorMessages(code int64) ErrMsg { var em ErrMsg = ErrMsg{} errorMessage := "" statusCode := 200 errorCode := 0 switch code { case 1062: errorMessage = "Duplicate entry" statusCode = 409 errorCode = 10 } em.ErrCode = errorCode em.Message = errorMessage em.StatusCode = statusCode return em } /* UserCreate creates a new user and stores it. The SQL query is vulnerable to injection, so the route matching needs be safe. */ func UserCreate(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "http://localhost:8000") NewUser := User{ Name: r.FormValue("user"), Email: r.FormValue("mail"), First: r.FormValue("first"), Last: r.FormValue("last"), } output, err := json.MarshalIndent(NewUser, "", " ") fmt.Println(string(output)) if err != nil { fmt.Println("Something went wrong!") } Response := CreateResponse{} sql := "INSERT INTO users SET user_name='" + NewUser.Name + "', user_first='" + NewUser.First + "', user_last='" + NewUser.Last + "', user_email='" + NewUser.Email + "'" result, err := database.Exec(sql) if err != nil { errorMessage, errorCode := dbParseEror(err.Error()) em := ErrorMessages(errorCode) fmt.Println(errorMessage) Response.Error = em.Message Response.ErrorCode = em.ErrCode w.WriteHeader(em.StatusCode) } var id int64 var rows int64 if result != nil { id, _ = result.LastInsertId() rows, _ = result.RowsAffected() } else { id = 0 rows = 0 } fmt.Printf("Id: %d, Rows affected: %d\n", id, rows) createOutput, err := json.Marshal(Response) fmt.Fprintln(w, string(createOutput)) } func UsersRetrieve(w http.ResponseWriter, r *http.Request) { w.Header().Set("Pragma", "no-cache") rows, err := database.Query(` SELECT u.user_id, u.user_name, u.user_email, u.user_first, u.user_last FROM users u LIMIT 10 `) if err != nil { fmt.Fprintln(w, "Something went wrong!") log.Fatal(err) } Response := Users{} for rows.Next() { user := User{} rows.Scan(&user.ID, &user.Name, &user.Email, &user.First, &user.Last) Response.Users = append(Response.Users, user) } output, _ := json.MarshalIndent(Response, "", " ") fmt.Fprintln(w, string(output)) } type API struct { Message string `json:"message"` } func UserMethods(w http.ResponseWriter, r *http.Request) { } func UserReplace(w http.ResponseWriter, r *http.Request) { } func UserDelete(w http.ResponseWriter, r *http.Request) { } func UserRetrieve(w http.ResponseWriter, r *http.Request) { w.Header().Set("Pragma", "no-cache") vars := mux.Vars(r) id := vars["id"] user := User{} var result API sqlQuery := ` SELECT u.user_id, u.user_name, u.user_first, u.user_last, u.user_email FROM users u WHERE u.user_id = ? ` stmt, err := database.Prepare(sqlQuery) if err != nil { log.Fatal(err.Error()) } row := stmt.QueryRow(id) scanErr := row.Scan(&user.ID, &user.Name, &user.First, &user.Last, &user.Email) switch { case scanErr == sql.ErrNoRows: // FIXME XSS result = API{Message: fmt.Sprintf("No such user: %s", id)} json, err := json.MarshalIndent(result, "", " ") if err != nil { log.Fatal(err.Error()) } w.Write(json) case err != nil: // FIXME XSS result = API{Message: fmt.Sprintf("Error reading user: %s", id)} json, errIndent := json.MarshalIndent(result, "", " ") if errIndent != nil { log.Fatal(errIndent.Error()) } w.Write(json) log.Fatal(err.Error()) case err == nil: json, errIndent := json.MarshalIndent(user, "", " ") if errIndent != nil { log.Fatal(errIndent.Error()) } w.Write(json) } } func main() { var err error db, err := sql.Open("mysql", "goroot:gopass@/goweb_social_network") if err != nil { log.Fatal(err.Error()) } database = db routes := mux.NewRouter() routes.HandleFunc("/api/users", UserCreate).Methods("POST") routes.HandleFunc(`/api/users`, UsersRetrieve).Methods("GET") // Not yet implemented. routes.HandleFunc(`/api/users`, UserMethods).Methods("OPTIONS") routes.HandleFunc(`/api/users/{id:[\d]+}`, UserRetrieve).Methods("GET") routes.HandleFunc(`/api/users/{id:[\d]+}`, UserReplace).Methods("PUT") routes.HandleFunc(`/api/users/{id:[\d]+}`, UserDelete).Methods("DELETE") // -------------------- http.Handle("/", routes) http.ListenAndServe(":8080", nil) }