Skip to content

Commit aaf73ba

Browse files
committed
API: Add list and removal API for hash
1 parent 406dfc2 commit aaf73ba

File tree

5 files changed

+265
-41
lines changed

5 files changed

+265
-41
lines changed

api/get_hash.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"net/http"
7+
"strconv"
8+
"wwfc/database"
9+
)
10+
11+
func HandleGetHash(w http.ResponseWriter, r *http.Request) {
12+
var store database.HashStore
13+
var success bool
14+
var err string
15+
var statusCode int
16+
17+
if r.Method == http.MethodPost {
18+
store, success, err, statusCode = handleGetHashImpl(r)
19+
} else if r.Method == http.MethodOptions {
20+
statusCode = http.StatusNoContent
21+
w.Header().Set("Access-Control-Allow-Methods", "POST")
22+
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
23+
} else {
24+
err = "Incorrect request. POST only."
25+
statusCode = http.StatusMethodNotAllowed
26+
w.Header().Set("Allow", "POST")
27+
}
28+
29+
w.Header().Set("Access-Control-Allow-Origin", "*")
30+
31+
var jsonData []byte
32+
33+
if statusCode != http.StatusNoContent {
34+
w.Header().Set("Content-Type", "application/json")
35+
jsonData, _ = json.Marshal(GetHashResponse{success, err, store})
36+
}
37+
38+
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
39+
w.WriteHeader(statusCode)
40+
w.Write(jsonData)
41+
}
42+
43+
type GetHashRequestSpec struct {
44+
Secret string `json:"secret"`
45+
}
46+
47+
type GetHashResponse struct {
48+
Success bool
49+
Error string
50+
Hashes database.HashStore
51+
}
52+
53+
func handleGetHashImpl(r *http.Request) (database.HashStore, bool, string, int) {
54+
ret := database.HashStore{}
55+
56+
body, err := io.ReadAll(r.Body)
57+
if err != nil {
58+
return ret, false, "Unable to read request body", http.StatusBadRequest
59+
}
60+
61+
var req GetHashRequestSpec
62+
err = json.Unmarshal(body, &req)
63+
if err != nil {
64+
return ret, false, err.Error(), http.StatusBadRequest
65+
}
66+
67+
if apiSecret == "" || req.Secret != apiSecret {
68+
return nil, false, "Invalid API secret in request", http.StatusUnauthorized
69+
}
70+
71+
return database.GetHashes(), true, "", http.StatusOK
72+
}

api/remove_hash.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"net/http"
7+
"strconv"
8+
"wwfc/database"
9+
)
10+
11+
func HandleRemoveHash(w http.ResponseWriter, r *http.Request) {
12+
var success bool
13+
var err string
14+
var statusCode int
15+
16+
if r.Method == http.MethodPost {
17+
success, err, statusCode = handleRemoveHashImpl(r)
18+
} else if r.Method == http.MethodOptions {
19+
statusCode = http.StatusNoContent
20+
w.Header().Set("Access-Control-Allow-Methods", "POST")
21+
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
22+
} else {
23+
err = "Incorrect request. POST only."
24+
statusCode = http.StatusMethodNotAllowed
25+
w.Header().Set("Allow", "POST")
26+
}
27+
28+
w.Header().Set("Access-Control-Allow-Origin", "*")
29+
30+
var jsonData []byte
31+
32+
if statusCode != http.StatusNoContent {
33+
w.Header().Set("Content-Type", "application/json")
34+
jsonData, _ = json.Marshal(RemoveHashResponse{success, err})
35+
}
36+
37+
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
38+
w.WriteHeader(statusCode)
39+
w.Write(jsonData)
40+
}
41+
42+
type RemoveHashRequestSpec struct {
43+
Secret string `json:"secret"`
44+
PackID uint32 `json:"pack_id"`
45+
Version uint32 `json:"version"`
46+
}
47+
48+
type RemoveHashResponse struct {
49+
Success bool
50+
Error string
51+
}
52+
53+
func handleRemoveHashImpl(r *http.Request) (bool, string, int) {
54+
body, err := io.ReadAll(r.Body)
55+
if err != nil {
56+
return false, "Unable to read request body", http.StatusBadRequest
57+
}
58+
59+
var req RemoveHashRequestSpec
60+
err = json.Unmarshal(body, &req)
61+
if err != nil {
62+
return false, err.Error(), http.StatusBadRequest
63+
}
64+
65+
if apiSecret == "" || req.Secret != apiSecret {
66+
return false, "Invalid API secret in request", http.StatusUnauthorized
67+
}
68+
69+
err = database.RemoveHash(pool, ctx, req.PackID, req.Version)
70+
if err != nil {
71+
return false, err.Error(), http.StatusInternalServerError
72+
}
73+
74+
return true, "", http.StatusOK
75+
}

