Sake: Fill in lots of new soap values
Some checks failed
Build CI / build (push) Has been cancelled

This commit is contained in:
Palapeli
2026-04-04 05:42:23 -04:00
parent 7245d1779c
commit d70b0ee335
4 changed files with 188 additions and 171 deletions

View File

@@ -14,25 +14,3 @@ const (
SakeFileResultFileTooLarge = 5
SakeFileResultServerError = 6
)
const (
ResultSuccess = "Success" // 4xx51
ResultSecretKeyInvalid = "SecretKeyInvalid" // 4xx52
ResultServiceDisabled = "ServiceDisabled" // 4xx53
ResultDatabaseUnavailable = "DatabaseUnavailable" // 4xx58
ResultLoginTicketInvalid = "LoginTicketInvalid" // 4xx59
ResultLoginTicketExpired = "LoginTicketExpired" // 4xx60
ResultTableNotFound = "TableNotFound" // 4xx61
ResultRecordNotFound = "RecordNotFound" // 4xx62
ResultFieldNotFound = "FieldNotFound" // 4xx63
ResultFieldTypeInvalid = "FieldTypeInvalid" // 4xx64
ResultNoPermission = "NoPermission" // 4xx65
ResultRecordLimitReached = "RecordLimitReached" // 4xx66
ResultAlreadyRated = "AlreadyRated" // 4xx67
ResultNotRateable = "NotRateable" // 4xx68
ResultNotOwned = "NotOwned" // 4xx69
ResultFilterInvalid = "FilterInvalid" // 4xx70
ResultSortInvalid = "SortInvalid" // 4xx71
ResultTargetFilterInvalid = "TargetFilterInvalid" // 4xx80
ResultUnknownError = "UnknownError" // 4xx72
)

View File

