Files
browser/platform/domain/scum.go
T
npc0-hue a822e9250a Store SCUM facts in typed platform tables
Run pushes typed SCUM facts through POST /api/v1/run/scum/facts, but the
production router rejected that path before the signature middleware because
runServiceRequest listed individual Run channel prefixes, and MySQL kept SCUM
rows inside the whole metadata snapshot instead of platform tables.

- /api/v1/run/ is now the signed machine channel space while
  /api/v1/run/endpoints keeps normal bearer/admin authorization.
- MySQL gets real scum_user, scum_user_trajectory, scum_vehicle,
  scum_vehicle_trajectory and scum_vehicle_lock tables with parameterized
  per-row repositories instead of full snapshot rewrites. Snapshot-shaped
  tables from the unreleased interim build are replaced, and SCUM rows still
  inside a metadata snapshot are migrated once.
- Facts ingest verifies the target server plugin type and converges stale
  online users to offline after SCUMUserOfflineAfter.
- The plugin page and browser read one bounded /scum/surface response instead
  of five list calls per refresh.

Call-count budget for one server: per 5s facts batch, one SELECT plus one
INSERT/UPDATE per reported user and vehicle, one INSERT per moved trajectory
sample or new lock row, one bounded stale-user SELECT, and a trajectory
retention DELETE at most once per hour. One browser refresh issues one surface
request every 15s instead of five list requests.
2026-09-15 12:12:11 +08:00

318 lines
7.2 KiB
Go

