mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-05-06 05:26:33 -05:00
SAKE: Limit records and fields per profile
This commit is contained in:
parent
2418fbf37d
commit
0a5e354e61
|
|
@ -41,31 +41,37 @@ type SakeRecord struct {
|
|||
|
||||
const (
|
||||
getSakeRecordsQuery = `
|
||||
SELECT owner_id, record_id, fields
|
||||
FROM sake_records
|
||||
WHERE game_id = $1
|
||||
AND table_id = $2
|
||||
AND (cardinality($4::integer[]) = 0 OR record_id = ANY($4::integer[]))
|
||||
SELECT owner_id, record_id, fields
|
||||
FROM sake_records
|
||||
WHERE game_id = $1
|
||||
AND table_id = $2
|
||||
AND (cardinality($4::integer[]) = 0 OR record_id = ANY($4::integer[]))
|
||||
AND (cardinality($3::integer[]) = 0 OR owner_id = ANY($3::integer[]))`
|
||||
|
||||
updateSakeRecordQuery = `
|
||||
UPDATE sake_records
|
||||
SET
|
||||
fields = CASE WHEN owner_id = $4 THEN fields || $5 ELSE fields END,
|
||||
update_time = CASE WHEN owner_id = $4 THEN CURRENT_TIMESTAMP ELSE update_time END
|
||||
fields = CASE WHEN owner_id = $4 THEN fields || $5 ELSE fields END,
|
||||
update_time = CASE WHEN owner_id = $4 THEN CURRENT_TIMESTAMP ELSE update_time END
|
||||
WHERE game_id = $1
|
||||
AND table_id = $2
|
||||
AND record_id = $3
|
||||
AND record_id = $3
|
||||
RETURNING owner_id`
|
||||
|
||||
insertSakeRecordQuery = `
|
||||
INSERT INTO sake_records (game_id, table_id, owner_id, fields)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING record_id`
|
||||
|
||||
checkMaxSakeRecordsQuery = `
|
||||
SELECT COUNT(*)
|
||||
FROM sake_records
|
||||
WHERE owner_id = $1`
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSakeNotOwned = errors.New("record is not owned by the specified owner ID")
|
||||
ErrSakeNotOwned = errors.New("record is not owned by the specified owner ID")
|
||||
ErrSakeFieldLimitExceeded = errors.New("record has too many fields")
|
||||
)
|
||||
|
||||
func parseSakeFieldsFromJson(fieldsJson []byte) (map[string]SakeField, error) {
|
||||
|
|
@ -146,6 +152,10 @@ func UpdateSakeRecord(pool *pgxpool.Pool, ctx context.Context, record SakeRecord
|
|||
var existingOwnerId int32
|
||||
err = pool.QueryRow(ctx, updateSakeRecordQuery, record.GameId, record.TableId, record.RecordId, ownerId, fieldsJson).Scan(&existingOwnerId)
|
||||
if err != nil {
|
||||
var pgErr *pgconn.PgError
|
||||
if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.CheckViolation {
|
||||
return ErrSakeFieldLimitExceeded
|
||||
}
|
||||
return err
|
||||
}
|
||||
if ownerId != 0 && existingOwnerId != ownerId {
|
||||
|
|
@ -177,3 +187,12 @@ func InsertSakeRecord(pool *pgxpool.Pool, ctx context.Context, record SakeRecord
|
|||
}
|
||||
return recordId, err
|
||||
}
|
||||
|
||||
func IsMaxSakeRecordsReached(pool *pgxpool.Pool, ctx context.Context, profileId uint32, maxRecords int) (bool, error) {
|
||||
var count int
|
||||
err := pool.QueryRow(ctx, checkMaxSakeRecordsQuery, profileId).Scan(&count)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count >= maxRecords, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ import (
|
|||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxSakeRecordsPerProfile = 96
|
||||
MaxSakeFieldsPerRecord = 64
|
||||
MaxSakeFieldValueLength = 4096
|
||||
)
|
||||
|
||||
const (
|
||||
ResultSuccess = "Success"
|
||||
ResultSecretKeyInvalid = "SecretKeyInvalid"
|
||||
|
|
@ -248,26 +254,19 @@ func getRequestIdentity(moduleName string, request StorageRequestData) (uint32,
|
|||
return profileId, *gameInfo, ResultSuccess
|
||||
}
|
||||
|
||||
func getInputFields(moduleName string, request StorageRequestData) (map[string]database.SakeField, string) {
|
||||
record := database.SakeRecord{
|
||||
Fields: make(map[string]database.SakeField),
|
||||
}
|
||||
for _, field := range request.Values.RecordFields {
|
||||
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 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{
|
||||
|
|
@ -489,6 +488,35 @@ func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameI
|
|||
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 {
|
||||
|
|
@ -537,16 +565,18 @@ func fillValue(valueType database.SakeFieldType, value string) StorageValue {
|
|||
|
||||
func (body *StorageResponseBody) setResultTag(xmlName string, result string) {
|
||||
switch xmlName {
|
||||
case SakeNamespace + "/GetMyRecords":
|
||||
body.GetMyRecordsResponse = &StorageGetMyRecordsResponse{
|
||||
GetMyRecordsResult: result,
|
||||
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{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,12 +66,13 @@ ALTER TABLE public.users OWNER TO wiilink;
|
|||
--
|
||||
-- Name: sake_records; Type: TABLE; Schema: public; Owner: wiilink
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.sake_records (
|
||||
game_id integer NOT NULL,
|
||||
table_id character varying NOT NULL,
|
||||
record_id integer NOT NULL DEFAULT (random() * 2147483647)::integer,
|
||||
owner_id integer NOT NULL,
|
||||
fields jsonb NOT NULL,
|
||||
fields jsonb NOT NULL CHECK (jsonb_typeof(fields) = 'object' AND jsonb_array_length(fields) <= 64),
|
||||
create_time timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user