Rebuild SCUM plugin data ownership
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
"browser.local/platform/dto"
|
||||
)
|
||||
|
||||
// serverPluginDataCollection provides scoped, opaque plugin-owned records.
|
||||
func (h *coreHandlers) serverPluginDataCollection(w http.ResponseWriter, r *http.Request) {
|
||||
instance, err := h.core.GetServerInstanceForSession(bearerToken(r), r.PathValue("id"))
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
collection := r.PathValue("collection")
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
limit, err := optionalPositiveInt(r.URL.Query().Get("limit"))
|
||||
if err != nil || limit < 0 {
|
||||
writeAPIError(w, http.StatusBadRequest, errorCodeBadRequest, "invalid plugin data limit", nil)
|
||||
return
|
||||
}
|
||||
items, err := h.core.ListPluginDataForSession(bearerToken(r), domain.PluginDataFilter{PluginID: instance.PluginID, ServerInstanceID: instance.ID, Collection: collection, Key: r.URL.Query().Get("key"), Limit: limit})
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.PluginDataRecordsFromDomain(items))
|
||||
case http.MethodPut:
|
||||
request, err := decodeJSON[dto.PluginDataPutRequest](r)
|
||||
if err != nil {
|
||||
writeDecodeError(w, err)
|
||||
return
|
||||
}
|
||||
value, err := h.core.PutPluginDataForSession(bearerToken(r), domain.PluginDataRecord{PluginID: instance.PluginID, ServerInstanceID: instance.ID, Collection: collection, Key: request.Key, Value: request.Value})
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.PluginDataRecordFromDomain(value))
|
||||
default:
|
||||
w.Header().Set("Allow", http.MethodGet+", "+http.MethodPut)
|
||||
writeAPIError(w, http.StatusMethodNotAllowed, errorCodeMethodNotAllowed, "method not allowed", nil)
|
||||
}
|
||||
}
|
||||
|
||||
var _ = strconv.IntSize
|
||||
@@ -92,6 +92,7 @@ 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}/plugin-data/{collection}", h.serverPluginDataCollection)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/game-players", h.serverGamePlayers)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/game-players/{playerId}", h.serverGamePlayerDetail)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/game-players/{playerId}/state", h.serverGamePlayerState)
|
||||
@@ -104,12 +105,7 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/game-gift-grants", h.serverGameGiftGrants)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/game-gift-grants/{grantId}/approve", h.serverGameGiftGrantApprove)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/players", h.serverSCUMPlayers)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/users", h.serverSCUMUsers)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/squads", h.serverSCUMSquads)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/datasets/squads", h.serverSCUMDataSquads)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/activity", h.serverSCUMActivity)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/gifts", h.serverSCUMGifts)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/map-points", h.serverSCUMMapPoints)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/squad-members", h.serverSCUMSquadMembers)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/vehicles", h.serverSCUMVehicles)
|
||||
mux.HandleFunc("/api/v1/server-instances/{id}/scum/flags", h.serverSCUMFlags)
|
||||
|
||||
@@ -21,41 +21,6 @@ func (h *coreHandlers) serverSCUMPlayers(w http.ResponseWriter, r *http.Request)
|
||||
writeJSON(w, http.StatusOK, dto.SCUMPlayerLiveStatesFromDomain(items))
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMUsers(w http.ResponseWriter, r *http.Request) {
|
||||
h.serverSCUMDataSet(w, r, domain.SCUMDataSetUsers)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMDataSquads(w http.ResponseWriter, r *http.Request) {
|
||||
h.serverSCUMDataSet(w, r, domain.SCUMDataSetSquads)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMActivity(w http.ResponseWriter, r *http.Request) {
|
||||
h.serverSCUMDataSet(w, r, domain.SCUMDataSetActivity)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMGifts(w http.ResponseWriter, r *http.Request) {
|
||||
h.serverSCUMDataSet(w, r, domain.SCUMDataSetGiftEvents)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMMapPoints(w http.ResponseWriter, r *http.Request) {
|
||||
h.serverSCUMDataSet(w, r, domain.SCUMDataSetMapPoints)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMDataSet(w http.ResponseWriter, r *http.Request, target domain.SCUMDataSet) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
return
|
||||
}
|
||||
filter := scumProjectionFilterFromRequest(r, r.PathValue("id"))
|
||||
filter.TargetTable = target
|
||||
items, err := h.core.ListSCUMDataRowsForSession(bearerToken(r), filter)
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.SCUMDataRowsFromDomain(items))
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMSquads(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
|
||||
@@ -21,9 +21,8 @@ func TestSCUMProjectionOperationAndWorkflowAPIsExposeSafeTypedSurfaces(t *testin
|
||||
}
|
||||
plugin := validGamePluginRequest().ToDomain()
|
||||
plugin.DeclaredPermissions = append(plugin.DeclaredPermissions, "server.game-client.command", "server.game-client.read")
|
||||
plugin.RequiredRunCapabilities = append(plugin.RequiredRunCapabilities, domain.JobCapabilityRemoteRunRCONCommand, domain.JobCapabilityRemoteRunProtectedRCON, domain.JobCapabilityRemoteRunDBSQLiteQuery)
|
||||
plugin.RuntimeProfiles.TransportProfiles = []domain.RuntimeTransportProfile{{Key: "scum-management", Kind: "rcon", TargetKey: "scum-management", Capabilities: []string{domain.JobCapabilityRemoteRunRCONCommand, domain.JobCapabilityRemoteRunProtectedRCON}}, {Key: "scum-data", Kind: "sqlite", TargetKey: "scum-data", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteQuery}}}
|
||||
plugin.GameClientBridge.QueryTemplates = []domain.GameClientBridgeQueryTemplateDeclaration{{Key: "v57.directory.people", Title: "SCUM users", Permission: "server.game-client.read", Engine: "sqlite", TransportKey: "scum-data", TargetKey: "scum-data", ParameterSchemaRef: "schemas/bridge/queries/scum-player-profile.parameters.schema.json", ResultSchemaRef: "schemas/bridge/queries/scum-player-profile.result.schema.json", MaxRows: 200, TimeoutSeconds: 15, RowTarget: &domain.SCUMRowTargetDeclaration{TargetTable: string(domain.SCUMDataSetUsers), UpsertKeys: []string{"profileId"}, ColumnMappings: map[string]string{"profileId": "user_profile_id", "displayName": "display_name"}}}}
|
||||
plugin.RequiredRunCapabilities = append(plugin.RequiredRunCapabilities, domain.JobCapabilityRemoteRunRCONCommand, domain.JobCapabilityRemoteRunProtectedRCON)
|
||||
plugin.RuntimeProfiles.TransportProfiles = []domain.RuntimeTransportProfile{{Key: "scum-management", Kind: "rcon", TargetKey: "scum-management", Capabilities: []string{domain.JobCapabilityRemoteRunRCONCommand, domain.JobCapabilityRemoteRunProtectedRCON}}}
|
||||
plugin.GameClientBridge.OperationTemplates = []domain.GameClientBridgeOperationTemplateDeclaration{{Key: "player.fame.set", Title: "Set fame", Permission: "server.game-client.command", ApprovalLevel: domain.GameClientBridgeApprovalLevelOperator, Kind: domain.GameClientBridgeOperationKindRCON, TransportKey: "scum-management", TargetKey: "scum-management", PayloadSchemaRef: "schemas/bridge/player-fame-set.payload.schema.json", TimeoutSeconds: 60, MaxPayloadBytes: 2048, Safety: domain.GameClientBridgeOperationSafety{RequiresApproval: true, RequiresConfirmation: true}}}
|
||||
plugin.GameClientBridge.Retention = domain.GameClientBridgeRetention{KeepForSeconds: 86400, MaxRecords: 1000}
|
||||
if _, err := core.CreateGamePlugin(plugin); err != nil {
|
||||
@@ -41,9 +40,6 @@ func TestSCUMProjectionOperationAndWorkflowAPIsExposeSafeTypedSurfaces(t *testin
|
||||
if _, err := core.ApplySCUMObservationResult(domain.SCUMObservationResult{ServerInstanceID: "server-scum-api", PluginID: plugin.ID, Source: "run.sqlite.read", QueryKey: "scum.player.profile", Sequence: 1, Checksum: "sha256:api-profile", ObservedAt: time.Now().UTC(), Rows: []map[string]any{{"gamePlayerId": "steam-api", "displayName": "API Player", "normalBalance": 25, "x": 1, "y": 2, "z": 3}}}); err != nil {
|
||||
t.Fatalf("seed projection: %v", err)
|
||||
}
|
||||
if _, err := core.ApplySCUMObservationResult(domain.SCUMObservationResult{ServerInstanceID: "server-scum-api", PluginID: plugin.ID, Source: "run.sqlite.read", QueryKey: "v57.directory.people", Sequence: 1, Checksum: "sha256:api-users", ObservedAt: time.Now().UTC(), Rows: []map[string]any{{"user_profile_id": "profile-api", "display_name": "API User"}}}); err != nil {
|
||||
t.Fatalf("seed SCUM users: %v", err)
|
||||
}
|
||||
auth, err := core.LoginUser(domain.UserLogin{Account: "scum-api-owner@example.test", Password: "secret-password"})
|
||||
if err != nil {
|
||||
t.Fatalf("login: %v", err)
|
||||
@@ -53,10 +49,6 @@ func TestSCUMProjectionOperationAndWorkflowAPIsExposeSafeTypedSurfaces(t *testin
|
||||
if players.Count != 1 || players.Items[0].GamePlayerID != "steam-api" || players.Items[0].Position.X != 1 {
|
||||
t.Fatalf("unexpected SCUM players response: %+v", players)
|
||||
}
|
||||
users := getJSONWithAuth[dto.SCUMDataRowListResponse](t, router, "/api/v1/server-instances/server-scum-api/scum/users", auth.SessionID)
|
||||
if users.Count != 1 || users.Items[0].Fields["profileId"] != "profile-api" || users.Items[0].Payload["display_name"] != "API User" {
|
||||
t.Fatalf("unexpected persisted SCUM users response: %+v", users)
|
||||
}
|
||||
operation := postJSONWithAuth[dto.SCUMOperationResponse](t, router, "/api/v1/server-instances/server-scum-api/scum/operations", dto.SCUMOperationRequestBody{TemplateKey: "player.fame.set", PlayerID: "steam-api", Payload: map[string]any{"fame": 12}, Reason: "api typed op", IdempotencyKey: "api-fame-1"}, auth.SessionID)
|
||||
if operation.Status != string(domain.SCUMWorkflowStepWaiting) || operation.TemplateKey != "player.fame.set" {
|
||||
t.Fatalf("unexpected SCUM operation response: %+v", operation)
|
||||
|
||||
Reference in New Issue
Block a user