api/hash.go renamed to api/set_hash.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import (
1111
"github.com/logrusorgru/aurora/v3"
1212
)
1313

14-
func HandleHash(w http.ResponseWriter, r *http.Request) {
14+
func HandleSetHash(w http.ResponseWriter, r *http.Request) {
1515
var success bool
1616
var err string
1717
var statusCode int
1818

1919
if r.Method == http.MethodPost {
20-
success, err, statusCode = handleHashImpl(r)
20+
success, err, statusCode = handleSetHashImpl(r)
2121
} else if r.Method == http.MethodOptions {
2222
statusCode = http.StatusNoContent
2323
w.Header().Set("Access-Control-Allow-Methods", "POST")
@@ -57,7 +57,7 @@ type HashResponse struct {
5757
Error string
5858
}
5959

60-
func handleHashImpl(r *http.Request) (bool, string, int) {
60+
func handleSetHashImpl(r *http.Request) (bool, string, int) {
6161
// TODO: Actual authentication rather than a fixed secret
6262

6363
body, err := io.ReadAll(r.Body)

database/hash.go

Lines changed: 103 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package database
22

33
import (
44
"context"
5+
"errors"
6+
"regexp"
57
"wwfc/logging"
68

79
"github.com/jackc/pgx/v4/pgxpool"
@@ -10,12 +12,13 @@ import (
1012
)
1113

1214
const (
13-
GetHashes = `SELECT * FROM hashes`
14-
InsertHash = `INSERT
15+
QueryHashes = `SELECT * FROM hashes`
16+
InsertHash = `INSERT
1517
INTO hashes (pack_id, version, hash_ntscu, hash_ntscj, hash_ntsck, hash_pal)
1618
VALUES ($1, $2, $3, $4, $5, $6)
1719
ON CONFLICT (pack_id, version)
1820
DO UPDATE SET hash_ntscu = $3, hash_ntscj = $4, hash_ntsck = $5, hash_pal = $6`
21+
DeleteHash = `DELETE FROM hashes WHERE pack_id = $1 AND version = $2`
1922
)
2023

2124
type Region byte
@@ -27,18 +30,56 @@ const (
2730
R_PAL
2831
)
2932

33+
func IndexByRegionByte(hbr HashesByRegion, b Region) string {
34+
switch b {
35+
case R_NTSCU:
36+
return hbr.NTSCU
37+
case R_NTSCJ:
38+
return hbr.NTSCJ
39+
case R_NTSCK:
40+
return hbr.NTSCK
41+
case R_PAL:
42+
return hbr.PAL
43+
default:
44+
return ""
45+
}
46+
}
47+
48+
type HashesByRegion struct {
49+
NTSCU string
50+
NTSCJ string
51+
NTSCK string
52+
PAL string
53+
}
54+
55+
type HashStore map[uint32]map[uint32]HashesByRegion
56+
3057
var (
31-
mutex = deadlock.Mutex{}
32-
hashes = map[uint32]map[uint32]map[Region]string{}
58+
mutex = deadlock.Mutex{}
59+
hashes = HashStore{}
60+
emptyRegexp *regexp.Regexp
3361
)
3462

63+
// Used to flatten the 40-wide empty strings returned by the db, and to filter
64+
// whitespace potentially submitted over the api
65+
func flattenBlank(str string) string {
66+
if emptyRegexp.MatchString(str) {
67+
return ""
68+
}
69+
70+
return str
71+
}
72+
3573
func HashInit(pool *pgxpool.Pool, ctx context.Context) error {
3674
mutex.Lock()
3775
defer mutex.Unlock()
3876

39-
logging.Info("DB", "Populating hashes from the database")
77+
// Populate regexp once
78+
emptyRegexp = regexp.MustCompile("^\\s+$")
79+
80+
logging.Info("DATABASE", "Populating hashes from the database")
4081

41-
rows, err := pool.Query(ctx, GetHashes)
82+
rows, err := pool.Query(ctx, QueryHashes)
4283
defer rows.Close()
4384
if err != nil {
4485
return err
@@ -60,61 +101,87 @@ func HashInit(pool *pgxpool.Pool, ctx context.Context) error {
60101
versions, exists := hashes[packID]
61102

62103
if !exists {
63-
temp := map[uint32]map[Region]string{}
104+
temp := map[uint32]HashesByRegion{}
64105
hashes[packID] = temp
65106
versions = temp
66107
}
67108

68-
regions, exists := versions[version]
69-
70-
if !exists {
71-
temp := map[Region]string{}
72-
versions[version] = temp
73-
regions = temp
109+
versions[version] = HashesByRegion{
110+
NTSCU: flattenBlank(hashNTSCU),
111+
NTSCJ: flattenBlank(hashNTSCJ),
112+
NTSCK: flattenBlank(hashNTSCK),
113+
PAL: flattenBlank(hashPAL),
74114
}
75115

76-
regions[R_NTSCU] = hashNTSCU
77-
regions[R_NTSCJ] = hashNTSCJ
78-
regions[R_NTSCK] = hashNTSCK
79-
regions[R_PAL] = hashPAL
80-
81-
logging.Info("DB", "Populated hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "\nNTSCU:", aurora.Cyan(hashNTSCU), "\nNTSCJ:", aurora.Cyan(hashNTSCJ), "\nNTSCK:", aurora.Cyan(hashNTSCK), "\nPAL:", aurora.Cyan(hashPAL))
116+
logging.Info("DATABASE", "Populated hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "\nNTSCU:", aurora.Cyan(hashNTSCU), "\nNTSCJ:", aurora.Cyan(hashNTSCJ), "\nNTSCK:", aurora.Cyan(hashNTSCK), "\nPAL:", aurora.Cyan(hashPAL))
82117
}
83118

84119
return nil
85120
}
86121

122+
func GetHashes() HashStore {
123+
mutex.Lock()
124+
defer mutex.Unlock()
125+
126+
// Create a copy while the mutex is locked. Defer runs before return
127+
ret := hashes
128+
129+
return ret
130+
}
131+
132+
var (
133+
ErrPackIDMissing = errors.New("The specified PackID does not exist")
134+
ErrVersionMissing = errors.New("The specified version does not exist")
135+
)
136+
137+
func RemoveHash(pool *pgxpool.Pool, ctx context.Context, packID uint32, version uint32) error {
138+
mutex.Lock()
139+
defer mutex.Unlock()
140+
141+
if versions, exists := hashes[packID]; exists {
142+
if _, exists := versions[version]; exists {
143+
delete(versions, version)
144+
} else {
145+
return ErrVersionMissing
146+
}
147+
} else {
148+
return ErrPackIDMissing
149+
}
150+
151+
_, err := pool.Exec(ctx, DeleteHash, packID, version)
152+
if err != nil {
153+
logging.Error("DATABASE", "Failure to remove hash for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "Error:", err.Error())
154+
} else {
155+
logging.Warn("DATABASE", "Removed hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version))
156+
}
157+
return err
158+
}
159+
87160
func UpdateHash(pool *pgxpool.Pool, ctx context.Context, packID uint32, version uint32, hashNTSCU string, hashNTSCJ string, hashNTSCK string, hashPAL string) error {
88161
mutex.Lock()
89162
defer mutex.Unlock()
90163

91164
versions, exists := hashes[packID]
92165

93166
if !exists {
94-
temp := map[uint32]map[Region]string{}
167+
temp := map[uint32]HashesByRegion{}
95168
hashes[packID] = temp
96169
versions = temp
97170
}
98171

99-
regions, exists := versions[packID]
100-
101-
if !exists {
102-
temp := map[Region]string{}
103-
versions[packID] = temp
104-
regions = temp
172+
versions[version] = HashesByRegion{
173+
NTSCU: flattenBlank(hashNTSCU),
174+
NTSCJ: flattenBlank(hashNTSCJ),
175+
NTSCK: flattenBlank(hashNTSCK),
176+
PAL: flattenBlank(hashPAL),
105177
}
106178

107-
regions[R_NTSCU] = hashNTSCU
108-
regions[R_NTSCJ] = hashNTSCJ
109-
regions[R_NTSCK] = hashNTSCK
110-
regions[R_PAL] = hashPAL
111-
112179
_, err := pool.Exec(ctx, InsertHash, packID, version, hashNTSCU, hashNTSCJ, hashNTSCK, hashPAL)
113180

114181
if err != nil {
115-
logging.Error("DB", "Failed to update hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "error:", err.Error())
182+
logging.Error("DATABASE", "Failed to update hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "error:", err.Error())
116183
} else {
117-
logging.Info("DB", "Successfully updated hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version))
184+
logging.Info("DATABASE", "Successfully updated hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version))
118185
}
119186

120187
return err
@@ -126,9 +193,9 @@ func ValidateHash(packID uint32, version uint32, region Region, hash string) boo
126193

127194
if versions, exists := hashes[packID]; exists {
128195
if regions, exists := versions[version]; exists {
129-
if hash_real, exists := regions[region]; exists {
130-
return hash_real != "" && hash_real == hash
131-
}
196+
hash_real := IndexByRegionByte(regions, region)
197+
198+
return hash_real != "" && hash_real == hash
132199
}
133200
}
134201

0 commit comments

Comments
 (0)