Remove SCUM sqlite user projections

This commit is contained in:
npc0-hue
2026-09-14 13:41:44 +08:00
parent b1f102bafb
commit ecdca8e28b
76 changed files with 2264 additions and 1558 deletions
+6
View File
@@ -79,6 +79,11 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/server-instances/{id}/game-client-bridge/commands/{commandId}/cancel", h.serverGameClientBridgeCommandCancel)
mux.HandleFunc("/api/v1/server-instances/{id}/game-client-bridge/commands/{commandId}", h.serverGameClientBridgeCommandDetail)
mux.HandleFunc("/api/v1/server-instances/{id}/game-client-bridge/snapshots", h.serverGameClientBridgeSnapshots)
mux.HandleFunc("/api/v1/server-instances/{id}/scum/users", h.serverSCUMUsers)
mux.HandleFunc("/api/v1/server-instances/{id}/scum/user-trajectories", h.serverSCUMUserTrajectories)
mux.HandleFunc("/api/v1/server-instances/{id}/scum/vehicles", h.serverSCUMVehicles)
mux.HandleFunc("/api/v1/server-instances/{id}/scum/vehicle-trajectories", h.serverSCUMVehicleTrajectories)
mux.HandleFunc("/api/v1/server-instances/{id}/scum/vehicle-locks", h.serverSCUMVehicleLocks)
mux.HandleFunc("/api/v1/server-instances/{id}/plugin-data/{collection}", h.serverPluginDataCollection)
mux.HandleFunc("/api/v1/server-instances/{id}/plugin-data/{collection}/transaction", h.serverPluginDataTransaction)
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/check", h.serverDependenciesCheck)
@@ -103,6 +108,7 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/run/control/heartbeat", h.requireRunSignature(h.runControlHeartbeat))
mux.HandleFunc("/api/v1/run/control/events", h.requireRunSignature(h.runControlEvents))
mux.HandleFunc("/api/v1/run/lifecycle/report", h.requireRunSignature(h.runLifecycleReport))
mux.HandleFunc("/api/v1/run/scum/facts", h.requireRunSignature(h.runSCUMFacts))
mux.HandleFunc("/api/v1/run/jobs/claim", h.requireRunSignature(h.runJobClaim))
mux.HandleFunc("/api/v1/run/jobs/ack", h.requireRunSignature(h.runJobAck))
mux.HandleFunc("/api/v1/run/jobs/progress", h.requireRunSignature(h.runJobProgress))
+1 -1
View File
@@ -154,7 +154,7 @@ Server-scoped terminal log streaming (`GET /api/v1/server-instances/{id}/logs/ev
Runtime distribution APIs require the current bearer session, server visibility, plugin-declared permissions, complete runtime bindings only for actions that truly depend on external logical bindings, and platform-builder readiness. Run-side lifecycle commands separately require run endpoint capability support and use plugin-declared lifecycle actions without making manual runtime-profile binding a user prerequisite. Responses and summaries expose artifact IDs, job IDs, checksums, key generations, fingerprints, status, and redacted `secret://runtime-keys/.../current` refs only. They do not expose raw run keys, FTP passwords, database DSNs, RCON passwords, host paths, direct sockets, run endpoint private addresses, build workspace paths, or large inline logs.
SCUM product APIs expose only safe local projections, typed operation/workflow requests, approval status, confirmation status, blocker reasons, and bounded summaries. They never expose SCUM.db SQL text, DB paths, DSNs, RCON command text, raw request payloads, run sockets, host paths, or credentials.
SCUM product APIs expose platform-owned `scum_user`, trajectory, vehicle, and lock table records plus typed operation/workflow requests, approval status, confirmation status, blocker reasons, and bounded summaries. They never expose direct game database SQL text, DB paths, DSNs, RCON command text, raw request payloads, run sockets, host paths, or credentials.
`POST /api/v1/server-instances/workflows/create` requires only the plugin type and server name. A runtime binding may still be maintained internally for advanced logical transports, but browser lifecycle controls must not force operators to choose a runtime profile before start/stop or run-package generation when the plugin deployment/lifecycle declaration is sufficient. Platform builds distributions itself and never needs a registered Run endpoint with `distribution.build` to do so.
+217
View File
@@ -0,0 +1,217 @@
package api
import (
"net/http"
"strconv"
"time"
"browser.local/platform/domain"
"browser.local/platform/dto"
)
func (h *coreHandlers) serverSCUMUsers(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeMethodNotAllowed(w, http.MethodGet)
return
}
filter, ok := scumUserFilterFromRequest(w, r, r.PathValue("id"))
if !ok {
return
}
items, err := h.core.ListSCUMUsersForSession(bearerToken(r), filter)
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.SCUMUserListFromDomain(items))
}
func (h *coreHandlers) serverSCUMUserTrajectories(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeMethodNotAllowed(w, http.MethodGet)
return
}
filter, ok := scumUserTrajectoryFilterFromRequest(w, r, r.PathValue("id"))
if !ok {
return
}
items, err := h.core.ListSCUMUserTrajectoriesForSession(bearerToken(r), filter)
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.SCUMUserTrajectoryListFromDomain(items))
}
func (h *coreHandlers) serverSCUMVehicles(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeMethodNotAllowed(w, http.MethodGet)
return
}
filter, ok := scumVehicleFilterFromRequest(w, r, r.PathValue("id"))
if !ok {
return
}
items, err := h.core.ListSCUMVehiclesForSession(bearerToken(r), filter)
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.SCUMVehicleListFromDomain(items))
}
func (h *coreHandlers) serverSCUMVehicleTrajectories(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeMethodNotAllowed(w, http.MethodGet)
return
}
filter, ok := scumVehicleTrajectoryFilterFromRequest(w, r, r.PathValue("id"))
if !ok {
return
}
items, err := h.core.ListSCUMVehicleTrajectoriesForSession(bearerToken(r), filter)
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.SCUMVehicleTrajectoryListFromDomain(items))
}
func (h *coreHandlers) serverSCUMVehicleLocks(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeMethodNotAllowed(w, http.MethodGet)
return
}
filter, ok := scumVehicleLockFilterFromRequest(w, r, r.PathValue("id"))
if !ok {
return
}
items, err := h.core.ListSCUMVehicleLocksForSession(bearerToken(r), filter)
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.SCUMVehicleLockListFromDomain(items))
}
func (h *coreHandlers) runSCUMFacts(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeMethodNotAllowed(w, http.MethodPost)
return
}
request, err := decodeJSON[dto.SCUMFactIngestRequest](r)
if err != nil {
writeDecodeError(w, err)
return
}
result, err := h.core.IngestSCUMFacts(request.ToDomain())
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusAccepted, dto.SCUMFactIngestFromDomain(result))
}
func scumUserFilterFromRequest(w http.ResponseWriter, r *http.Request, serverID string) (domain.SCUMUserFilter, bool) {
limit, ok := optionalLimitFromRequest(w, r)
if !ok {
return domain.SCUMUserFilter{}, false
}
online, ok := optionalBoolFromRequest(w, r, "online")
if !ok {
return domain.SCUMUserFilter{}, false
}
changedAfter, ok := optionalTimeFromRequest(w, r, "changedAfter")
if !ok {
return domain.SCUMUserFilter{}, false
}
return domain.SCUMUserFilter{ServerInstanceID: serverID, SteamID: r.URL.Query().Get("steamId"), Online: online, ChangedAfter: changedAfter, Limit: limit}, true
}
func scumUserTrajectoryFilterFromRequest(w http.ResponseWriter, r *http.Request, serverID string) (domain.SCUMUserTrajectoryFilter, bool) {
limit, ok := optionalLimitFromRequest(w, r)
if !ok {
return domain.SCUMUserTrajectoryFilter{}, false
}
after, ok := optionalTimeFromRequest(w, r, "after")
if !ok {
return domain.SCUMUserTrajectoryFilter{}, false
}
return domain.SCUMUserTrajectoryFilter{ServerInstanceID: serverID, SCUMUserID: r.URL.Query().Get("scumUserId"), SteamID: r.URL.Query().Get("steamId"), After: after, Limit: limit}, true
}
func scumVehicleFilterFromRequest(w http.ResponseWriter, r *http.Request, serverID string) (domain.SCUMVehicleFilter, bool) {
limit, ok := optionalLimitFromRequest(w, r)
if !ok {
return domain.SCUMVehicleFilter{}, false
}
exists, ok := optionalBoolFromRequest(w, r, "exists")
if !ok {
return domain.SCUMVehicleFilter{}, false
}
changedAfter, ok := optionalTimeFromRequest(w, r, "changedAfter")
if !ok {
return domain.SCUMVehicleFilter{}, false
}
return domain.SCUMVehicleFilter{ServerInstanceID: serverID, GameVehicleID: r.URL.Query().Get("gameVehicleId"), Exists: exists, ChangedAfter: changedAfter, Limit: limit}, true
}
func scumVehicleTrajectoryFilterFromRequest(w http.ResponseWriter, r *http.Request, serverID string) (domain.SCUMVehicleTrajectoryFilter, bool) {
limit, ok := optionalLimitFromRequest(w, r)
if !ok {
return domain.SCUMVehicleTrajectoryFilter{}, false
}
after, ok := optionalTimeFromRequest(w, r, "after")
if !ok {
return domain.SCUMVehicleTrajectoryFilter{}, false
}
return domain.SCUMVehicleTrajectoryFilter{ServerInstanceID: serverID, SCUMVehicleID: r.URL.Query().Get("scumVehicleId"), GameVehicleID: r.URL.Query().Get("gameVehicleId"), After: after, Limit: limit}, true
}
func scumVehicleLockFilterFromRequest(w http.ResponseWriter, r *http.Request, serverID string) (domain.SCUMVehicleLockFilter, bool) {
limit, ok := optionalLimitFromRequest(w, r)
if !ok {
return domain.SCUMVehicleLockFilter{}, false
}
after, ok := optionalTimeFromRequest(w, r, "after")
if !ok {
return domain.SCUMVehicleLockFilter{}, false
}
query := r.URL.Query()
return domain.SCUMVehicleLockFilter{ServerInstanceID: serverID, SCUMVehicleID: query.Get("scumVehicleId"), GameVehicleID: query.Get("gameVehicleId"), SCUMUserID: query.Get("scumUserId"), SteamID: query.Get("steamId"), After: after, Limit: limit}, true
}
func optionalLimitFromRequest(w http.ResponseWriter, r *http.Request) (int, bool) {
limit, err := optionalPositiveInt(r.URL.Query().Get("limit"))
if err != nil || limit < 0 {
writeAPIError(w, http.StatusBadRequest, errorCodeBadRequest, "invalid limit", nil)
return 0, false
}
return limit, true
}
func optionalBoolFromRequest(w http.ResponseWriter, r *http.Request, key string) (*bool, bool) {
value := r.URL.Query().Get(key)
if value == "" {
return nil, true
}
parsed, err := strconv.ParseBool(value)
if err != nil {
writeAPIError(w, http.StatusBadRequest, errorCodeBadRequest, "invalid "+key, nil)
return nil, false
}
return &parsed, true
}
func optionalTimeFromRequest(w http.ResponseWriter, r *http.Request, key string) (time.Time, bool) {
value := r.URL.Query().Get(key)
if value == "" {
return time.Time{}, true
}
parsed, err := time.Parse(time.RFC3339, value)
if err != nil {
writeAPIError(w, http.StatusBadRequest, errorCodeBadRequest, "invalid "+key, nil)
return time.Time{}, false
}
return parsed, true
}