@@ -35,7 +35,7 @@ var (
ghostDataFilterRegex = regexp.MustCompile(`^course = ([1-9]\d?|0) and gameid = 1687 and time < ([1-9][0-9]{0,5})$`)
)
func getMarioKartWiiGhostDataRecord(moduleName string, request StorageRequestData) ([]database.SakeRecord, bool) {
func getMarioKartWiiGhostDataRecord(moduleName string, request StorageRequestCommon) ([]database.SakeRecord, bool) {
if request.Sort != "time desc" {
logging.Error(moduleName, "mariokartwii/GhostData: Invalid sort string:", aurora.Cyan(request.Sort))
return []database.SakeRecord{}, false
@@ -56,12 +56,12 @@ func getMarioKartWiiGhostDataRecord(moduleName string, request StorageRequestDat
return []database.SakeRecord{}, false
}
if len(request.OwnerIDs.OwnerID) != 0 {
if len(request.OwnerIDs.Int) != 0 {
logging.Error(moduleName, "mariokartwii/GhostData: Invalid owner id array:", aurora.Cyan(request.OwnerIDs))
return []database.SakeRecord{}, false
}
if request.CacheFlag != 0 {
if request.CacheFlag {
logging.Error(moduleName, "mariokartwii/GhostData: Invalid cache value:", aurora.Cyan(request.CacheFlag))
return []database.SakeRecord{}, false
}
@@ -105,7 +105,7 @@ func getMarioKartWiiGhostDataRecord(moduleName string, request StorageRequestDat
}}, true
}
func getMarioKartWiiStoredGhostDataRecord(moduleName string, request StorageRequestData) ([]database.SakeRecord, bool) {
func getMarioKartWiiStoredGhostDataRecord(moduleName string, request StorageRequestCommon) ([]database.SakeRecord, bool) {
if request.Sort != "time" {
logging.Error(moduleName, "mariokartwii/StoredGhostData: Invalid sort string:", aurora.Cyan(request.Sort))
return []database.SakeRecord{}, false
@@ -126,12 +126,12 @@ func getMarioKartWiiStoredGhostDataRecord(moduleName string, request StorageRequ
return []database.SakeRecord{}, false
}
if len(request.OwnerIDs.OwnerID) != 0 {
if len(request.OwnerIDs.Int) != 0 {
logging.Error(moduleName, "mariokartwii/StoredGhostData: Invalid owner id array:", aurora.Cyan(request.OwnerIDs))
return []database.SakeRecord{}, false
}
if request.CacheFlag != 0 {
if request.CacheFlag {
logging.Error(moduleName, "mariokartwii/StoredGhostData: Invalid cache value:", aurora.Cyan(request.CacheFlag))
return []database.SakeRecord{}, false
}
@@ -453,7 +453,7 @@ func isPlayerInfoValid(playerInfoString string) (string, bool) {
return fixedPlayerInfoString, true
}
func filterMarioKartWiiFriendInfo(value string, isOwner bool) (string, string) {
func filterMarioKartWiiFriendInfo(value string, isOwner bool) (string, Result) {
// Clear personal info from the Mii data before sending to the client. This
// cannot be done before inserting into the database because the client
// expects the server to return the exact copy of what it has previously updated.

View File

@@ -20,100 +20,135 @@ const (
SakeNamespace = "http://gamespy.net/sake"
)
const (
ResultSuccess = Result("Success") // 4xx51
ResultDatabaseUnavailable = Result("DatabaseUnavailable") // 4xx58
ResultLoginTicketInvalid = Result("LoginTicketInvalid") // 4xx59
ResultLoginTicketExpired = Result("LoginTicketExpired") // 4xx60
ResultSecretKeyInvalid = Result("SecretKeyInvalid") // 4xx52
ResultTableNotFound = Result("TableNotFound") // 4xx61
ResultRecordNotFound = Result("RecordNotFound") // 4xx62
ResultFieldNotFound = Result("FieldNotFound") // 4xx63
ResultFieldTypeInvalid = Result("FieldTypeInvalid") // 4xx64
ResultNoPermission = Result("NoPermission") // 4xx65
ResultRecordLimitReached = Result("RecordLimitReached") // 4xx66
ResultAlreadyRated = Result("AlreadyRated") // 4xx67
ResultNotRateable = Result("NotRateable") // 4xx68
ResultNotOwned = Result("NotOwned") // 4xx69
ResultFilterInvalid = Result("FilterInvalid") // 4xx70
ResultSortInvalid = Result("SortInvalid") // 4xx71
ResultTargetFilterInvalid = Result("TargetFilterInvalid") // 4xx80
ResultUnknownError = Result("UnknownError") // 4xx72
ResultAlreadyReported = Result("AlreadyReported") // 4xx72
ResultNotModerated = Result("NotModerated") // 4xx72
ResultCategoryInvalid = Result("CategoryInvalid") // 4xx72
ResultDuplicateRecord = Result("DuplicateRecord") // 4xx72
ResultServiceDisabled = Result("ServiceDisabled") // 4xx53
)
type Result string
type StorageRequestEnvelope struct {
XMLName xml.Name
Body StorageRequestBody
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
Body StorageRequestBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
}
type StorageRequestBody struct {
XMLName xml.Name
Data StorageRequestData `xml:",any"`
Data StorageRequestCommon `xml:",any"`
}
type StorageRequestData struct {
XMLName xml.Name
GameID int `xml:"gameid"`
SecretKey string `xml:"secretKey"`
LoginTicket string `xml:"loginTicket"`
TableID string `xml:"tableid"`
RecordID int32 `xml:"recordid"`
Filter string `xml:"filter"`
Sort string `xml:"sort"`
Offset int `xml:"offset"`
Max int `xml:"max"`
Surrounding int `xml:"surrounding"`
OwnerIDs StorageOwnerIDs `xml:"ownerids"`
CacheFlag int `xml:"cacheFlag"`
Fields StorageFields `xml:"fields"`
Values StorageUpdateRecordValues `xml:"values"`
// Combined struct for all of the different operations:
// CreateRecord, UpdateRecord, DeleteRecord, SearchForRecords, GetMyRecords
// GetSpecificRecords, GetRandomRecords, GetRecordCount, RateRecord, GetRecordLimit
type StorageRequestCommon struct {
XMLName xml.Name
GameID int32 `xml:"gameid"`
LoginTicket string `xml:"loginTicket"`
SecretKey string `xml:"secretKey"`
TableID string `xml:"tableid"`
RecordID int32 `xml:"recordid"`
Fields ArrayOfString `xml:"fields"`
Filter string `xml:"filter"`
Sort string `xml:"sort"`
Offset int32 `xml:"offset"`
Max int32 `xml:"max"`
TargetFilter string `xml:"targetFilter"`
Surrounding int32 `xml:"surrounding"`
OwnerIDs ArrayOfInt `xml:"ownerids"`
CacheFlag bool `xml:"cacheFlag"`
Rating byte `xml:"rating"`
Values ArrayOfRecordField `xml:"values"`
}
type StorageFields struct {
XMLName xml.Name
Fields []string `xml:"string"`
}
type StorageUpdateRecordValues struct {
RecordFields []StorageRecordField `xml:"RecordField"`
}
type StorageRecordField struct {
Name string `xml:"name"`
Value StorageRecordValue `xml:"value"`
}
type StorageRecordValue struct {
XMLName xml.Name
Value *StorageValue `xml:",any"`
}
type StorageValue struct {
// Combined struct for all of the different value types:
// ByteValue, ShortValue, IntValue, FloatValue, AsciiStringValue
// UnicodeStringValue, BooleanValue, DateAndTimeValue, BinaryDataValue
// Int64Value
type CommonValue struct {
XMLName xml.Name
Value string `xml:"value"`
}
type StorageOwnerIDs struct {
OwnerID []int32 `xml:"int"`
}
type StorageResponseEnvelope struct {
XMLName xml.Name
Body StorageResponseBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
XMLName xml.Name `xml:"s:Envelope"`
NamespaceSoap string `xml:"xmlns:s,attr"`
Body StorageResponseBody `xml:"s:Body"`
}
type StorageResponseBody struct {
CreateRecordResponse *StorageCreateRecordResponse `xml:"http://gamespy.net/sake CreateRecordResponse"`
UpdateRecordResponse *StorageUpdateRecordResponse `xml:"http://gamespy.net/sake UpdateRecordResponse"`
GetMyRecordsResponse *StorageGetMyRecordsResponse `xml:"http://gamespy.net/sake GetMyRecordsResponse"`
SearchForRecordsResponse *StorageSearchForRecordsResponse `xml:"http://gamespy.net/sake SearchForRecordsResponse"`
CreateRecordResponse *CreateRecordResponse `xml:"http://gamespy.net/sake CreateRecordResponse"`
UpdateRecordResponse *UpdateRecordResponse `xml:"http://gamespy.net/sake UpdateRecordResponse"`
GetMyRecordsResponse *GetMyRecordsResponse `xml:"http://gamespy.net/sake GetMyRecordsResponse"`
SearchForRecordsResponse *SearchForRecordsResponse `xml:"http://gamespy.net/sake SearchForRecordsResponse"`
}
type StorageResponseValues struct {
ArrayOfRecordValue []StorageArrayOfRecordValue `xml:"ArrayOfRecordValue"`
}
type StorageArrayOfRecordValue struct {
RecordValues []StorageRecordValue `xml:"RecordValue"`
}
type StorageCreateRecordResponse struct {
CreateRecordResult string
type CreateRecordResponse struct {
CreateRecordResult Result
RecordID int32 `xml:"recordid"`
}
type StorageUpdateRecordResponse struct {
UpdateRecordResult string
type UpdateRecordResponse struct {
UpdateRecordResult Result
}
type StorageGetMyRecordsResponse struct {
GetMyRecordsResult string
Values StorageResponseValues `xml:"values"`
type GetMyRecordsResponse struct {
GetMyRecordsResult Result
Values ArrayOfArrayOfRecordValue `xml:"values"`
}
type StorageSearchForRecordsResponse struct {
XMLName xml.Name
SearchForRecordsResult string
Values StorageResponseValues `xml:"values"`
type SearchForRecordsResponse struct {
SearchForRecordsResult Result
Values ArrayOfArrayOfRecordValue `xml:"values"`
}
type RecordField struct {
Name string `xml:"name"`
Value RecordValue `xml:"value"`
}
type RecordValue struct {
Value CommonValue `xml:",any"`
}
type ArrayOfString struct {
String []string `xml:"string"`
}
type ArrayOfInt struct {
Int []int32 `xml:"int"`
}
type ArrayOfRecordValue struct {
RecordValues []RecordValue `xml:"RecordValue"`
}
type ArrayOfArrayOfRecordValue struct {
ArrayOfRecordValue []ArrayOfRecordValue `xml:"ArrayOfRecordValue"`
}
type ArrayOfRecordField struct {
RecordFields []RecordField `xml:"RecordField"`
}
var (
@@ -132,7 +167,7 @@ var (
tagToSakeType = common.ReverseMap(sakeTypeToTag).(map[string]database.SakeFieldType)
storageRequestHandlers = map[string]func(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody{
storageRequestHandlers = map[string]func(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestCommon) StorageResponseBody{
SakeNamespace + "/CreateRecord": createRecord,
SakeNamespace + "/UpdateRecord": updateRecord,
SakeNamespace + "/GetMyRecords": getMyRecords,
@@ -163,7 +198,7 @@ func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Requ
}
response := StorageResponseEnvelope{
XMLName: xml.Name{Space: SOAPEnvNamespace, Local: "Envelope"},
NamespaceSoap: SOAPEnvNamespace,
}
xmlName := soap.Body.Data.XMLName.Space + "/" + soap.Body.Data.XMLName.Local
@@ -175,10 +210,10 @@ func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Requ
panic("unknown SOAPAction: " + aurora.Cyan(xmlName).String())
}
profileId, gameInfo, errorString := getRequestIdentity(moduleName, soap.Body.Data)
if errorString != ResultSuccess {
logging.Error(moduleName, "Failed to get request identity:", aurora.Cyan(errorString))
response.Body.setResultTag(xmlName, errorString)
profileId, gameInfo, result := getRequestIdentity(moduleName, soap.Body.Data)
if result != ResultSuccess {
logging.Error(moduleName, "Failed to get request identity:", aurora.Cyan(result))
response.Body.setResultTag(xmlName, result)
} else {
response.Body = handler(moduleName, profileId, gameInfo, soap.Body.Data)
}
@@ -200,8 +235,8 @@ func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Requ
w.Write(payload)
}
func getRequestIdentity(moduleName string, request StorageRequestData) (uint32, common.GameInfo, string) {
gameInfo := common.GetGameInfoByID(request.GameID)
func getRequestIdentity(moduleName string, request StorageRequestCommon) (uint32, common.GameInfo, Result) {
gameInfo := common.GetGameInfoByID(int(request.GameID))
if gameInfo == nil {
logging.Error(moduleName, "Invalid game ID:", aurora.Cyan(request.GameID))
return 0, common.GameInfo{}, ResultDatabaseUnavailable
@@ -225,22 +260,22 @@ func getRequestIdentity(moduleName string, request StorageRequestData) (uint32,
return profileId, *gameInfo, ResultSuccess
}
func createRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody {
func createRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestCommon) StorageResponseBody {
if reached, err := database.IsMaxSakeRecordsReached(pool, ctx, profileId, MaxSakeRecordsPerProfile); err != nil {
logging.Error(moduleName, "Failed to check max sake records:", err)
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{
CreateRecordResult: ResultDatabaseUnavailable,
}}
} else if reached {
logging.Error(moduleName, "Profile", aurora.Cyan(profileId), "has reached the maximum number of sake records")
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{
CreateRecordResult: ResultRecordLimitReached,
}}
}
if request.TableID == "" {
logging.Error(moduleName, "No table ID provided")
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{
CreateRecordResult: ResultTableNotFound,
}}
}
@@ -249,23 +284,23 @@ func createRecord(moduleName string, profileId uint32, gameInfo common.GameInfo,
if table != nil && table.Reserved {
// Reserved for special handler
logging.Error(moduleName, "Attempt to create record in reserved table", aurora.Cyan(request.TableID), "in game", aurora.BrightCyan(gameInfo.Name))
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{
CreateRecordResult: ResultNoPermission,
}}
}
if !table.AllowsPublicCreate() {
logging.Error(moduleName, "Attempt to create record in table that doesn't allow public create", aurora.Cyan(request.TableID), "in game", aurora.BrightCyan(gameInfo.Name))
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{
CreateRecordResult: ResultNoPermission,
}}
}
var record database.SakeRecord
var result string
var result Result
record.Fields, result = getInputFields(moduleName, request, table, true)
if result != ResultSuccess {
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{
CreateRecordResult: result,
}}
}
@@ -279,29 +314,29 @@ func createRecord(moduleName string, profileId uint32, gameInfo common.GameInfo,
recordId, err := database.InsertSakeRecord(pool, ctx, record)
if err != nil {
logging.Error(moduleName, "Failed to insert sake record into the database:", err)
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{
CreateRecordResult: ResultDatabaseUnavailable,
}}
}
logging.Info(moduleName, "Created record in table", aurora.Cyan(record.TableId), "with ID", aurora.Cyan(recordId), "for profile", aurora.Cyan(profileId))
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{
CreateRecordResult: ResultSuccess,
RecordID: recordId,
}}
}
func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody {
if len(request.Fields.Fields) == 0 {
func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestCommon) StorageResponseBody {
if len(request.Fields.String) == 0 {
// GameSpy client doesn't consider zero fields valid
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
return StorageResponseBody{GetMyRecordsResponse: &GetMyRecordsResponse{
GetMyRecordsResult: "BadNumFields",
}}
}
if request.TableID == "" {
logging.Error(moduleName, "No table ID provided")
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
return StorageResponseBody{GetMyRecordsResponse: &GetMyRecordsResponse{
GetMyRecordsResult: ResultTableNotFound,
}}
}
@@ -309,26 +344,26 @@ func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo,
table := GetTable(gameInfo.Name, request.TableID)
if table != nil && table.Reserved {
logging.Error(moduleName, "Attempt to get my records from reserved table", aurora.Cyan(request.TableID))
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
return StorageResponseBody{GetMyRecordsResponse: &GetMyRecordsResponse{
GetMyRecordsResult: ResultTableNotFound,
}}
}
records, err := database.GetSakeRecords(pool, ctx, gameInfo.GameID, []int32{int32(profileId)}, request.TableID, nil, request.Fields.Fields, request.Filter)
records, err := database.GetSakeRecords(pool, ctx, gameInfo.GameID, []int32{int32(profileId)}, request.TableID, nil, request.Fields.String, request.Filter)
if err != nil {
logging.Error(moduleName, "Failed to get sake records from the database:", err)
if err == pgx.ErrNoRows {
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
return StorageResponseBody{GetMyRecordsResponse: &GetMyRecordsResponse{
GetMyRecordsResult: ResultRecordNotFound,
}}
}
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
return StorageResponseBody{GetMyRecordsResponse: &GetMyRecordsResponse{
GetMyRecordsResult: ResultDatabaseUnavailable,
}}
}
responseValues, result := fillResponseValues(moduleName, profileId, table, records, request)
response := StorageGetMyRecordsResponse{
response := GetMyRecordsResponse{
GetMyRecordsResult: result,
Values: responseValues,
}
@@ -337,10 +372,10 @@ func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo,
return StorageResponseBody{GetMyRecordsResponse: &response}
}
func updateRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody {
func updateRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestCommon) StorageResponseBody {
if request.TableID == "" {
logging.Error(moduleName, "No table ID provided")
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
return StorageResponseBody{UpdateRecordResponse: &UpdateRecordResponse{
UpdateRecordResult: ResultTableNotFound,
}}
}
@@ -349,63 +384,63 @@ func updateRecord(moduleName string, profileId uint32, gameInfo common.GameInfo,
if table != nil && table.Reserved {
// Reserved for special handler
logging.Error(moduleName, "Attempt to update record in reserved table", aurora.Cyan(request.TableID), "in game", aurora.BrightCyan(gameInfo.Name))
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
return StorageResponseBody{UpdateRecordResponse: &UpdateRecordResponse{
UpdateRecordResult: ResultNoPermission,
}}
}
if !table.AllowsOwnerUpdate() {
logging.Error(moduleName, "Attempt to update record in table that doesn't allow owner update", aurora.Cyan(request.TableID), "in game", aurora.BrightCyan(gameInfo.Name))
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
return StorageResponseBody{UpdateRecordResponse: &UpdateRecordResponse{
UpdateRecordResult: ResultNoPermission,
}}
}
var record database.SakeRecord
var result string
var result Result
record.Fields, result = getInputFields(moduleName, request, table, false)
if result != ResultSuccess {
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
return StorageResponseBody{UpdateRecordResponse: &UpdateRecordResponse{
UpdateRecordResult: result,
}}
}
record.GameId = gameInfo.GameID
record.TableId = request.TableID
record.RecordId = request.RecordID
record.RecordId = int32(request.RecordID)
record.OwnerId = int32(profileId)
err := database.UpdateSakeRecord(pool, ctx, record, int32(profileId))
if err != nil {
logging.Error(moduleName, "Failed to update sake record in the database:", err)
if err == database.ErrSakeNotOwned {
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
return StorageResponseBody{UpdateRecordResponse: &UpdateRecordResponse{
UpdateRecordResult: ResultNotOwned,
}}
}
if err == pgx.ErrNoRows {
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
return StorageResponseBody{UpdateRecordResponse: &UpdateRecordResponse{
UpdateRecordResult: ResultRecordNotFound,
}}
}
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
return StorageResponseBody{UpdateRecordResponse: &UpdateRecordResponse{
UpdateRecordResult: ResultDatabaseUnavailable,
}}
}
logging.Info(moduleName, "Updated record", aurora.Cyan(record.RecordId), "in table", aurora.Cyan(record.TableId), "for profile", aurora.Cyan(profileId))
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
return StorageResponseBody{UpdateRecordResponse: &UpdateRecordResponse{
UpdateRecordResult: ResultSuccess,
}}
}
func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody {
func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestCommon) StorageResponseBody {
var records []database.SakeRecord
if request.TableID == "" {
logging.Error(moduleName, "No table ID provided")
return StorageResponseBody{SearchForRecordsResponse: &StorageSearchForRecordsResponse{
return StorageResponseBody{SearchForRecordsResponse: &SearchForRecordsResponse{
SearchForRecordsResult: ResultTableNotFound,
}}
}
@@ -416,26 +451,26 @@ func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameI
var ok bool
records, ok = table.SearchForRecordsHandler(moduleName, request)
if !ok {
return StorageResponseBody{SearchForRecordsResponse: &StorageSearchForRecordsResponse{
return StorageResponseBody{SearchForRecordsResponse: &SearchForRecordsResponse{
SearchForRecordsResult: ResultUnknownError,
}}
}
} else if table != nil && table.Reserved {
logging.Error(moduleName, "Attempt to search for records in reserved table", aurora.Cyan(request.TableID))
return StorageResponseBody{SearchForRecordsResponse: &StorageSearchForRecordsResponse{
return StorageResponseBody{SearchForRecordsResponse: &SearchForRecordsResponse{
SearchForRecordsResult: ResultTableNotFound,
}}
} else {
ownerIds := request.OwnerIDs.OwnerID
ownerIds := request.OwnerIDs.Int
if !table.AllowsPublicRead() {
ownerIds = []int32{int32(profileId)}
}
var err error
records, err = database.GetSakeRecords(pool, ctx, gameInfo.GameID, ownerIds, request.TableID, nil, request.Fields.Fields, request.Filter)
records, err = database.GetSakeRecords(pool, ctx, gameInfo.GameID, ownerIds, request.TableID, nil, request.Fields.String, request.Filter)
if err != nil {
logging.Error(moduleName, "Failed to get sake records from the database:", err)
return StorageResponseBody{SearchForRecordsResponse: &StorageSearchForRecordsResponse{
return StorageResponseBody{SearchForRecordsResponse: &SearchForRecordsResponse{
SearchForRecordsResult: ResultDatabaseUnavailable,
}}
}
@@ -467,12 +502,12 @@ func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameI
})
// Enforce the maximum number of records after sorting
if request.Max > 0 && len(records) > request.Max {
if request.Max > 0 && len(records) > int(request.Max) {
records = records[:request.Max]
}
responseValues, result := fillResponseValues(moduleName, profileId, table, records, request)
response := StorageSearchForRecordsResponse{
response := SearchForRecordsResponse{
SearchForRecordsResult: result,
Values: responseValues,
}
@@ -481,7 +516,7 @@ func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameI
return StorageResponseBody{SearchForRecordsResponse: &response}
}
func getInputFields(moduleName string, request StorageRequestData, table *SakeTable, useDefault bool) (map[string]database.SakeField, string) {
func getInputFields(moduleName string, request StorageRequestCommon, table *SakeTable, useDefault bool) (map[string]database.SakeField, Result) {
if len(request.Values.RecordFields) > MaxSakeFieldsPerRecord {
logging.Error(moduleName, "Too many fields in record:", aurora.Cyan(len(request.Values.RecordFields)))
return nil, ResultFieldTypeInvalid
@@ -512,7 +547,7 @@ func getInputFields(moduleName string, request StorageRequestData, table *SakeTa
logging.Error(moduleName, "Invalid value for field", aurora.Cyan(field.Name).String()+":", aurora.Cyan(value))
return nil, result
}
var result string
var result Result
sakeField.Value, result = table.FilterFieldFromClient(field.Name, value)
if result != ResultSuccess {
logging.Error(moduleName, "Failed to filter from client value for field", aurora.Cyan(field.Name), ":", aurora.Cyan(result))
@@ -523,20 +558,20 @@ func getInputFields(moduleName string, request StorageRequestData, table *SakeTa
return fields, ResultSuccess
}
func fillResponseValues(moduleName string, profileId uint32, table *SakeTable, records []database.SakeRecord, request StorageRequestData) (StorageResponseValues, string) {
var response StorageResponseValues
func fillResponseValues(moduleName string, profileId uint32, table *SakeTable, records []database.SakeRecord, request StorageRequestCommon) (ArrayOfArrayOfRecordValue, Result) {
var response ArrayOfArrayOfRecordValue
for _, record := range records {
valueArray := StorageArrayOfRecordValue{}
for _, field := range request.Fields.Fields {
valueArray := ArrayOfRecordValue{}
for _, field := range request.Fields.String {
if field == "ownerid" {
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: &StorageValue{
valueArray.RecordValues = append(valueArray.RecordValues, RecordValue{Value: CommonValue{
XMLName: xml.Name{Local: "intValue"},
Value: strconv.FormatInt(int64(int32(record.OwnerId)), 10),
}})
continue
}
if field == "recordid" {
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: &StorageValue{
valueArray.RecordValues = append(valueArray.RecordValues, RecordValue{Value: CommonValue{
XMLName: xml.Name{Local: "intValue"},
Value: strconv.FormatInt(int64(int32(record.RecordId)), 10),
}})
@@ -551,46 +586,50 @@ func fillResponseValues(moduleName string, profileId uint32, table *SakeTable, r
}
}
if fieldValue == nil {
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: nil})
continue
logging.Warn(moduleName, "Field", aurora.Cyan(field), "not found in record", aurora.Cyan(record.RecordId))
valueArray = ArrayOfRecordValue{}
break
}
var result string
var result Result
fieldValue.Value, result = table.FilterFieldFromDatabase(field, fieldValue.Value, record.OwnerId == int32(profileId))
if result != ResultSuccess {
logging.Error(moduleName, "Failed to filter to client value for field", aurora.Cyan(field), ":", aurora.Cyan(result))
return StorageResponseValues{}, result
return ArrayOfArrayOfRecordValue{}, result
}
value := fillValue(fieldValue.Type, fieldValue.Value)
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: &value})
valueArray.RecordValues = append(valueArray.RecordValues, RecordValue{Value: value})
}
if len(valueArray.RecordValues) == 0 {
continue
}
response.ArrayOfRecordValue = append(response.ArrayOfRecordValue, valueArray)
}
return response, ResultSuccess
}
func fillValue(valueType database.SakeFieldType, value string) StorageValue {
return StorageValue{
func fillValue(valueType database.SakeFieldType, value string) CommonValue {
return CommonValue{
XMLName: xml.Name{Local: sakeTypeToTag[valueType]},
Value: value,
}
}
func (body *StorageResponseBody) setResultTag(xmlName string, result string) {
func (body *StorageResponseBody) setResultTag(xmlName string, result Result) {
switch xmlName {
case SakeNamespace + "/CreateRecord":
body.CreateRecordResponse = &StorageCreateRecordResponse{
body.CreateRecordResponse = &CreateRecordResponse{
CreateRecordResult: result,
}
case SakeNamespace + "/UpdateRecord":
body.UpdateRecordResponse = &StorageUpdateRecordResponse{
body.UpdateRecordResponse = &UpdateRecordResponse{
UpdateRecordResult: result,
}
case SakeNamespace + "/GetMyRecords":
body.GetMyRecordsResponse = &StorageGetMyRecordsResponse{
body.GetMyRecordsResponse = &GetMyRecordsResponse{
GetMyRecordsResult: result,
}
case SakeNamespace + "/SearchForRecords":
body.SearchForRecordsResponse = &StorageSearchForRecordsResponse{}
body.SearchForRecordsResponse = &SearchForRecordsResponse{}
}
}

View File

@@ -51,9 +51,9 @@ type SakeFieldDefinition struct {
// Optional function for custom validation.
IsValidFunc func(value string) bool
// Optional function for custom filtering. This function receives the value from the client AFTER validation, before inserting into the database.
FilterFromClientFunc func(value string, isOwner bool) (string, string)
FilterFromClientFunc func(value string, isOwner bool) (string, Result)
// Optional function for custom filtering. This function receives the value from the database before sending to the client.
FilterFromDatabaseFunc func(value string, isOwner bool) (string, string)
FilterFromDatabaseFunc func(value string, isOwner bool) (string, Result)
}
type SakeTable struct {
@@ -76,7 +76,7 @@ type SakeTable struct {
// If true, Sake will return a NoPermission error for requests that don't have a custom handler
Reserved bool
// Custom handler for SearchForRecords. Returns an array of response Sake records.
SearchForRecordsHandler func(string, StorageRequestData) ([]database.SakeRecord, bool)
SearchForRecordsHandler func(string, StorageRequestCommon) ([]database.SakeRecord, bool)
// Field definitions for this table. The key is the field name.
Fields map[string]SakeFieldDefinition
}
@@ -288,7 +288,7 @@ func (t *SakeTable) GetDefaultFields() map[string]database.SakeField {
return defaultFields
}
func (t *SakeTable) CheckValidField(fieldName string, field database.SakeField) string {
func (t *SakeTable) CheckValidField(fieldName string, field database.SakeField) Result {
lengthLimit := MaxSakeFieldValueLength
var verifyFunc func(value string) bool
if t != nil && len(t.Fields) != 0 {
@@ -389,7 +389,7 @@ func (t *SakeTable) CheckValidField(fieldName string, field database.SakeField)
return ResultSuccess
}
func (t *SakeTable) FilterFieldFromClient(fieldName string, value string) (string, string) {
func (t *SakeTable) FilterFieldFromClient(fieldName string, value string) (string, Result) {
if t == nil || t.Fields == nil {
return value, ResultSuccess
}
@@ -403,7 +403,7 @@ func (t *SakeTable) FilterFieldFromClient(fieldName string, value string) (strin
return fieldDef.FilterFromClientFunc(value, true)
}
func (t *SakeTable) FilterFieldFromDatabase(fieldName string, value string, isOwner bool) (string, string) {
func (t *SakeTable) FilterFieldFromDatabase(fieldName string, value string, isOwner bool) (string, Result) {
if t == nil || t.Fields == nil {
return value, ResultSuccess
}