GET v1/users is now actually pretty darn cool

This commit is contained in:
Howl 2016-10-16 16:26:10 +02:00
parent dccec469af
commit fefde77261
4 changed files with 213 additions and 17 deletions

View File

@ -44,5 +44,5 @@ LEFT JOIN users_stats
ON users.id=users_stats.id
WHERE users.id=?
LIMIT 1`
return userPuts(md, md.DB.QueryRowx(query, data.UserID))
return userPutsSingle(md, md.DB.QueryRowx(query, data.UserID))
}

View File

@ -22,33 +22,36 @@ type userData struct {
Country string `json:"country"`
}
// UsersGET is the API handler for GET /users
func UsersGET(md common.MethodData) common.CodeMessager {
shouldRet, whereClause, param := whereClauseUser(md, "users")
if shouldRet != nil {
return *shouldRet
}
query := `
SELECT users.id, users.username, register_datetime, privileges,
const userFields = `users.id, users.username, register_datetime, privileges,
latest_activity, users_stats.username_aka,
users_stats.country
FROM users
INNER JOIN users_stats
ON users.id=users_stats.id
`
// UsersGET is the API handler for GET /users
func UsersGET(md common.MethodData) common.CodeMessager {
shouldRet, whereClause, param := whereClauseUser(md, "users")
if shouldRet != nil {
return userPutsMulti(md)
}
query := `
SELECT ` + userFields + `
WHERE ` + whereClause + ` AND ` + md.User.OnlyUserPublic(true) + `
LIMIT 1`
return userPuts(md, md.DB.QueryRowx(query, param))
return userPutsSingle(md, md.DB.QueryRowx(query, param))
}
type userPutsUserData struct {
type userPutsSingleUserData struct {
common.ResponseBase
userData
}
func userPuts(md common.MethodData, row *sqlx.Row) common.CodeMessager {
func userPutsSingle(md common.MethodData, row *sqlx.Row) common.CodeMessager {
var err error
var user userPutsUserData
var user userPutsSingleUserData
err = row.StructScan(&user.userData)
switch {
@ -63,6 +66,62 @@ func userPuts(md common.MethodData, row *sqlx.Row) common.CodeMessager {
return user
}
type userPutsMultiUserData struct {
common.ResponseBase
Users []userData `json:"users"`
}
func userPutsMulti(md common.MethodData) common.CodeMessager {
q := md.C.Request.URL.Query()
// query composition
wh := common.
Where("users.username = ?", md.Query("nname")).
Where("users.id = ?", md.Query("iid")).
Where("users.privileges = ?", md.Query("privileges")).
Where("users.privileges & ? > 0", md.Query("has_privileges")).
Where("users_stats.country = ?", md.Query("country")).
Where("users_stats.username_aka = ?", md.Query("name_aka")).
In("users.id", q["ids"]...).
In("users.username", q["names"]...).
In("users_stats.username_aka", q["names_aka"]...).
In("users_stats.country", q["countries"]...)
query := "" +
"SELECT " + userFields + wh.ClauseSafe() + " AND " + md.User.OnlyUserPublic(true) +
" " + common.Sort(md, common.SortConfiguration{
Allowed: []string{
"id",
"username",
"privileges",
"donor_expire",
"latest_activity",
"silence_end",
},
Default: "id ASC",
Table: "users",
}) +
" " + common.Paginate(md.Query("p"), md.Query("l"), 100)
// query execution
rows, err := md.DB.Queryx(query, wh.Params...)
if err != nil {
md.Err(err)
return Err500
}
var r userPutsMultiUserData
for rows.Next() {
var u userData
err := rows.StructScan(&u)
if err != nil {
md.Err(err)
continue
}
r.Users = append(r.Users, u)
}
r.Code = 200
return r
}
// UserSelfGET is a shortcut for /users/id/self. (/users/self)
func UserSelfGET(md common.MethodData) common.CodeMessager {
md.C.Request.URL.RawQuery = "id=self&" + md.C.Request.URL.RawQuery

View File

@ -15,15 +15,56 @@ func (w *WhereClause) Where(clause, passedParam string, allowedValues ...string)
if len(allowedValues) != 0 && !contains(allowedValues, passedParam) {
return w
}
// checks passed, if string is empty add "WHERE"
w.addWhere()
w.Clause += clause
w.Params = append(w.Params, passedParam)
return w
}
func (w *WhereClause) addWhere() {
// if string is empty add "WHERE", else add AND
if w.Clause == "" {
w.Clause += "WHERE "
} else {
w.Clause += " AND "
}
w.Clause += clause
w.Params = append(w.Params, passedParam)
}
// In generates an IN clause.
// initial is the initial part, e.g. "users.id".
// Fields are the possible values.
// Sample output: users.id IN ('1', '2', '3')
func (w *WhereClause) In(initial string, fields ...string) *WhereClause {
if len(fields) == 0 {
return w
}
w.addWhere()
w.Clause += initial + " IN (" + generateQuestionMarks(len(fields)) + ")"
fieldsInterfaced := make([]interface{}, 0, len(fields))
for _, i := range fields {
fieldsInterfaced = append(fieldsInterfaced, interface{}(i))
}
w.Params = append(w.Params, fieldsInterfaced...)
return w
}
func generateQuestionMarks(x int) (qm string) {
for i := 0; i < x-1; i++ {
qm += "?, "
}
if x > 0 {
qm += "?"
}
return qm
}
// ClauseSafe returns the clause, always containing something. If w.Clause is
// empty, it returns "WHERE 1".
func (w *WhereClause) ClauseSafe() string {
if w.Clause == "" {
return "WHERE 1"
}
return w.Clause
}
// Where is the same as WhereClause.Where, but creates a new WhereClause.

96
common/where_test.go Normal file
View File

@ -0,0 +1,96 @@
package common
import (
"reflect"
"testing"
)
func Test_generateQuestionMarks(t *testing.T) {
type args struct {
x int
}
tests := []struct {
name string
args args
wantQm string
}{
{"-1", args{-1}, ""},
{"0", args{0}, ""},
{"1", args{1}, "?"},
{"2", args{2}, "?, ?"},
}
for _, tt := range tests {
if gotQm := generateQuestionMarks(tt.args.x); gotQm != tt.wantQm {
t.Errorf("%q. generateQuestionMarks() = %v, want %v", tt.name, gotQm, tt.wantQm)
}
}
}
func TestWhereClause_In(t *testing.T) {
type args struct {
initial string
fields []interface{}
}
tests := []struct {
name string
fields *WhereClause
args args
want *WhereClause
}{
{
"simple",
&WhereClause{},
args{"users.id", []interface{}{"1", "2", "3"}},
&WhereClause{"WHERE users.id IN (?, ?, ?)", []interface{}{"1", "2", "3"}},
},
{
"withExisting",
Where("users.username = ?", "Howl").Where("users.xd > ?", "6"),
args{"users.id", []interface{}{"1"}},
&WhereClause{
"WHERE users.username = ? AND users.xd > ? AND users.id IN (?)",
[]interface{}{"Howl", "6", "1"},
},
},
}
for _, tt := range tests {
w := tt.fields
if got := w.In(tt.args.initial, tt.args.fields...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("%q. WhereClause.In() = %v, want %v", tt.name, got, tt.want)
}
}
}
func TestWhere(t *testing.T) {
type args struct {
clause string
passedParam string
allowedValues []string
}
tests := []struct {
name string
args args
want *WhereClause
}{
{
"simple",
args{"users.id = ?", "5", nil},
&WhereClause{"WHERE users.id = ?", []interface{}{"5"}},
},
{
"allowed",
args{"users.id = ?", "5", []string{"1", "3", "5"}},
&WhereClause{"WHERE users.id = ?", []interface{}{"5"}},
},
{
"notAllowed",
args{"users.id = ?", "5", []string{"0"}},
&WhereClause{},
},
}
for _, tt := range tests {
if got := Where(tt.args.clause, tt.args.passedParam, tt.args.allowedValues...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("%q. Where() = %#v, want %#v", tt.name, got, tt.want)
}
}
}