package domain
import "time"
const (
SCUMUserOfflineAfter = 15 * time.Minute
SCUMWelcomeMessageDelay = time.Minute
SCUMDefaultListLimit = 500
SCUMDefaultTrajectoryLimit = 500
)
type SCUMPosition struct {
X float64
Y float64
Z float64
}
type SCUMUser struct {
ID string
ServerInstanceID string
SteamID string
DisplayName string
SquadID int64
RegisteredAt time.Time
LastLoginAt time.Time
LastActivityAt time.Time
LastLoginIP string
Online bool
X *float64
Y *float64
Z *float64
BankBalance *int64
GoldBars *int64
RiddenVehicleID string
GameVehicleID string
LastLoginAge time.Duration
WelcomeQueuedAt time.Time
WelcomeLastError string
CreatedAt time.Time
UpdatedAt time.Time
}
type SCUMUserFilter struct {
ServerInstanceID string
SteamID string
Online *bool
ChangedAfter time.Time
// StaleBefore matches users whose latest activity is older than the given
// instant. It backs the offline convergence query for stale online users.
StaleBefore time.Time
Limit int
}
type SCUMUserTrajectory struct {
ID string
ServerInstanceID string
SCUMUserID string
SteamID string
DisplayName string
X float64
Y float64
Z float64
RiddenVehicleID string
GameVehicleID string
ObservedAt time.Time
CreatedAt time.Time
}
type SCUMUserTrajectoryFilter struct {
ServerInstanceID string
SCUMUserID string
SteamID string
After time.Time
Limit int
}
type SCUMVehicle struct {
ID string
ServerInstanceID string
GameVehicleID string
VehicleClass string
DisplayName string
Exists bool
Locked bool
X *float64
Y *float64
Z *float64
LastObservedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type SCUMVehicleFilter struct {
ServerInstanceID string
GameVehicleID string
Exists *bool
ChangedAfter time.Time
Limit int
}
type SCUMVehicleTrajectory struct {
ID string
ServerInstanceID string
SCUMVehicleID string
GameVehicleID string
VehicleClass string
X float64
Y float64
Z float64
ObservedAt time.Time
CreatedAt time.Time
}
type SCUMVehicleTrajectoryFilter struct {
ServerInstanceID string
SCUMVehicleID string
GameVehicleID string
After time.Time
Limit int
}
type SCUMVehicleLock struct {
ID string
ServerInstanceID string
SCUMVehicleID string
GameVehicleID string
SCUMUserID string
SteamID string
LockedAt time.Time
CreatedAt time.Time
}
type SCUMVehicleLockFilter struct {
ServerInstanceID string
SCUMVehicleID string
GameVehicleID string
SCUMUserID string
SteamID string
After time.Time
Limit int
}
type SCUMSurface struct {
Users []SCUMUser
Vehicles []SCUMVehicle
UserTrajectories []SCUMUserTrajectory
VehicleTrajectories []SCUMVehicleTrajectory
VehicleLocks []SCUMVehicleLock
}
type SCUMFactIngest struct {
RunEndpointID string
SessionToken string
ServerInstanceID string
Users []SCUMUserFact
Vehicles []SCUMVehicleFact
}
type SCUMUserFact struct {
SteamID string
DisplayName string
SquadID *int64
LoginIP string
Online bool
Login bool
ObservedAt time.Time
LoginObservedAt time.Time
Position *SCUMPosition
BankBalance *int64
GoldBars *int64
RiddenGameVehicleID string
}
type SCUMVehicleFact struct {
GameVehicleID string
VehicleClass string
DisplayName string
Exists *bool
Locked *bool
LockedBySteamID string
LockedAt time.Time
ObservedAt time.Time
Position *SCUMPosition
}
type SCUMFactIngestResult struct {
Accepted bool
AcceptedUserCount int
AcceptedVehicleCount int
WelcomeQueuedCount int
ServerTime time.Time
}
func CopySCUMUser(value SCUMUser) SCUMUser {
value.X = copyFloatPtr(value.X)
value.Y = copyFloatPtr(value.Y)
value.Z = copyFloatPtr(value.Z)
value.BankBalance = copyInt64Ptr(value.BankBalance)
value.GoldBars = copyInt64Ptr(value.GoldBars)
return value
}
func CopySCUMUsers(values []SCUMUser) []SCUMUser {
if values == nil {
return nil
}
out := make([]SCUMUser, len(values))
for i, value := range values {
out[i] = CopySCUMUser(value)
}
return out
}
func CopySCUMUserTrajectory(value SCUMUserTrajectory) SCUMUserTrajectory { return value }
func CopySCUMUserTrajectories(values []SCUMUserTrajectory) []SCUMUserTrajectory {
if values == nil {
return nil
}
out := make([]SCUMUserTrajectory, len(values))
copy(out, values)
return out
}
func CopySCUMVehicle(value SCUMVehicle) SCUMVehicle {
value.X = copyFloatPtr(value.X)
value.Y = copyFloatPtr(value.Y)
value.Z = copyFloatPtr(value.Z)
return value
}
func CopySCUMVehicles(values []SCUMVehicle) []SCUMVehicle {
if values == nil {
return nil
}
out := make([]SCUMVehicle, len(values))
for i, value := range values {
out[i] = CopySCUMVehicle(value)
}
return out
}
func CopySCUMVehicleTrajectory(value SCUMVehicleTrajectory) SCUMVehicleTrajectory { return value }
func CopySCUMVehicleTrajectories(values []SCUMVehicleTrajectory) []SCUMVehicleTrajectory {
if values == nil {
return nil
}
out := make([]SCUMVehicleTrajectory, len(values))
copy(out, values)
return out
}
func CopySCUMVehicleLock(value SCUMVehicleLock) SCUMVehicleLock { return value }
func CopySCUMVehicleLocks(values []SCUMVehicleLock) []SCUMVehicleLock {
if values == nil {
return nil
}
out := make([]SCUMVehicleLock, len(values))
copy(out, values)
return out
}
func CopySCUMFactIngest(value SCUMFactIngest) SCUMFactIngest {
value.Users = CopySCUMUserFacts(value.Users)
value.Vehicles = CopySCUMVehicleFacts(value.Vehicles)
return value
}
func CopySCUMUserFacts(values []SCUMUserFact) []SCUMUserFact {
if values == nil {
return nil
}
out := make([]SCUMUserFact, len(values))
for i, value := range values {
out[i] = value
out[i].SquadID = copyInt64Ptr(value.SquadID)
out[i].BankBalance = copyInt64Ptr(value.BankBalance)
out[i].GoldBars = copyInt64Ptr(value.GoldBars)
if value.Position != nil {
position := *value.Position
out[i].Position = &position
}
}
return out
}
func CopySCUMVehicleFacts(values []SCUMVehicleFact) []SCUMVehicleFact {
if values == nil {
return nil
}
out := make([]SCUMVehicleFact, len(values))
for i, value := range values {
out[i] = value
out[i].Exists = copyBoolPtr(value.Exists)
out[i].Locked = copyBoolPtr(value.Locked)
if value.Position != nil {
position := *value.Position
out[i].Position = &position
}
}
return out
}
func copyInt64Ptr(value *int64) *int64 {
if value == nil {
return nil
}
copy := *value
return &copy
}
func copyBoolPtr(value *bool) *bool {
if value == nil {
return nil
}
copy := *value
return &copy
}