mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-05-07 23:44:25 -05:00
584 lines
20 KiB
Go
584 lines
20 KiB
Go
package sake
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"io"
|
|
"net/http"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
"wwfc/common"
|
|
"wwfc/database"
|
|
"wwfc/logging"
|
|
|
|
"github.com/jackc/pgx/v4"
|
|
"github.com/logrusorgru/aurora/v3"
|
|
)
|
|
|
|
const (
|
|
MaxSakeRecordsPerProfile = 96
|
|
MaxSakeFieldsPerRecord = 64
|
|
MaxSakeFieldValueLength = 4096
|
|
)
|
|
|
|
const (
|
|
ResultSuccess = "Success"
|
|
ResultSecretKeyInvalid = "SecretKeyInvalid"
|
|
ResultServiceDisabled = "ServiceDisabled"
|
|
ResultDatabaseUnavailable = "DatabaseUnavailable"
|
|
ResultLoginTicketInvalid = "LoginTicketInvalid"
|
|
ResultLoginTicketExpired = "LoginTicketExpired"
|
|
ResultTableNotFound = "TableNotFound"
|
|
ResultRecordNotFound = "RecordNotFound"
|
|
ResultFieldNotFound = "FieldNotFound"
|
|
ResultFieldTypeInvalid = "FieldTypeInvalid"
|
|
ResultNoPermission = "NoPermission"
|
|
ResultRecordLimitReached = "RecordLimitReached"
|
|
ResultAlreadyRated = "AlreadyRated"
|
|
ResultNotRateable = "NotRateable"
|
|
ResultNotOwned = "NotOwned"
|
|
ResultFilterInvalid = "FilterInvalid"
|
|
ResultSortInvalid = "SortInvalid"
|
|
ResultTargetFilterInvalid = "TargetFilterInvalid"
|
|
ResultUnknownError = "UnknownError"
|
|
)
|
|
|
|
const (
|
|
SOAPEnvNamespace = "http://schemas.xmlsoap.org/soap/envelope/"
|
|
SakeNamespace = "http://gamespy.net/sake"
|
|
)
|
|
|
|
type StorageRequestEnvelope struct {
|
|
XMLName xml.Name
|
|
Body StorageRequestBody
|
|
}
|
|
|
|
type StorageRequestBody struct {
|
|
XMLName xml.Name
|
|
Data StorageRequestData `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"`
|
|
}
|
|
|
|
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 {
|
|
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"`
|
|
}
|
|
|
|
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"`
|
|
}
|
|
|
|
type StorageResponseValues struct {
|
|
ArrayOfRecordValue []StorageArrayOfRecordValue `xml:"ArrayOfRecordValue"`
|
|
}
|
|
|
|
type StorageArrayOfRecordValue struct {
|
|
RecordValues []StorageRecordValue `xml:"RecordValue"`
|
|
}
|
|
|
|
type StorageCreateRecordResponse struct {
|
|
CreateRecordResult string
|
|
RecordID int32 `xml:"recordid"`
|
|
}
|
|
|
|
type StorageUpdateRecordResponse struct {
|
|
UpdateRecordResult string
|
|
}
|
|
|
|
type StorageGetMyRecordsResponse struct {
|
|
GetMyRecordsResult string
|
|
Values StorageResponseValues `xml:"values"`
|
|
}
|
|
|
|
type StorageSearchForRecordsResponse struct {
|
|
XMLName xml.Name
|
|
SearchForRecordsResult string
|
|
Values StorageResponseValues `xml:"values"`
|
|
}
|
|
|
|
var (
|
|
sakeTypeToTag = map[database.SakeFieldType]string{
|
|
database.SakeFieldTypeByte: "byteValue",
|
|
database.SakeFieldTypeShort: "shortValue",
|
|
database.SakeFieldTypeInt: "intValue",
|
|
database.SakeFieldTypeFloat: "floatValue",
|
|
database.SakeFieldTypeAsciiString: "asciiStringValue",
|
|
database.SakeFieldTypeUnicodeString: "unicodeStringValue",
|
|
database.SakeFieldTypeBoolean: "booleanValue",
|
|
database.SakeFieldTypeDateAndTime: "dateAndTimeValue",
|
|
database.SakeFieldTypeBinaryData: "binaryDataValue",
|
|
database.SakeFieldTypeInt64: "int64Value",
|
|
}
|
|
|
|
tagToSakeType = common.ReverseMap(sakeTypeToTag).(map[string]database.SakeFieldType)
|
|
|
|
storageRequestHandlers = map[string]func(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody{
|
|
SakeNamespace + "/CreateRecord": createRecord,
|
|
SakeNamespace + "/UpdateRecord": updateRecord,
|
|
SakeNamespace + "/GetMyRecords": getMyRecords,
|
|
SakeNamespace + "/SearchForRecords": searchForRecords,
|
|
}
|
|
)
|
|
|
|
func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
|
|
headerAction := r.Header.Get("SOAPAction")
|
|
if headerAction == "" {
|
|
logging.Error(moduleName, "No SOAPAction in header")
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
logging.Info("SAKE", "Received storage request with SOAPAction:", aurora.Yellow(headerAction), "and body:", aurora.Cyan(string(body)))
|
|
|
|
// Parse the SOAP request XML
|
|
soap := StorageRequestEnvelope{}
|
|
err = xml.Unmarshal(body, &soap)
|
|
if err != nil {
|
|
logging.Error(moduleName, "Received invalid XML")
|
|
return
|
|
}
|
|
|
|
response := StorageResponseEnvelope{
|
|
XMLName: xml.Name{Space: SOAPEnvNamespace, Local: "Envelope"},
|
|
}
|
|
|
|
xmlName := soap.Body.Data.XMLName.Space + "/" + soap.Body.Data.XMLName.Local
|
|
if headerAction == xmlName || headerAction == `"`+xmlName+`"` {
|
|
logging.Info(moduleName, "SOAPAction:", aurora.Yellow(soap.Body.Data.XMLName.Local))
|
|
|
|
handler, ok := storageRequestHandlers[xmlName]
|
|
if !ok {
|
|
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)
|
|
} else {
|
|
response.Body = handler(moduleName, profileId, gameInfo, soap.Body.Data)
|
|
}
|
|
} else {
|
|
logging.Error(moduleName, "Invalid SOAPAction or XML request:", aurora.Cyan(headerAction))
|
|
}
|
|
|
|
out, err := xml.Marshal(response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
logging.Info(moduleName, "Responding with body:", aurora.Cyan(string(out)))
|
|
|
|
payload := append([]byte(xml.Header), out...)
|
|
|
|
w.Header().Set("Content-Type", "text/xml")
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(payload)))
|
|
w.Write(payload)
|
|
}
|
|
|
|
func getRequestIdentity(moduleName string, request StorageRequestData) (uint32, common.GameInfo, string) {
|
|
gameInfo := common.GetGameInfoByID(request.GameID)
|
|
if gameInfo == nil {
|
|
logging.Error(moduleName, "Invalid game ID:", aurora.Cyan(request.GameID))
|
|
return 0, common.GameInfo{}, ResultDatabaseUnavailable
|
|
}
|
|
|
|
if gameInfo.SecretKey != request.SecretKey {
|
|
logging.Error(moduleName, "Mismatch", aurora.BrightCyan(gameInfo.Name), "secret key:", aurora.Cyan(request.SecretKey), "!=", aurora.BrightCyan(gameInfo.SecretKey))
|
|
return 0, common.GameInfo{}, ResultSecretKeyInvalid
|
|
}
|
|
|
|
profileId, issueTime, err := common.UnmarshalGPCMLoginTicket(request.LoginTicket)
|
|
if err != nil {
|
|
logging.Error(moduleName, err)
|
|
return 0, common.GameInfo{}, ResultLoginTicketInvalid
|
|
}
|
|
|
|
if issueTime.Add(48 * time.Hour).Before(time.Now()) {
|
|
return 0, common.GameInfo{}, ResultLoginTicketExpired
|
|
}
|
|
|
|
return profileId, *gameInfo, ResultSuccess
|
|
}
|
|
|
|
func createRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) 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{
|
|
CreateRecordResult: ResultDatabaseUnavailable,
|
|
}}
|
|
} else if reached {
|
|
logging.Error(moduleName, "Profile", aurora.Cyan(profileId), "has reached the maximum number of sake records")
|
|
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
|
|
CreateRecordResult: ResultRecordLimitReached,
|
|
}}
|
|
}
|
|
|
|
if request.TableID == "" {
|
|
logging.Error(moduleName, "No table ID provided")
|
|
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
|
|
CreateRecordResult: ResultTableNotFound,
|
|
}}
|
|
}
|
|
|
|
if gameInfo.Name == "mariokartwii" && (request.TableID == "GhostData" || request.TableID == "StoredGhostData") {
|
|
// Reserved for special handler
|
|
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
|
|
CreateRecordResult: ResultTableNotFound,
|
|
}}
|
|
}
|
|
|
|
var record database.SakeRecord
|
|
var result string
|
|
record.Fields, result = getInputFields(moduleName, request)
|
|
if result != ResultSuccess {
|
|
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
|
|
CreateRecordResult: result,
|
|
}}
|
|
}
|
|
|
|
record.GameId = gameInfo.GameID
|
|
record.TableId = request.TableID
|
|
record.RecordId = 0
|
|
record.OwnerId = int32(profileId)
|
|
|
|
// TODO: Limit number of records or fields a user can have
|
|
recordId, err := database.InsertSakeRecord(pool, ctx, record)
|
|
if err != nil {
|
|
return StorageResponseBody{CreateRecordResponse: &StorageCreateRecordResponse{
|
|
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{
|
|
CreateRecordResult: ResultSuccess,
|
|
RecordID: recordId,
|
|
}}
|
|
}
|
|
|
|
func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody {
|
|
if len(request.Fields.Fields) == 0 {
|
|
// GameSpy client doesn't consider zero fields valid
|
|
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
|
|
GetMyRecordsResult: "BadNumFields",
|
|
}}
|
|
}
|
|
if request.TableID == "" {
|
|
logging.Error(moduleName, "No table ID provided")
|
|
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
|
|
GetMyRecordsResult: ResultTableNotFound,
|
|
}}
|
|
}
|
|
|
|
records, err := database.GetSakeRecords(pool, ctx, gameInfo.GameID, []int32{int32(profileId)}, request.TableID, nil, request.Fields.Fields, 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{
|
|
GetMyRecordsResult: ResultRecordNotFound,
|
|
}}
|
|
}
|
|
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
|
|
GetMyRecordsResult: ResultDatabaseUnavailable,
|
|
}}
|
|
}
|
|
|
|
response := StorageGetMyRecordsResponse{
|
|
GetMyRecordsResult: ResultSuccess,
|
|
Values: fillResponseValues(records, request),
|
|
}
|
|
logging.Info(moduleName, "Returning", aurora.Cyan(len(records)), "records from table", aurora.Cyan(request.TableID), "for profile", aurora.Cyan(profileId))
|
|
|
|
return StorageResponseBody{GetMyRecordsResponse: &response}
|
|
}
|
|
|
|
func updateRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody {
|
|
if request.TableID == "" {
|
|
logging.Error(moduleName, "No table ID provided")
|
|
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
|
|
UpdateRecordResult: ResultTableNotFound,
|
|
}}
|
|
}
|
|
|
|
if gameInfo.Name == "mariokartwii" && (request.TableID == "GhostData" || request.TableID == "StoredGhostData") {
|
|
// Reserved for special handler
|
|
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
|
|
UpdateRecordResult: ResultTableNotFound,
|
|
}}
|
|
}
|
|
|
|
var record database.SakeRecord
|
|
var result string
|
|
record.Fields, result = getInputFields(moduleName, request)
|
|
if result != ResultSuccess {
|
|
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
|
|
UpdateRecordResult: result,
|
|
}}
|
|
}
|
|
|
|
record.GameId = gameInfo.GameID
|
|
record.TableId = request.TableID
|
|
record.RecordId = 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{
|
|
UpdateRecordResult: ResultNotOwned,
|
|
}}
|
|
}
|
|
if err == pgx.ErrNoRows {
|
|
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
|
|
UpdateRecordResult: ResultRecordNotFound,
|
|
}}
|
|
}
|
|
return StorageResponseBody{UpdateRecordResponse: &StorageUpdateRecordResponse{
|
|
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{
|
|
UpdateRecordResult: ResultSuccess,
|
|
}}
|
|
}
|
|
|
|
var searchRecordHandlers = map[string]func(string, StorageRequestData) (database.SakeRecord, bool){
|
|
"mariokartwii/GhostData": getMarioKartWiiGhostDataRecord,
|
|
"mariokartwii/StoredGhostData": getMarioKartWiiStoredGhostDataRecord,
|
|
}
|
|
|
|
var (
|
|
standardFilterRegex = regexp.MustCompile(`^ownerid\s*=\s*-?(\d{1,10})$`)
|
|
)
|
|
|
|
// TODO: Test this
|
|
func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestData) StorageResponseBody {
|
|
var records []database.SakeRecord
|
|
|
|
if handler, ok := searchRecordHandlers[gameInfo.Name+"/"+request.TableID]; ok {
|
|
record, ok := handler(moduleName, request)
|
|
if !ok {
|
|
return StorageResponseBody{SearchForRecordsResponse: &StorageSearchForRecordsResponse{
|
|
SearchForRecordsResult: ResultUnknownError,
|
|
}}
|
|
}
|
|
records = append(records, record)
|
|
} else {
|
|
filter := request.Filter
|
|
ownerIds := request.OwnerIDs.OwnerID
|
|
|
|
// Parse standard filter
|
|
if matches := standardFilterRegex.FindStringSubmatch(request.Filter); matches != nil {
|
|
ownerIdParsed, err := strconv.ParseInt(matches[1], 10, 32)
|
|
if err != nil {
|
|
logging.Error(moduleName, "Invalid owner ID in filter:", aurora.Cyan(request.Filter))
|
|
return StorageResponseBody{SearchForRecordsResponse: &StorageSearchForRecordsResponse{
|
|
SearchForRecordsResult: ResultFilterInvalid,
|
|
}}
|
|
}
|
|
ownerIds = append(ownerIds, int32(ownerIdParsed))
|
|
filter = ""
|
|
}
|
|
|
|
var err error
|
|
records, err = database.GetSakeRecords(pool, ctx, gameInfo.GameID, ownerIds, request.TableID, nil, request.Fields.Fields, filter)
|
|
if err != nil {
|
|
logging.Error(moduleName, "Failed to get sake records from the database:", err)
|
|
return StorageResponseBody{SearchForRecordsResponse: &StorageSearchForRecordsResponse{
|
|
SearchForRecordsResult: ResultDatabaseUnavailable,
|
|
}}
|
|
}
|
|
}
|
|
|
|
// Sort the records now
|
|
sort.Slice(records, func(l, r int) bool {
|
|
lVal, lExists := records[l].Fields[request.Sort]
|
|
rVal, rExists := records[r].Fields[request.Sort]
|
|
if !lExists || !rExists {
|
|
// Prioritises the one that exists or goes left if both false
|
|
return rExists
|
|
}
|
|
|
|
if lVal.Type != database.SakeFieldTypeInt || rVal.Type != database.SakeFieldTypeInt {
|
|
panic(aurora.Cyan(lVal.Type).String() + " used as sort value")
|
|
}
|
|
|
|
lValInt, err := strconv.ParseInt(lVal.Value, 10, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
rValInt, err := strconv.ParseInt(rVal.Value, 10, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return lValInt < rValInt
|
|
})
|
|
|
|
// Enforce the maximum number of records after sorting
|
|
if request.Max > 0 && len(records) > request.Max {
|
|
records = records[:request.Max]
|
|
}
|
|
|
|
response := StorageSearchForRecordsResponse{
|
|
SearchForRecordsResult: "Success",
|
|
Values: fillResponseValues(records, request),
|
|
}
|
|
logging.Info(moduleName, "Found", aurora.Cyan(len(records)), "records in table", aurora.Cyan(request.TableID), "for profile", aurora.Cyan(profileId), "with filter", aurora.Cyan(request.Filter))
|
|
|
|
return StorageResponseBody{SearchForRecordsResponse: &response}
|
|
}
|
|
|
|
func getInputFields(moduleName string, request StorageRequestData) (map[string]database.SakeField, string) {
|
|
record := database.SakeRecord{
|
|
Fields: make(map[string]database.SakeField),
|
|
}
|
|
if len(request.Values.RecordFields) > MaxSakeFieldsPerRecord {
|
|
logging.Error(moduleName, "Too many fields in record:", aurora.Cyan(len(request.Values.RecordFields)))
|
|
return nil, ResultFieldTypeInvalid
|
|
}
|
|
|
|
for _, field := range request.Values.RecordFields {
|
|
value := field.Value.Value.Value
|
|
if len(value) > MaxSakeFieldValueLength {
|
|
logging.Error(moduleName, "Field value too long for field", aurora.Cyan(field.Name), "with length", aurora.Cyan(len(value)))
|
|
return nil, ResultFieldTypeInvalid
|
|
}
|
|
fieldType, ok := tagToSakeType[field.Value.Value.XMLName.Local]
|
|
if !ok {
|
|
logging.Error(moduleName, "Invalid field type tag:", aurora.Cyan(field.Value.Value.XMLName.Local))
|
|
return nil, ResultFieldTypeInvalid
|
|
}
|
|
if field.Name == "ownerid" || field.Name == "recordid" || field.Name == "gameid" || field.Name == "tableid" {
|
|
logging.Error(moduleName, "Attempt to set reserved field:", aurora.Cyan(field.Name))
|
|
return nil, ResultNoPermission
|
|
}
|
|
record.Fields[field.Name] = database.SakeField{Type: fieldType, Value: field.Value.Value.Value}
|
|
}
|
|
return record.Fields, ResultSuccess
|
|
}
|
|
|
|
func fillResponseValues(records []database.SakeRecord, request StorageRequestData) StorageResponseValues {
|
|
var response StorageResponseValues
|
|
for _, record := range records {
|
|
valueArray := StorageArrayOfRecordValue{}
|
|
for _, field := range request.Fields.Fields {
|
|
if field == "ownerid" {
|
|
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: &StorageValue{
|
|
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{
|
|
XMLName: xml.Name{Local: "intValue"},
|
|
Value: strconv.FormatInt(int64(int32(record.RecordId)), 10),
|
|
}})
|
|
continue
|
|
}
|
|
|
|
var fieldValue *database.SakeField
|
|
for name, value := range record.Fields {
|
|
if name == field {
|
|
fieldValue = &value
|
|
break
|
|
}
|
|
}
|
|
if fieldValue == nil {
|
|
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: nil})
|
|
} else {
|
|
value := fillValue(fieldValue.Type, fieldValue.Value)
|
|
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: &value})
|
|
}
|
|
}
|
|
response.ArrayOfRecordValue = append(response.ArrayOfRecordValue, valueArray)
|
|
}
|
|
return response
|
|
}
|
|
|
|
func fillValue(valueType database.SakeFieldType, value string) StorageValue {
|
|
return StorageValue{
|
|
XMLName: xml.Name{Local: sakeTypeToTag[valueType]},
|
|
Value: value,
|
|
}
|
|
}
|
|
|
|
func (body *StorageResponseBody) setResultTag(xmlName string, result string) {
|
|
switch xmlName {
|
|
case SakeNamespace + "/CreateRecord":
|
|
body.CreateRecordResponse = &StorageCreateRecordResponse{
|
|
CreateRecordResult: result,
|
|
}
|
|
case SakeNamespace + "/UpdateRecord":
|
|
body.UpdateRecordResponse = &StorageUpdateRecordResponse{
|
|
UpdateRecordResult: result,
|
|
}
|
|
case SakeNamespace + "/GetMyRecords":
|
|
body.GetMyRecordsResponse = &StorageGetMyRecordsResponse{
|
|
GetMyRecordsResult: result,
|
|
}
|
|
case SakeNamespace + "/SearchForRecords":
|
|
body.SearchForRecordsResponse = &StorageSearchForRecordsResponse{}
|
|
}
|
|
}
|