Fill the SCUM user and vehicle tables from the plugin-declared database read path
The SCUM 用户管理 list stayed empty because the typed scum_user and scum_vehicle tables had no producer: the run ingest endpoint is signed-run only, and the previously registered plugin templates asked for a Run database capability the endpoint never advertises. The plugin now declares bounded, read-only SQLite projections for players and vehicles (sql/scum-db-v57/*.sql with query schemas), and the platform dispatches those templates as durable remote.run.db.sqlite.query jobs on run heartbeat and page open, then projects the returned rows into the typed SCUM tables through the same shared ingest path used by signed run facts.
This commit is contained in:
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -151,6 +152,9 @@ func (svc *CoreService) GetSCUMSurfaceForSession(sessionID, serverInstanceID str
|
||||
if _, err := svc.reconcileSCUMUserPresence(serverInstanceID, svc.now()); err != nil {
|
||||
return domain.SCUMSurface{}, err
|
||||
}
|
||||
if err := svc.ReconcileSCUMQueryTemplates(serverInstanceID); err != nil {
|
||||
log.Printf("SCUM query template dispatch skipped server=%s error=%s", serverInstanceID, err.Error())
|
||||
}
|
||||
users, err := svc.store.SCUMUsers().List(domain.SCUMUserFilter{ServerInstanceID: serverInstanceID, Limit: domain.SCUMDefaultListLimit})
|
||||
if err != nil {
|
||||
return domain.SCUMSurface{}, err
|
||||
@@ -222,9 +226,6 @@ func (svc *CoreService) IngestSCUMFacts(batch domain.SCUMFactIngest) (domain.SCU
|
||||
if len(batch.Users) == 0 && len(batch.Vehicles) == 0 {
|
||||
return domain.SCUMFactIngestResult{}, validationError("SCUM facts are required")
|
||||
}
|
||||
if len(batch.Users) > 1000 || len(batch.Vehicles) > 1000 {
|
||||
return domain.SCUMFactIngestResult{}, validationError("SCUM fact batch is too large")
|
||||
}
|
||||
if err := svc.validateRunSession(batch.RunEndpointID, batch.SessionToken); err != nil {
|
||||
return domain.SCUMFactIngestResult{}, err
|
||||
}
|
||||
@@ -242,7 +243,24 @@ func (svc *CoreService) IngestSCUMFacts(batch domain.SCUMFactIngest) (domain.SCU
|
||||
if !strings.EqualFold(strings.TrimSpace(plugin.ServerType), "scum") {
|
||||
return domain.SCUMFactIngestResult{}, ErrForbidden
|
||||
}
|
||||
stamp := svc.now()
|
||||
return svc.ingestSCUMFactsForInstance(instance, batch, svc.now())
|
||||
}
|
||||
|
||||
// ingestSCUMFactsForInstance applies one already-authorized fact batch to the
|
||||
// typed SCUM tables. Both the signed run ingest channel and the plugin-declared
|
||||
// database projection path converge here.
|
||||
//
|
||||
// Call-count budget for one batch: one bounded stale-user convergence read per
|
||||
// instance, then one read plus one insert/update per reported user and vehicle,
|
||||
// and at most one trajectory sample per moved subject.
|
||||
func (svc *CoreService) ingestSCUMFactsForInstance(instance domain.ServerInstance, batch domain.SCUMFactIngest, stamp time.Time) (domain.SCUMFactIngestResult, error) {
|
||||
batch = domain.CopySCUMFactIngest(batch)
|
||||
if len(batch.Users) == 0 && len(batch.Vehicles) == 0 {
|
||||
return domain.SCUMFactIngestResult{}, validationError("SCUM facts are required")
|
||||
}
|
||||
if len(batch.Users) > domain.SCUMFactBatchLimit || len(batch.Vehicles) > domain.SCUMFactBatchLimit {
|
||||
return domain.SCUMFactIngestResult{}, validationError("SCUM fact batch is too large")
|
||||
}
|
||||
if _, err := svc.reconcileSCUMUserPresence(instance.ID, stamp); err != nil {
|
||||
return domain.SCUMFactIngestResult{}, err
|
||||
}
|
||||
@@ -304,7 +322,11 @@ func (svc *CoreService) ingestSCUMUserFact(instance domain.ServerInstance, fact
|
||||
previousActivity := user.LastActivityAt
|
||||
freshLogin := created || (fact.Login && (previousActivity.IsZero() || loginAt.Sub(previousActivity) > domain.SCUMUserOfflineAfter))
|
||||
moved := fact.Online && fact.Position != nil && userPositionMoved(user, fact.Position)
|
||||
if fact.Login || created {
|
||||
loginEvidence := fact.Login || created
|
||||
if !loginEvidence && !fact.LoginObservedAt.IsZero() && loginAt.After(user.LastLoginAt) {
|
||||
loginEvidence = true
|
||||
}
|
||||
if loginEvidence {
|
||||
user.LastLoginAt = loginAt
|
||||
}
|
||||
user.DisplayName = firstNonBlank(fact.DisplayName, user.DisplayName)
|
||||
|
||||
Reference in New Issue
Block a user