mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-04-24 23:47:50 -05:00
API: Add list and removal API for hash
This commit is contained in:
parent
406dfc296d
commit
aaf73ba784
72
api/get_hash.go
Normal file
72
api/get_hash.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"wwfc/database"
|
||||
)
|
||||
|
||||
func HandleGetHash(w http.ResponseWriter, r *http.Request) {
|
||||
var store database.HashStore
|
||||
var success bool
|
||||
var err string
|
||||
var statusCode int
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
store, success, err, statusCode = handleGetHashImpl(r)
|
||||
} else if r.Method == http.MethodOptions {
|
||||
statusCode = http.StatusNoContent
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
} else {
|
||||
err = "Incorrect request. POST only."
|
||||
statusCode = http.StatusMethodNotAllowed
|
||||
w.Header().Set("Allow", "POST")
|
||||
}
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
var jsonData []byte
|
||||
|
||||
if statusCode != http.StatusNoContent {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
jsonData, _ = json.Marshal(GetHashResponse{success, err, store})
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(jsonData)
|
||||
}
|
||||
|
||||
type GetHashRequestSpec struct {
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
type GetHashResponse struct {
|
||||
Success bool
|
||||
Error string
|
||||
Hashes database.HashStore
|
||||
}
|
||||
|
||||
func handleGetHashImpl(r *http.Request) (database.HashStore, bool, string, int) {
|
||||
ret := database.HashStore{}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return ret, false, "Unable to read request body", http.StatusBadRequest
|
||||
}
|
||||
|
||||
var req GetHashRequestSpec
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
return ret, false, err.Error(), http.StatusBadRequest
|
||||
}
|
||||
|
||||
if apiSecret == "" || req.Secret != apiSecret {
|
||||
return nil, false, "Invalid API secret in request", http.StatusUnauthorized
|
||||
}
|
||||
|
||||
return database.GetHashes(), true, "", http.StatusOK
|
||||
}
|
||||
75
api/remove_hash.go
Normal file
75
api/remove_hash.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"wwfc/database"
|
||||
)
|
||||
|
||||
func HandleRemoveHash(w http.ResponseWriter, r *http.Request) {
|
||||
var success bool
|
||||
var err string
|
||||
var statusCode int
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
success, err, statusCode = handleRemoveHashImpl(r)
|
||||
} else if r.Method == http.MethodOptions {
|
||||
statusCode = http.StatusNoContent
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
} else {
|
||||
err = "Incorrect request. POST only."
|
||||
statusCode = http.StatusMethodNotAllowed
|
||||
w.Header().Set("Allow", "POST")
|
||||
}
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
var jsonData []byte
|
||||
|
||||
if statusCode != http.StatusNoContent {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
jsonData, _ = json.Marshal(RemoveHashResponse{success, err})
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(jsonData)
|
||||
}
|
||||
|
||||
type RemoveHashRequestSpec struct {
|
||||
Secret string `json:"secret"`
|
||||
PackID uint32 `json:"pack_id"`
|
||||
Version uint32 `json:"version"`
|
||||
}
|
||||
|
||||
type RemoveHashResponse struct {
|
||||
Success bool
|
||||
Error string
|
||||
}
|
||||
|
||||
func handleRemoveHashImpl(r *http.Request) (bool, string, int) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return false, "Unable to read request body", http.StatusBadRequest
|
||||
}
|
||||
|
||||
var req RemoveHashRequestSpec
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
return false, err.Error(), http.StatusBadRequest
|
||||
}
|
||||
|
||||
if apiSecret == "" || req.Secret != apiSecret {
|
||||
return false, "Invalid API secret in request", http.StatusUnauthorized
|
||||
}
|
||||
|
||||
err = database.RemoveHash(pool, ctx, req.PackID, req.Version)
|
||||
if err != nil {
|
||||
return false, err.Error(), http.StatusInternalServerError
|
||||
}
|
||||
|
||||
return true, "", http.StatusOK
|
||||
}
|
||||
|
|
@ -11,13 +11,13 @@ import (
|
|||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func HandleHash(w http.ResponseWriter, r *http.Request) {
|
||||
func HandleSetHash(w http.ResponseWriter, r *http.Request) {
|
||||
var success bool
|
||||
var err string
|
||||
var statusCode int
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
success, err, statusCode = handleHashImpl(r)
|
||||
success, err, statusCode = handleSetHashImpl(r)
|
||||
} else if r.Method == http.MethodOptions {
|
||||
statusCode = http.StatusNoContent
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST")
|
||||
|
|
@ -57,7 +57,7 @@ type HashResponse struct {
|
|||
Error string
|
||||
}
|
||||
|
||||
func handleHashImpl(r *http.Request) (bool, string, int) {
|
||||
func handleSetHashImpl(r *http.Request) (bool, string, int) {
|
||||
// TODO: Actual authentication rather than a fixed secret
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
139
database/hash.go
139
database/hash.go
|
|
@ -2,6 +2,8 @@ package database
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
|
|
@ -10,12 +12,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
GetHashes = `SELECT * FROM hashes`
|
||||
InsertHash = `INSERT
|
||||
QueryHashes = `SELECT * FROM hashes`
|
||||
InsertHash = `INSERT
|
||||
INTO hashes (pack_id, version, hash_ntscu, hash_ntscj, hash_ntsck, hash_pal)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (pack_id, version)
|
||||
DO UPDATE SET hash_ntscu = $3, hash_ntscj = $4, hash_ntsck = $5, hash_pal = $6`
|
||||
DeleteHash = `DELETE FROM hashes WHERE pack_id = $1 AND version = $2`
|
||||
)
|
||||
|
||||
type Region byte
|
||||
|
|
@ -27,18 +30,56 @@ const (
|
|||
R_PAL
|
||||
)
|
||||
|
||||
func IndexByRegionByte(hbr HashesByRegion, b Region) string {
|
||||
switch b {
|
||||
case R_NTSCU:
|
||||
return hbr.NTSCU
|
||||
case R_NTSCJ:
|
||||
return hbr.NTSCJ
|
||||
case R_NTSCK:
|
||||
return hbr.NTSCK
|
||||
case R_PAL:
|
||||
return hbr.PAL
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type HashesByRegion struct {
|
||||
NTSCU string
|
||||
NTSCJ string
|
||||
NTSCK string
|
||||
PAL string
|
||||
}
|
||||
|
||||
type HashStore map[uint32]map[uint32]HashesByRegion
|
||||
|
||||
var (
|
||||
mutex = deadlock.Mutex{}
|
||||
hashes = map[uint32]map[uint32]map[Region]string{}
|
||||
mutex = deadlock.Mutex{}
|
||||
hashes = HashStore{}
|
||||
emptyRegexp *regexp.Regexp
|
||||
)
|
||||
|
||||
// Used to flatten the 40-wide empty strings returned by the db, and to filter
|
||||
// whitespace potentially submitted over the api
|
||||
func flattenBlank(str string) string {
|
||||
if emptyRegexp.MatchString(str) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func HashInit(pool *pgxpool.Pool, ctx context.Context) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
logging.Info("DB", "Populating hashes from the database")
|
||||
// Populate regexp once
|
||||
emptyRegexp = regexp.MustCompile("^\\s+$")
|
||||
|
||||
rows, err := pool.Query(ctx, GetHashes)
|
||||
logging.Info("DATABASE", "Populating hashes from the database")
|
||||
|
||||
rows, err := pool.Query(ctx, QueryHashes)
|
||||
defer rows.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -60,30 +101,62 @@ func HashInit(pool *pgxpool.Pool, ctx context.Context) error {
|
|||
versions, exists := hashes[packID]
|
||||
|
||||
if !exists {
|
||||
temp := map[uint32]map[Region]string{}
|
||||
temp := map[uint32]HashesByRegion{}
|
||||
hashes[packID] = temp
|
||||
versions = temp
|
||||
}
|
||||
|
||||
regions, exists := versions[version]
|
||||
|
||||
if !exists {
|
||||
temp := map[Region]string{}
|
||||
versions[version] = temp
|
||||
regions = temp
|
||||
versions[version] = HashesByRegion{
|
||||
NTSCU: flattenBlank(hashNTSCU),
|
||||
NTSCJ: flattenBlank(hashNTSCJ),
|
||||
NTSCK: flattenBlank(hashNTSCK),
|
||||
PAL: flattenBlank(hashPAL),
|
||||
}
|
||||
|
||||
regions[R_NTSCU] = hashNTSCU
|
||||
regions[R_NTSCJ] = hashNTSCJ
|
||||
regions[R_NTSCK] = hashNTSCK
|
||||
regions[R_PAL] = hashPAL
|
||||
|
||||
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))
|
||||
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))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetHashes() HashStore {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
// Create a copy while the mutex is locked. Defer runs before return
|
||||
ret := hashes
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPackIDMissing = errors.New("The specified PackID does not exist")
|
||||
ErrVersionMissing = errors.New("The specified version does not exist")
|
||||
)
|
||||
|
||||
func RemoveHash(pool *pgxpool.Pool, ctx context.Context, packID uint32, version uint32) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if versions, exists := hashes[packID]; exists {
|
||||
if _, exists := versions[version]; exists {
|
||||
delete(versions, version)
|
||||
} else {
|
||||
return ErrVersionMissing
|
||||
}
|
||||
} else {
|
||||
return ErrPackIDMissing
|
||||
}
|
||||
|
||||
_, err := pool.Exec(ctx, DeleteHash, packID, version)
|
||||
if err != nil {
|
||||
logging.Error("DATABASE", "Failure to remove hash for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "Error:", err.Error())
|
||||
} else {
|
||||
logging.Warn("DATABASE", "Removed hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateHash(pool *pgxpool.Pool, ctx context.Context, packID uint32, version uint32, hashNTSCU string, hashNTSCJ string, hashNTSCK string, hashPAL string) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
|
@ -91,30 +164,24 @@ func UpdateHash(pool *pgxpool.Pool, ctx context.Context, packID uint32, version
|
|||
versions, exists := hashes[packID]
|
||||
|
||||
if !exists {
|
||||
temp := map[uint32]map[Region]string{}
|
||||
temp := map[uint32]HashesByRegion{}
|
||||
hashes[packID] = temp
|
||||
versions = temp
|
||||
}
|
||||
|
||||
regions, exists := versions[packID]
|
||||
|
||||
if !exists {
|
||||
temp := map[Region]string{}
|
||||
versions[packID] = temp
|
||||
regions = temp
|
||||
versions[version] = HashesByRegion{
|
||||
NTSCU: flattenBlank(hashNTSCU),
|
||||
NTSCJ: flattenBlank(hashNTSCJ),
|
||||
NTSCK: flattenBlank(hashNTSCK),
|
||||
PAL: flattenBlank(hashPAL),
|
||||
}
|
||||
|
||||
regions[R_NTSCU] = hashNTSCU
|
||||
regions[R_NTSCJ] = hashNTSCJ
|
||||
regions[R_NTSCK] = hashNTSCK
|
||||
regions[R_PAL] = hashPAL
|
||||
|
||||
_, err := pool.Exec(ctx, InsertHash, packID, version, hashNTSCU, hashNTSCJ, hashNTSCK, hashPAL)
|
||||
|
||||
if err != nil {
|
||||
logging.Error("DB", "Failed to update hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "error:", err.Error())
|
||||
logging.Error("DATABASE", "Failed to update hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version), "error:", err.Error())
|
||||
} else {
|
||||
logging.Info("DB", "Successfully updated hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version))
|
||||
logging.Info("DATABASE", "Successfully updated hashes for PackID:", aurora.Cyan(packID), "Version:", aurora.Cyan(version))
|
||||
}
|
||||
|
||||
return err
|
||||
|
|
@ -126,9 +193,9 @@ func ValidateHash(packID uint32, version uint32, region Region, hash string) boo
|
|||
|
||||
if versions, exists := hashes[packID]; exists {
|
||||
if regions, exists := versions[version]; exists {
|
||||
if hash_real, exists := regions[region]; exists {
|
||||
return hash_real != "" && hash_real == hash
|
||||
}
|
||||
hash_real := IndexByRegionByte(regions, region)
|
||||
|
||||
return hash_real != "" && hash_real == hash
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
14
nas/main.go
14
nas/main.go
|
|
@ -178,8 +178,18 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/api/hash" {
|
||||
api.HandleHash(w, r)
|
||||
if r.URL.Path == "/api/set_hash" {
|
||||
api.HandleSetHash(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/api/get_hash" {
|
||||
api.HandleGetHash(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/api/remove_hash" {
|
||||
api.HandleRemoveHash(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user