Skip to content

Commit

Permalink
Merge pull request #12 from connorwalsh/user-crud
Browse files Browse the repository at this point in the history
User CRUD
  • Loading branch information
c authored Feb 18, 2018
2 parents 0aa15bd + 3bd002f commit 0bf771b
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 45 deletions.
8 changes: 8 additions & 0 deletions server/consts/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package consts

const (
CREATE = "create"
READ = "read"
UPDATE = "update"
DELETE = "delete"
)
7 changes: 4 additions & 3 deletions server/core/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"

"github.com/connorwalsh/new-yorken-poesry-magazine/server/consts"
"github.com/connorwalsh/new-yorken-poesry-magazine/server/types"
"github.com/gocraft/web"
)
Expand Down Expand Up @@ -70,7 +71,7 @@ func (a *API) CreateUser(rw web.ResponseWriter, req *web.Request) {
// TODO send failure response to client
}

err = user.Validate()
err = user.Validate(consts.CREATE)
if err != nil {
a.Error(err.Error())
}
Expand All @@ -97,13 +98,13 @@ func (a *API) GetUser(rw web.ResponseWriter, req *web.Request) {

// assigning said id to id of user struct
user := &types.User{Id: id}
err = user.Validate()
err = user.Validate(consts.READ)
if err != nil {
a.Error(err.Error())
}

// invoke read
err = user.ReadUser(a.db)
err = user.Read(a.db)
if err != nil {
a.Error(err.Error())
}
Expand Down
13 changes: 2 additions & 11 deletions server/core/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ package core

import (
"fmt"
"regexp"

"github.com/connorwalsh/new-yorken-poesry-magazine/server/utils"
"github.com/gocraft/web"
)

var (
uuidRegexp = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
)

// Validate incoming requests.
// ensure path parameters are valid, etc.
func (a *API) Validate(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) {
Expand All @@ -31,7 +27,7 @@ func ValidateParams(params map[string]string) error {
switch k {
case API_ID_PATH_PARAM:
// id path params MUST be V4 UUIDs
if !IsValidUUIDV4(v) {
if !utils.IsValidUUIDV4(v) {
return fmt.Errorf("Id parameter must be a UUID V4 (given %s)", v)
}
default:
Expand All @@ -43,11 +39,6 @@ func ValidateParams(params map[string]string) error {
return nil
}

// check to see if a string is a UUID V4
func IsValidUUIDV4(uuid string) bool {
return uuidRegexp.MatchString(uuid)
}

// Authorize requests.
// ensure a user cannot delete other users, etc.
func (*API) Authorize(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) {
Expand Down
6 changes: 4 additions & 2 deletions server/core/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"
"testing"

"github.com/connorwalsh/new-yorken-poesry-magazine/server/utils"
"github.com/satori/go.uuid"
"github.com/stretchr/testify/assert"
)
Expand All @@ -14,17 +15,18 @@ func MainTest(m *testing.M) {
os.Exit(retcode)
}

// move this test to utils
func TestIsValidUUIDV4_WithValidUUID(t *testing.T) {
id := uuid.NewV4().String()

assert.True(t, IsValidUUIDV4(id))
assert.True(t, utils.IsValidUUIDV4(id))
}

func TestIsValidUUIDV4_WithInValidUUID(t *testing.T) {
id := uuid.NewV4().String()

// say no to sql injections
assert.False(t, IsValidUUIDV4(id+" OR 1=1 --see u in h3ll"))
assert.False(t, utils.IsValidUUIDV4(id+" OR 1=1 --see u in h3ll"))
}

func TestValidateParams_NoParams(t *testing.T) {
Expand Down
105 changes: 76 additions & 29 deletions server/types/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package types
import (
"database/sql"
"fmt"

"github.com/connorwalsh/new-yorken-poesry-magazine/server/consts"
"github.com/connorwalsh/new-yorken-poesry-magazine/server/utils"
_ "github.com/lib/pq"
)

type User struct {
Expand All @@ -13,8 +17,30 @@ type User struct {
Poets []*Poet `json:"poets"`
}

func (u *User) Validate() error {
// TODO
func (u *User) Validate(action string) error {
// make sure id, if not an empty string, is a uuid
if !utils.IsValidUUIDV4(u.Id) && u.Id != "" {
return fmt.Errorf("User Id must be a valid uuid, given %s", u.Id)
}

// perform validation on a per action basis
switch action {
case consts.CREATE:
case consts.UPDATE:
case consts.DELETE:
// TODO ensure that only a user can delete themselves
fallthrough
default:
// only ensure that the id is present
// this aplies to the READ and DELETE cases
// we minimally need the Id to exist in these cases
if u.Id == "" {
return fmt.Errorf(
"User Id is a required field to fulfill a %s",
action,
)
}
}

return nil
}
Expand Down Expand Up @@ -48,42 +74,45 @@ func (*User) CreateTable(db *sql.DB) error {
return nil
}

func (u *User) CreateUser(db *sql.DB) error {
func (u *User) Create(id string, db *sql.DB) error {
var (
err error
)

// we assume that all validation/sanitization has already been called

// assign id
u.Id = id

// prepare statement if not already done so.
if userCreateStmt == nil {
// create statement
stmt := `INSERT INTO users (
id, username, password, email, poets
) VALUES (?, ?, ?, ?, ?)`
id, username, password, email
) VALUES ($1, $2, $3, $4)`
userCreateStmt, err = db.Prepare(stmt)
if err != nil {
return err
}
}

_, err = userCreateStmt.Exec(u.Id, u.Username, u.Password, u.Email, u.Poets)
_, err = userCreateStmt.Exec(u.Id, u.Username, u.Password, u.Email)
if err != nil {
return err
}

return nil
}

func (u *User) ReadUser(db *sql.DB) error {
func (u *User) Read(db *sql.DB) error {
var (
err error
)

// prepare statement if not already done so.
if userReadStmt == nil {
// read statement
stmt := `SELECT * FROM users WHERE id = ?`
stmt := `SELECT * FROM users WHERE id = $1`
userReadStmt, err = db.Prepare(stmt)
if err != nil {
return err
Expand All @@ -94,49 +123,47 @@ func (u *User) ReadUser(db *sql.DB) error {

// run prepared query over arguments
// NOTE: we are not joining from the poets tables
rows, err := userReadStmt.Query(u.Id)
if err != nil {
return err
}

// decode results into user struct
defer rows.Close()
for rows.Next() {
err = rows.Scan(&u.Id, &u.Username, &u.Password, &u.Email)
if err != nil {
return err
}
}
err = rows.Err()
if err != nil {
err = userReadStmt.
QueryRow(u.Id).
Scan(&u.Id, &u.Username, &u.Password, &u.Email)
switch {
case err == sql.ErrNoRows:
return fmt.Errorf("No user with id %s", u.Id)
case err != nil:
return err
}

fmt.Println(u)

return nil
}

func (u *User) UpdateUser(db *sql.DB) error {
func (u *User) Update(db *sql.DB) error {
// var (
// err error
// )

return nil
}

func (u *User) DeleteUser(db *sql.DB) error {
func (u *User) Delete(db *sql.DB) error {
var (
err error
)

// prepare statement if not already done so.
if userDeleteStmt == nil {
// delete statement
stmt := `DELETE FROM users WHERE id = ?`
stmt := `DELETE FROM users WHERE id = $1`
userDeleteStmt, err = db.Prepare(stmt)
if err != nil {
return err
}
}

_, err = userDeleteStmt.Exec(u.Id)
if err != nil {
return err
}

return nil
}

Expand All @@ -150,12 +177,32 @@ func ReadUsers(db *sql.DB) ([]*User, error) {
if userReadAllStmt == nil {
// readAll statement
// TODO pagination
stmt := `SELECT * FROM users`
stmt := `SELECT id, username, email FROM users`
userReadAllStmt, err = db.Prepare(stmt)
if err != nil {
return users, nil
}
}

rows, err := userReadAllStmt.Query()
if err != nil {
return users, err
}

defer rows.Close()
for rows.Next() {
user := &User{}
err = rows.Scan(&user.Id, &user.Username, &user.Email)
if err != nil {
return users, err
}

// append scanned user into list of all users
users = append(users, user)
}
if err := rows.Err(); err != nil {
return users, err
}

return users, nil
}
Loading

0 comments on commit 0bf771b

Please sign in to comment.