128 lines
4.8 KiB
Go
128 lines
4.8 KiB
Go
package companion
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func (store *SCUMSQLStore) StoreConsoleRecords(ctx context.Context, records []ConsoleRecord) (int, error) {
|
|
if store == nil || store.db == nil {
|
|
return 0, fmt.Errorf("SCUM plugin SQL store is not configured")
|
|
}
|
|
normalized := make([]ConsoleRecord, 0, len(records))
|
|
for _, record := range records {
|
|
value, err := normalizeConsoleRecord(record)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
normalized = append(normalized, value)
|
|
}
|
|
if len(normalized) == 0 {
|
|
return 0, nil
|
|
}
|
|
tx, err := store.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("begin SCUM console log write: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
stamp := time.Now().UTC()
|
|
for _, record := range normalized {
|
|
if _, err := tx.ExecContext(ctx, scumConsoleLogInsertSQL, scumConsoleRecordKey(record), record.ServerID, record.Stream, record.Sequence, record.OccurredAt, record.Text, stamp, stamp); err != nil {
|
|
return 0, fmt.Errorf("write SCUM console log: %w", err)
|
|
}
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return 0, fmt.Errorf("commit SCUM console logs: %w", err)
|
|
}
|
|
return len(normalized), nil
|
|
}
|
|
|
|
func (store *SCUMSQLStore) StoreSemanticEventBatch(ctx context.Context, batch SemanticEventBatch) (int, error) {
|
|
if store == nil || store.db == nil {
|
|
return 0, fmt.Errorf("SCUM plugin SQL store is not configured")
|
|
}
|
|
normalized := make([]SemanticEvent, 0, len(batch.Events))
|
|
for _, event := range batch.Events {
|
|
value, err := normalizeSemanticEvent(event)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
normalized = append(normalized, value)
|
|
}
|
|
if len(normalized) == 0 {
|
|
return 0, nil
|
|
}
|
|
tx, err := store.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("begin SCUM semantic event write: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
stamp := time.Now().UTC()
|
|
for _, event := range normalized {
|
|
if _, err := tx.ExecContext(ctx, scumSemanticEventInsertSQL, scumSemanticEventRecordKey(event), event.ServerID, event.Sequence, event.Type, event.PlayerID, nullText(event.DisplayName), event.OccurredAt, nullText(event.NetworkCorrelation), stamp, stamp); err != nil {
|
|
return 0, fmt.Errorf("write SCUM semantic event: %w", err)
|
|
}
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return 0, fmt.Errorf("commit SCUM semantic events: %w", err)
|
|
}
|
|
return len(normalized), nil
|
|
}
|
|
|
|
func normalizeConsoleRecord(record ConsoleRecord) (ConsoleRecord, error) {
|
|
record.ServerID = strings.TrimSpace(record.ServerID)
|
|
record.Stream = strings.TrimSpace(record.Stream)
|
|
record.Text = strings.TrimRight(record.Text, "\r\n")
|
|
if record.ServerID == "" || (record.Stream != "stdout" && record.Stream != "stderr") || record.Sequence == 0 || record.OccurredAt.IsZero() || strings.TrimSpace(record.Text) == "" || len(record.Text) > 8192 {
|
|
return ConsoleRecord{}, fmt.Errorf("SCUM console record is invalid")
|
|
}
|
|
record.OccurredAt = record.OccurredAt.UTC()
|
|
return record, nil
|
|
}
|
|
|
|
func normalizeSemanticEvent(event SemanticEvent) (SemanticEvent, error) {
|
|
event.ServerID = strings.TrimSpace(event.ServerID)
|
|
event.Type = strings.TrimSpace(event.Type)
|
|
event.PlayerID = strings.TrimSpace(event.PlayerID)
|
|
event.DisplayName = strings.TrimSpace(event.DisplayName)
|
|
event.NetworkCorrelation = strings.TrimSpace(event.NetworkCorrelation)
|
|
if event.ServerID == "" || event.Sequence == 0 || event.Type == "" || event.PlayerID == "" || event.OccurredAt.IsZero() || len(event.Type) > 80 || len(event.PlayerID) > 80 || len(event.DisplayName) > 120 || len(event.NetworkCorrelation) > 128 {
|
|
return SemanticEvent{}, fmt.Errorf("SCUM semantic event is invalid")
|
|
}
|
|
event.OccurredAt = event.OccurredAt.UTC()
|
|
return event, nil
|
|
}
|
|
|
|
func scumConsoleRecordKey(record ConsoleRecord) string {
|
|
digest := sha256.Sum256([]byte(strings.Join([]string{record.ServerID, record.Stream, fmt.Sprintf("%d", record.Sequence)}, "\x00")))
|
|
return hex.EncodeToString(digest[:])
|
|
}
|
|
|
|
func scumSemanticEventRecordKey(event SemanticEvent) string {
|
|
digest := sha256.Sum256([]byte(strings.Join([]string{event.ServerID, event.Type, event.PlayerID, fmt.Sprintf("%d", event.Sequence)}, "\x00")))
|
|
return hex.EncodeToString(digest[:])
|
|
}
|
|
|
|
const scumConsoleLogInsertSQL = `
|
|
INSERT INTO scum_console_logs (
|
|
record_key, server_instance_id, stream, sequence, occurred_at, line_text, created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
occurred_at = VALUES(occurred_at),
|
|
line_text = VALUES(line_text),
|
|
updated_at = VALUES(updated_at)`
|
|
|
|
const scumSemanticEventInsertSQL = `
|
|
INSERT INTO scum_semantic_events (
|
|
record_key, server_instance_id, sequence, event_type, player_id, display_name, occurred_at, network_correlation, created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
display_name = VALUES(display_name),
|
|
occurred_at = VALUES(occurred_at),
|
|
network_correlation = VALUES(network_correlation),
|
|
updated_at = VALUES(updated_at)`
|