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.
307 lines
16 KiB
Go
307 lines
16 KiB
Go
package dto
|
|
|
|
import (
|
|
"time"
|
|
|
|
"browser.local/platform/domain"
|
|
)
|
|
|
|
type SCUMPositionBody struct {
|
|
X float64 `json:"x"`
|
|
Y float64 `json:"y"`
|
|
Z float64 `json:"z"`
|
|
}
|
|
|
|
type SCUMUserResponse struct {
|
|
ID string `json:"id"`
|
|
ServerInstanceID string `json:"serverInstanceId"`
|
|
SteamID string `json:"steamId"`
|
|
GamePlayerID string `json:"gamePlayerId"`
|
|
DisplayName string `json:"displayName,omitempty"`
|
|
SquadID int64 `json:"squadId,omitempty"`
|
|
RegisteredAt time.Time `json:"registeredAt"`
|
|
LastLoginAt time.Time `json:"lastLoginAt"`
|
|
LastSeenAt time.Time `json:"lastSeenAt"`
|
|
LastActivityAt time.Time `json:"lastActivityAt"`
|
|
LastLoginAgeSeconds int64 `json:"lastLoginAgeSeconds,omitempty"`
|
|
LastLoginIP string `json:"lastLoginIp,omitempty"`
|
|
LoginIP string `json:"loginIp,omitempty"`
|
|
Online bool `json:"online"`
|
|
X *float64 `json:"x,omitempty"`
|
|
Y *float64 `json:"y,omitempty"`
|
|
Z *float64 `json:"z,omitempty"`
|
|
Position *SCUMPositionBody `json:"position,omitempty"`
|
|
BankBalance *int64 `json:"bankBalance,omitempty"`
|
|
NormalBalance *int64 `json:"normalBalance,omitempty"`
|
|
GoldBars *int64 `json:"goldBars,omitempty"`
|
|
GoldBalance *int64 `json:"goldBalance,omitempty"`
|
|
RiddenVehicleID string `json:"riddenVehicleId,omitempty"`
|
|
GameVehicleID string `json:"gameVehicleId,omitempty"`
|
|
Source string `json:"source"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
type SCUMUserListResponse struct {
|
|
Items []SCUMUserResponse `json:"items"`
|
|
Count int `json:"count"`
|
|
}
|
|
|
|
type SCUMUserTrajectoryResponse struct {
|
|
ID string `json:"id"`
|
|
ServerInstanceID string `json:"serverInstanceId"`
|
|
SCUMUserID string `json:"scumUserId"`
|
|
SubjectType string `json:"subjectType"`
|
|
SubjectID string `json:"subjectId"`
|
|
SteamID string `json:"steamId"`
|
|
DisplayName string `json:"displayName,omitempty"`
|
|
X float64 `json:"x"`
|
|
Y float64 `json:"y"`
|
|
Z float64 `json:"z"`
|
|
RiddenVehicleID string `json:"riddenVehicleId,omitempty"`
|
|
GameVehicleID string `json:"gameVehicleId,omitempty"`
|
|
ObservedAt time.Time `json:"observedAt"`
|
|
SampledAt time.Time `json:"sampledAt"`
|
|
Source string `json:"source"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
type SCUMUserTrajectoryListResponse struct {
|
|
Items []SCUMUserTrajectoryResponse `json:"items"`
|
|
Count int `json:"count"`
|
|
}
|
|
|
|
type SCUMVehicleResponse struct {
|
|
ID string `json:"id"`
|
|
ServerInstanceID string `json:"serverInstanceId"`
|
|
VehicleID string `json:"vehicleId"`
|
|
GameVehicleID string `json:"gameVehicleId"`
|
|
EntityID string `json:"entityId"`
|
|
VehicleClass string `json:"vehicleClass,omitempty"`
|
|
ClassName string `json:"className,omitempty"`
|
|
DisplayName string `json:"displayName,omitempty"`
|
|
Label string `json:"label,omitempty"`
|
|
Exists bool `json:"exists"`
|
|
Status string `json:"status"`
|
|
Locked bool `json:"locked"`
|
|
X *float64 `json:"x,omitempty"`
|
|
Y *float64 `json:"y,omitempty"`
|
|
Z *float64 `json:"z,omitempty"`
|
|
Position *SCUMPositionBody `json:"position,omitempty"`
|
|
LastObservedAt time.Time `json:"lastObservedAt"`
|
|
VehicleObservedAt time.Time `json:"vehicleObservedAt"`
|
|
Source string `json:"source"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
type SCUMVehicleListResponse struct {
|
|
Items []SCUMVehicleResponse `json:"items"`
|
|
Count int `json:"count"`
|
|
}
|
|
|
|
type SCUMVehicleTrajectoryResponse struct {
|
|
ID string `json:"id"`
|
|
ServerInstanceID string `json:"serverInstanceId"`
|
|
SCUMVehicleID string `json:"scumVehicleId"`
|
|
SubjectType string `json:"subjectType"`
|
|
SubjectID string `json:"subjectId"`
|
|
VehicleID string `json:"vehicleId"`
|
|
GameVehicleID string `json:"gameVehicleId"`
|
|
VehicleClass string `json:"vehicleClass,omitempty"`
|
|
ClassName string `json:"className,omitempty"`
|
|
X float64 `json:"x"`
|
|
Y float64 `json:"y"`
|
|
Z float64 `json:"z"`
|
|
ObservedAt time.Time `json:"observedAt"`
|
|
SampledAt time.Time `json:"sampledAt"`
|
|
Source string `json:"source"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
type SCUMVehicleTrajectoryListResponse struct {
|
|
Items []SCUMVehicleTrajectoryResponse `json:"items"`
|
|
Count int `json:"count"`
|
|
}
|
|
|
|
type SCUMVehicleLockResponse struct {
|
|
ID string `json:"id"`
|
|
ServerInstanceID string `json:"serverInstanceId"`
|
|
SCUMVehicleID string `json:"scumVehicleId"`
|
|
GameVehicleID string `json:"gameVehicleId"`
|
|
SCUMUserID string `json:"scumUserId"`
|
|
SteamID string `json:"steamId"`
|
|
LockedAt time.Time `json:"lockedAt"`
|
|
Source string `json:"source"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
type SCUMVehicleLockListResponse struct {
|
|
Items []SCUMVehicleLockResponse `json:"items"`
|
|
Count int `json:"count"`
|
|
}
|
|
|
|
type SCUMSurfaceResponse struct {
|
|
Users SCUMUserListResponse `json:"users"`
|
|
Vehicles SCUMVehicleListResponse `json:"vehicles"`
|
|
UserTrajectories SCUMUserTrajectoryListResponse `json:"userTrajectories"`
|
|
VehicleTrajectories SCUMVehicleTrajectoryListResponse `json:"vehicleTrajectories"`
|
|
VehicleLocks SCUMVehicleLockListResponse `json:"vehicleLocks"`
|
|
}
|
|
|
|
func SCUMSurfaceFromDomain(value domain.SCUMSurface) SCUMSurfaceResponse {
|
|
return SCUMSurfaceResponse{
|
|
Users: SCUMUserListFromDomain(value.Users), Vehicles: SCUMVehicleListFromDomain(value.Vehicles),
|
|
UserTrajectories: SCUMUserTrajectoryListFromDomain(value.UserTrajectories),
|
|
VehicleTrajectories: SCUMVehicleTrajectoryListFromDomain(value.VehicleTrajectories),
|
|
VehicleLocks: SCUMVehicleLockListFromDomain(value.VehicleLocks),
|
|
}
|
|
}
|
|
|
|
type SCUMFactIngestRequest struct {
|
|
RunEndpointID string `json:"runEndpointId"`
|
|
SessionToken string `json:"sessionToken"`
|
|
ServerInstanceID string `json:"serverInstanceId"`
|
|
Users []SCUMUserFactBody `json:"users,omitempty"`
|
|
Vehicles []SCUMVehicleFactBody `json:"vehicles,omitempty"`
|
|
}
|
|
|
|
type SCUMUserFactBody struct {
|
|
SteamID string `json:"steamId"`
|
|
DisplayName string `json:"displayName,omitempty"`
|
|
SquadID *int64 `json:"squadId,omitempty"`
|
|
LoginIP string `json:"loginIp,omitempty"`
|
|
Online bool `json:"online"`
|
|
Login bool `json:"login,omitempty"`
|
|
ObservedAt time.Time `json:"observedAt,omitempty"`
|
|
LoginObservedAt time.Time `json:"loginObservedAt,omitempty"`
|
|
Position *SCUMPositionBody `json:"position,omitempty"`
|
|
BankBalance *int64 `json:"bankBalance,omitempty"`
|
|
GoldBars *int64 `json:"goldBars,omitempty"`
|
|
RiddenGameVehicleID string `json:"riddenGameVehicleId,omitempty"`
|
|
}
|
|
|
|
type SCUMVehicleFactBody struct {
|
|
GameVehicleID string `json:"gameVehicleId"`
|
|
VehicleClass string `json:"vehicleClass,omitempty"`
|
|
DisplayName string `json:"displayName,omitempty"`
|
|
Exists *bool `json:"exists,omitempty"`
|
|
Locked *bool `json:"locked,omitempty"`
|
|
LockedBySteamID string `json:"lockedBySteamId,omitempty"`
|
|
LockedAt time.Time `json:"lockedAt,omitempty"`
|
|
ObservedAt time.Time `json:"observedAt,omitempty"`
|
|
Position *SCUMPositionBody `json:"position,omitempty"`
|
|
}
|
|
|
|
type SCUMFactIngestResponse struct {
|
|
Accepted bool `json:"accepted"`
|
|
AcceptedUserCount int `json:"acceptedUserCount"`
|
|
AcceptedVehicleCount int `json:"acceptedVehicleCount"`
|
|
WelcomeQueuedCount int `json:"welcomeQueuedCount"`
|
|
ServerTime time.Time `json:"serverTime"`
|
|
}
|
|
|
|
func SCUMUserListFromDomain(values []domain.SCUMUser) SCUMUserListResponse {
|
|
items := make([]SCUMUserResponse, len(values))
|
|
for i, value := range values {
|
|
items[i] = SCUMUserFromDomain(value)
|
|
}
|
|
return SCUMUserListResponse{Items: items, Count: len(items)}
|
|
}
|
|
|
|
func SCUMUserFromDomain(value domain.SCUMUser) SCUMUserResponse {
|
|
value = domain.CopySCUMUser(value)
|
|
return SCUMUserResponse{ID: value.ID, ServerInstanceID: value.ServerInstanceID, SteamID: value.SteamID, GamePlayerID: value.SteamID, DisplayName: value.DisplayName, SquadID: value.SquadID, RegisteredAt: value.RegisteredAt, LastLoginAt: value.LastLoginAt, LastSeenAt: value.LastActivityAt, LastActivityAt: value.LastActivityAt, LastLoginAgeSeconds: int64(value.LastLoginAge.Seconds()), LastLoginIP: value.LastLoginIP, LoginIP: value.LastLoginIP, Online: value.Online, X: value.X, Y: value.Y, Z: value.Z, Position: positionFromPointers(value.X, value.Y, value.Z), BankBalance: value.BankBalance, NormalBalance: value.BankBalance, GoldBars: value.GoldBars, GoldBalance: value.GoldBars, RiddenVehicleID: value.RiddenVehicleID, GameVehicleID: value.GameVehicleID, Source: "platform.scum_user", CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt}
|
|
}
|
|
|
|
func SCUMUserTrajectoryListFromDomain(values []domain.SCUMUserTrajectory) SCUMUserTrajectoryListResponse {
|
|
items := make([]SCUMUserTrajectoryResponse, len(values))
|
|
for i, value := range values {
|
|
items[i] = SCUMUserTrajectoryFromDomain(value)
|
|
}
|
|
return SCUMUserTrajectoryListResponse{Items: items, Count: len(items)}
|
|
}
|
|
|
|
func SCUMUserTrajectoryFromDomain(value domain.SCUMUserTrajectory) SCUMUserTrajectoryResponse {
|
|
return SCUMUserTrajectoryResponse{ID: value.ID, ServerInstanceID: value.ServerInstanceID, SCUMUserID: value.SCUMUserID, SubjectType: "player", SubjectID: value.SteamID, SteamID: value.SteamID, DisplayName: value.DisplayName, X: value.X, Y: value.Y, Z: value.Z, RiddenVehicleID: value.RiddenVehicleID, GameVehicleID: value.GameVehicleID, ObservedAt: value.ObservedAt, SampledAt: value.ObservedAt, Source: "platform.scum_user_trajectory", CreatedAt: value.CreatedAt}
|
|
}
|
|
|
|
func SCUMVehicleListFromDomain(values []domain.SCUMVehicle) SCUMVehicleListResponse {
|
|
items := make([]SCUMVehicleResponse, len(values))
|
|
for i, value := range values {
|
|
items[i] = SCUMVehicleFromDomain(value)
|
|
}
|
|
return SCUMVehicleListResponse{Items: items, Count: len(items)}
|
|
}
|
|
|
|
func SCUMVehicleFromDomain(value domain.SCUMVehicle) SCUMVehicleResponse {
|
|
value = domain.CopySCUMVehicle(value)
|
|
status := "missing"
|
|
if value.Exists {
|
|
status = "exists"
|
|
}
|
|
return SCUMVehicleResponse{ID: value.ID, ServerInstanceID: value.ServerInstanceID, VehicleID: value.ID, GameVehicleID: value.GameVehicleID, EntityID: value.GameVehicleID, VehicleClass: value.VehicleClass, ClassName: value.VehicleClass, DisplayName: value.DisplayName, Label: value.DisplayName, Exists: value.Exists, Status: status, Locked: value.Locked, X: value.X, Y: value.Y, Z: value.Z, Position: positionFromPointers(value.X, value.Y, value.Z), LastObservedAt: value.LastObservedAt, VehicleObservedAt: value.LastObservedAt, Source: "platform.scum_vehicle", CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt}
|
|
}
|
|
|
|
func SCUMVehicleTrajectoryListFromDomain(values []domain.SCUMVehicleTrajectory) SCUMVehicleTrajectoryListResponse {
|
|
items := make([]SCUMVehicleTrajectoryResponse, len(values))
|
|
for i, value := range values {
|
|
items[i] = SCUMVehicleTrajectoryFromDomain(value)
|
|
}
|
|
return SCUMVehicleTrajectoryListResponse{Items: items, Count: len(items)}
|
|
}
|
|
|
|
func SCUMVehicleTrajectoryFromDomain(value domain.SCUMVehicleTrajectory) SCUMVehicleTrajectoryResponse {
|
|
return SCUMVehicleTrajectoryResponse{ID: value.ID, ServerInstanceID: value.ServerInstanceID, SCUMVehicleID: value.SCUMVehicleID, SubjectType: "vehicle", SubjectID: value.GameVehicleID, VehicleID: value.SCUMVehicleID, GameVehicleID: value.GameVehicleID, VehicleClass: value.VehicleClass, ClassName: value.VehicleClass, X: value.X, Y: value.Y, Z: value.Z, ObservedAt: value.ObservedAt, SampledAt: value.ObservedAt, Source: "platform.scum_vehicle_trajectory", CreatedAt: value.CreatedAt}
|
|
}
|
|
|
|
func SCUMVehicleLockListFromDomain(values []domain.SCUMVehicleLock) SCUMVehicleLockListResponse {
|
|
items := make([]SCUMVehicleLockResponse, len(values))
|
|
for i, value := range values {
|
|
items[i] = SCUMVehicleLockFromDomain(value)
|
|
}
|
|
return SCUMVehicleLockListResponse{Items: items, Count: len(items)}
|
|
}
|
|
|
|
func SCUMVehicleLockFromDomain(value domain.SCUMVehicleLock) SCUMVehicleLockResponse {
|
|
return SCUMVehicleLockResponse{ID: value.ID, ServerInstanceID: value.ServerInstanceID, SCUMVehicleID: value.SCUMVehicleID, GameVehicleID: value.GameVehicleID, SCUMUserID: value.SCUMUserID, SteamID: value.SteamID, LockedAt: value.LockedAt, Source: "platform.scum_vehicle_lock", CreatedAt: value.CreatedAt}
|
|
}
|
|
|
|
func (request SCUMFactIngestRequest) ToDomain() domain.SCUMFactIngest {
|
|
users := make([]domain.SCUMUserFact, len(request.Users))
|
|
for i, user := range request.Users {
|
|
users[i] = user.ToDomain()
|
|
}
|
|
vehicles := make([]domain.SCUMVehicleFact, len(request.Vehicles))
|
|
for i, vehicle := range request.Vehicles {
|
|
vehicles[i] = vehicle.ToDomain()
|
|
}
|
|
return domain.SCUMFactIngest{RunEndpointID: request.RunEndpointID, SessionToken: request.SessionToken, ServerInstanceID: request.ServerInstanceID, Users: users, Vehicles: vehicles}
|
|
}
|
|
|
|
func (request SCUMUserFactBody) ToDomain() domain.SCUMUserFact {
|
|
return domain.SCUMUserFact{SteamID: request.SteamID, DisplayName: request.DisplayName, SquadID: request.SquadID, LoginIP: request.LoginIP, Online: request.Online, Login: request.Login, ObservedAt: request.ObservedAt, LoginObservedAt: request.LoginObservedAt, Position: positionToDomain(request.Position), BankBalance: request.BankBalance, GoldBars: request.GoldBars, RiddenGameVehicleID: request.RiddenGameVehicleID}
|
|
}
|
|
|
|
func (request SCUMVehicleFactBody) ToDomain() domain.SCUMVehicleFact {
|
|
return domain.SCUMVehicleFact{GameVehicleID: request.GameVehicleID, VehicleClass: request.VehicleClass, DisplayName: request.DisplayName, Exists: request.Exists, Locked: request.Locked, LockedBySteamID: request.LockedBySteamID, LockedAt: request.LockedAt, ObservedAt: request.ObservedAt, Position: positionToDomain(request.Position)}
|
|
}
|
|
|
|
func SCUMFactIngestFromDomain(result domain.SCUMFactIngestResult) SCUMFactIngestResponse {
|
|
return SCUMFactIngestResponse{Accepted: result.Accepted, AcceptedUserCount: result.AcceptedUserCount, AcceptedVehicleCount: result.AcceptedVehicleCount, WelcomeQueuedCount: result.WelcomeQueuedCount, ServerTime: result.ServerTime}
|
|
}
|
|
|
|
func positionToDomain(value *SCUMPositionBody) *domain.SCUMPosition {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
return &domain.SCUMPosition{X: value.X, Y: value.Y, Z: value.Z}
|
|
}
|
|
|
|
func positionFromPointers(x, y, z *float64) *SCUMPositionBody {
|
|
if x == nil || y == nil || z == nil {
|
|
return nil
|
|
}
|
|
return &SCUMPositionBody{X: *x, Y: *y, Z: *z}
|
|
}
|