view controllers/user.go @ 254:de6fdb316b8f

Implemented /users/{user} GET a listing of given user.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Fri, 27 Jul 2018 12:30:19 +0200
parents 322c3d0e05ef
children dfc2b035e055
line wrap: on
line source

package controllers

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strings"

	"github.com/gorilla/mux"

	"gemma.intevation.de/gemma/auth"
)

type (
	Email   string
	Country string
	Role    string

	BoundingBox struct {
		X1 float64 `json:"x1"`
		Y1 float64 `json:"y1"`
		X2 float64 `json:"x2"`
		Y2 float64 `json:"y2"`
	}

	User struct {
		User     string       `json:"user"`
		Role     Role         `json:"role"`
		Password string       `json:"password,omitempty"`
		Email    Email        `json:"email"`
		Country  Country      `json:"country"`
		Extent   *BoundingBox `json:"extent"`
	}
)

const (
	createUserSQL       = `SELECT sys_admin.create_user($1, $2, $3, $4, NULL, $5)`
	createUserExtentSQL = `SELECT sys_admin.create_user($1, $2, $3, $4,
  ST_MakeBox2D(ST_Point($5, $6), ST_Point($7, $8)), $9)`

	updateUserSQL       = `SELECT sys_admin.update_user($1, $2, $3, $4, $5, NULL, $6)`
	updateUserExtentSQL = `SELECT sys_admin.update_user($1, $2, $3, $4, $5,
  ST_MakeBox2D(ST_Point($6, $7), ST_Point($8, $9)), $10)`

	deleteUserSQL = `SELECT sys_admin.delete_user($1)`

	listUsersSQL = `SELECT
  rolname,
  username,
  country,
  email_address,
  ST_XMin(map_extent), ST_YMin(map_extent),
  ST_XMax(map_extent), ST_YMax(map_extent)
FROM sys_admin.list_users`

	listUserSQL = `SELECT
  rolname,
  country,
  email_address,
  ST_XMin(map_extent), ST_YMin(map_extent),
  ST_XMax(map_extent), ST_YMax(map_extent)
FROM sys_admin.list_users
WHERE username = $1`
)

var errNoString = errors.New("Not a string")

var (
	// https://stackoverflow.com/questions/201323/how-to-validate-an-email-address-using-a-regular-expression
	emailRe = regexp.MustCompile(
		`(?:[a-z0-9!#$%&'*+/=?^_` + "`" +
			`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_` + "`" +
			`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]` +
			`|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")` +
			`@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?` +
			`|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}` +
			`(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]` +
			`:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]` +
			`|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])`)
	errNoEmailAddress = errors.New("Not a valid email address")
)

func (e *Email) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	if !emailRe.MatchString(s) {
		return errNoEmailAddress
	}
	*e = Email(s)
	return nil
}

func (e Email) Value() (driver.Value, error) {
	return string(e), nil
}

func (e *Email) Scan(src interface{}) (err error) {
	if s, ok := src.(string); ok {
		*e = Email(s)
	} else {
		err = errNoString
	}
	return
}

var (
	validCountries = []string{
		"AT", "BG", "DE", "HU", "HR",
		"MD", "RO", "RS", "SK", "UA",
	}
	errNoValidCountry = errors.New("Not a valid country")
)

func (c *Country) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	s = strings.ToUpper(s)
	for _, v := range validCountries {
		if v == s {
			*c = Country(v)
			return nil
		}
	}
	return errNoValidCountry
}

func (c Country) Value() (driver.Value, error) {
	return string(c), nil
}

func (c *Country) Scan(src interface{}) (err error) {
	if s, ok := src.(string); ok {
		*c = Country(s)
	} else {
		err = errNoString
	}
	return
}

var (
	validRoles = []string{
		"waterway_user",
		"waterway_admin",
		"sys_admin",
	}
	errNoValidRole = errors.New("Not a valid role")
)

func (r Role) Value() (driver.Value, error) {
	return string(r), nil
}

func (r *Role) Scan(src interface{}) (err error) {
	if s, ok := src.(string); ok {
		*r = Role(s)
	} else {
		err = errNoString
	}
	return
}

func (r *Role) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	s = strings.ToLower(s)
	for _, v := range validRoles {
		if v == s {
			*r = Role(v)
			return nil
		}
	}
	return errNoValidRole
}

