Skip to content

Commit a044cea

Browse files
committed
Add /api/query for searching users in the db
1 parent 67d5e2d commit a044cea

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

api/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func StartServer(reload bool) {
9999
KickRoute,
100100
MotdRoute,
101101
PinfoRoute,
102+
QueryRoute,
102103
RemoveHashRoute,
103104
SetHashRoute,
104105
UnbanRoute,

api/query.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package api
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
"regexp"
8+
"wwfc/database"
9+
)
10+
11+
type QueryRequest struct {
12+
Secret string `json:"secret"`
13+
IP string `json:"ip"`
14+
DeviceID uint32 `json:"deviceID"`
15+
Csnum string `json:"csnum"`
16+
// 0: Either, 1: No Ban, 2: Ban
17+
HasBan byte `json:"hasban"`
18+
}
19+
20+
type QueryResponse struct {
21+
Users []database.User
22+
Success bool
23+
Error string
24+
}
25+
26+
var QueryRoute = MakeRouteSpec[QueryRequest, QueryResponse](
27+
true,
28+
"/api/query",
29+
HandleQuery,
30+
http.MethodPost,
31+
)
32+
33+
var (
34+
ErrInvalidIPFormat = errors.New("Invalid IP Format. IPs must be in the format '%d.%d.%d.%d'.")
35+
ErrInvalidDeviceID = errors.New("DeviceID cannot be 0.")
36+
ErrInvalidCsnum = errors.New("Csnums must be less than 16 characters long and match the format '^[a-zA-Z0-9]+$'.")
37+
ErrInvalidHasBan = errors.New("HasBan must be either 0 (Either), 1 (No Ban), 2 (Ban)")
38+
ErrEmptyParams = errors.New("At least one of IP, Csnum, and DeviceID must be nonzero or nonempty")
39+
40+
ipRegex = regexp.MustCompile(`\d\.\d\.\d\.`)
41+
csnumRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
42+
)
43+
44+
const QUERYBASE = `SELECT profile_id, user_id, gsbrcd, ng_device_id, email, unique_nick, firstname, lastname, has_ban, ban_reason, open_host, last_ingamesn, last_ip_address, csnum, ban_moderator, ban_reason_hidden, ban_issued, ban_expires FROM users WHERE`
45+
46+
func HandleQuery(req any, _ bool, _ *http.Request) (any, int, error) {
47+
_req := req.(QueryRequest)
48+
49+
if _req.IP != "" && !ipRegex.MatchString(_req.IP) {
50+
return nil, http.StatusBadRequest, ErrInvalidIPFormat
51+
}
52+
53+
if _req.Csnum != "" && (len(_req.Csnum) > 16 || !csnumRegex.MatchString(_req.Csnum)) {
54+
return nil, http.StatusBadRequest, ErrInvalidCsnum
55+
}
56+
57+
if _req.HasBan != 0 && _req.HasBan != 1 && _req.HasBan != 2 {
58+
return nil, http.StatusBadRequest, ErrInvalidHasBan
59+
}
60+
61+
if _req.IP == "" && _req.Csnum == "" && _req.DeviceID == 0 {
62+
return nil, http.StatusBadRequest, ErrEmptyParams
63+
}
64+
65+
query := QUERYBASE
66+
67+
if _req.IP != "" {
68+
query += fmt.Sprintf(" last_ip_address = '%s' AND", _req.IP)
69+
}
70+
71+
if _req.DeviceID != 0 {
72+
query += fmt.Sprintf(" %d = ANY(ng_device_id) AND", _req.DeviceID)
73+
}
74+
75+
if _req.Csnum != "" {
76+
query += fmt.Sprintf(" '%s' = ANY(csnum) AND", _req.Csnum)
77+
}
78+
79+
if _req.HasBan == 1 {
80+
query += fmt.Sprintf(" has_ban = false AND")
81+
} else if _req.HasBan == 2 {
82+
query += fmt.Sprintf(" has_ban = true AND")
83+
}
84+
85+
query = query[0 : len(query)-4]
86+
query += ";"
87+
88+
rows, err := pool.Query(ctx, query)
89+
defer rows.Close()
90+
if err != nil {
91+
return nil, http.StatusInternalServerError, err
92+
}
93+
94+
res := QueryResponse{}
95+
res.Users = []database.User{}
96+
97+
count := 0
98+
for rows.Next() {
99+
count++
100+
101+
user := database.User{}
102+
103+
// May be null
104+
var firstName *string
105+
var lastName *string
106+
var banReason *string
107+
var lastInGameSn *string
108+
var lastIPAddress *string
109+
var banModerator *string
110+
var banHiddenReason *string
111+
112+
err := rows.Scan(&user.ProfileId, &user.UserId, &user.GsbrCode, &user.NgDeviceId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.Restricted, &banReason, &user.OpenHost, &lastInGameSn, &lastIPAddress, &user.Csnum, &banModerator, &banHiddenReason, &user.BanIssued, &user.BanExpires)
113+
114+
if err != nil {
115+
return nil, http.StatusInternalServerError, err
116+
}
117+
118+
if firstName != nil {
119+
user.FirstName = *firstName
120+
}
121+
122+
if lastName != nil {
123+
user.LastName = *lastName
124+
}
125+
126+
if banReason != nil {
127+
user.BanReason = *banReason
128+
}
129+
130+
if lastInGameSn != nil {
131+
user.LastInGameSn = *lastInGameSn
132+
}
133+
134+
if lastIPAddress != nil {
135+
user.LastIPAddress = *lastIPAddress
136+
}
137+
138+
if banModerator != nil {
139+
user.BanModerator = *banModerator
140+
}
141+
142+
if banHiddenReason != nil {
143+
user.BanReasonHidden = *banHiddenReason
144+
}
145+
146+
res.Users = append(res.Users, user)
147+
148+
// TODO: Return a count of the total number of matches, do something
149+
// about returing 80000 matches if the query is vague enough
150+
}
151+
152+
return res, http.StatusOK, nil
153+
}

0 commit comments

Comments
 (0)