func deleteUser(
	rw http.ResponseWriter, req *http.Request,
	input interface{}, db *sql.DB,
) (jr JSONResult, err error) {

	user := mux.Vars(req)["user"]
	if user == "" {
		err = JSONError{http.StatusBadRequest, "error: user empty"}
		return
	}

	session, _ := auth.GetSession(req)
	if session.User == user {
		err = JSONError{http.StatusBadRequest, "error: cannot delete yourself"}
		return
	}

	if _, err = db.Exec(deleteUserSQL, user); err != nil {
		return
	}

	// Running in a go routine should not be necessary.
	go func() { auth.ConnPool.Logout(user) }()

	jr = JSONResult{Code: http.StatusNoContent}
	return
}

func updateUser(
	rw http.ResponseWriter, req *http.Request,
	input interface{}, db *sql.DB,
) (jr JSONResult, err error) {

	user := mux.Vars(req)["user"]
	if user == "" {
		err = JSONError{http.StatusBadRequest, "error: user empty"}
		return
	}

	newUser := input.(*User)

	if newUser.Extent == nil {
		_, err = db.Exec(
			updateUserSQL,
			user,
			newUser.Role,
			newUser.User,
			newUser.Password,
			newUser.Country,
			newUser.Email,
		)
	} else {
		_, err = db.Exec(
			updateUserExtentSQL,
			user,
			newUser.Role,
			newUser.User,
			newUser.Password,
			newUser.Country,
			newUser.Extent.X1, newUser.Extent.Y1,
			newUser.Extent.X2, newUser.Extent.Y2,
			newUser.Email,
		)
	}

	if err != nil {
		return
	}

	jr = JSONResult{
		Code: http.StatusCreated,
		Result: struct {
			Result string `json:"result"`
		}{
			Result: "success",
		},
	}
	return
}

func createUser(
	rw http.ResponseWriter, req *http.Request,
	input interface{}, db *sql.DB,
) (jr JSONResult, err error) {

	user := input.(*User)

	if user.Extent == nil {
		_, err = db.Exec(
			createUserSQL,
			user.Role,
			user.User,
			user.Password,
			user.Country,
			user.Email,
		)
	} else {
		_, err = db.Exec(
			createUserExtentSQL,
			user.Role,
			user.User,
			user.Password,
			user.Country,
			user.Extent.X1, user.Extent.Y1,
			user.Extent.X2, user.Extent.Y2,
			user.Email,
		)
	}

	if err != nil {
		return
	}

	jr = JSONResult{
		Code: http.StatusCreated,
		Result: struct {
			Result string `json:"result"`
		}{
			Result: "success",
		},
	}
	return
}

func listUsers(
	rw http.ResponseWriter, req *http.Request,
	input interface{}, db *sql.DB,
) (jr JSONResult, err error) {

	var rows *sql.Rows

	rows, err = db.Query(listUsersSQL)
	if err != nil {
		return
	}
	defer rows.Close()

	var users []*User

	for rows.Next() {
		user := &User{Extent: &BoundingBox{}}
		if err = rows.Scan(
			&user.Role,
			&user.User,
			&user.Country,
			&user.Email,
			&user.Extent.X1, &user.Extent.Y1,
			&user.Extent.X2, &user.Extent.Y2,
		); err != nil {
			return
		}
		users = append(users, user)
	}

	jr = JSONResult{
		Result: struct {
			Users []*User `json:"users"`
		}{
			Users: users,
		},
	}
	return
}

func listUser(
	rw http.ResponseWriter, req *http.Request,
	input interface{}, db *sql.DB,
) (jr JSONResult, err error) {

	user := mux.Vars(req)["user"]
	if user == "" {
		err = JSONError{http.StatusBadRequest, "error: user empty"}
		return
	}

	result := &User{
		User:   user,
		Extent: &BoundingBox{},
	}

	err = db.QueryRow(listUserSQL, user).Scan(
		&result.Role,
		&result.Country,
		&result.Email,
		&result.Extent.X1, &result.Extent.Y1,
		&result.Extent.X2, &result.Extent.Y2,
	)

	switch {
	case err == sql.ErrNoRows:
		err = JSONError{
			Code:    http.StatusNotFound,
			Message: fmt.Sprintf("Cannot find user %s.", user),
		}
		return
	case err != nil:
		return
	}

	jr.Result = result
	return
}