feat: Go port of the panel in panel-go/ — drop-in replacement for server.js
CI / test (push) Canceled after 0s
CI / anytls-smoke (push) Canceled after 0s

Pure-stdlib Go implementation of the whole control plane: same
nexusgate.json schema (unknown fields preserved verbatim, explicit
null/false tri-states modeled), same HTTP API and error strings,
embedded frontend (NG_PUBLIC_DIR override), self-implemented scrypt
auth, X25519 Reality keys, panel-hosted keyStore and heartbeat
reconciliation included. Version stays 0.6.8.

Verified: Go tests 33/33 (scrypt + Reality vectors generated from
the Node implementation, subscription goldens byte-identical),
Node suite 52/52 untouched, differential E2E against the Node
panel 50/50 including two-way DB swap. See panel-go/PARITY.md
and panel-go/INSTALL-GO.md.
This commit is contained in:
aaaamoooo
2026-10-20 08:21:54 +00:00
parent abb1a4344b
commit 3951134940
37 changed files with 12183 additions and 0 deletions
+962
View File
@@ -0,0 +1,962 @@
package main
// Shared data model + JSON plumbing for the Go port of the NexusGate panel.
//
// Compatibility contract with the Node implementation:
// - The database is one JSON object (see Data). Every record type mirrors
// the JS object shapes field by field; fields JS may leave absent use
// pointer+omitempty only where "absent" and "null" are semantically
// identical in the JS code (all reads are truthiness-based). Where the
// JS create paths write an explicit null, the Go field has NO omitempty
// so a nil pointer re-marshals as null.
// - Unknown fields (written by other panel versions) are captured per
// record in Extra and re-emitted verbatim on marshal, so a load/save
// round trip never drops data.
// - All JSON output goes through marshalJSON (HTML escaping disabled) to
// match JSON.stringify.
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"reflect"
"strings"
"sync"
"time"
)
// ---------- errors ----------
// HTTPError mirrors a JS Error carrying statusCode.
type HTTPError struct {
Status int
Message string
}
func (e *HTTPError) Error() string { return e.Message }
func httpErr(status int, format string, args ...any) *HTTPError {
return &HTTPError{Status: status, Message: fmt.Sprintf(format, args...)}
}
// ---------- time ----------
// NowISO is JS `new Date().toISOString()`: UTC, millisecond precision, Z.
func NowISO() string { return ISOFromTime(time.Now()) }
func ISOFromTime(t time.Time) string {
return t.UTC().Format("2006-01-02T15:04:05.000Z")
}
// ParseISO accepts the forms JS `new Date(value)` accepts for the inputs
// this panel produces/consumes: RFC3339 (with optional fractional seconds
// and offsets) and date-only "2006-01-02" (interpreted as UTC midnight).
func ParseISO(s string) (time.Time, bool) {
s = strings.TrimSpace(s)
if s == "" {
return time.Time{}, false
}
for _, layout := range []string{
time.RFC3339Nano,
"2006-01-02T15:04:05.000Z0700",
"2006-01-02T15:04:05Z0700",
"2006-01-02T15:04Z0700",
"2006-01-02",
} {
if t, err := time.Parse(layout, s); err == nil {
return t, true
}
}
return time.Time{}, false
}
// ParseISOMs is JS Date.parse: milliseconds since epoch; ok=false is NaN
// (comparisons against NaN are always false in the JS code paths).
func ParseISOMs(s string) (int64, bool) {
t, ok := ParseISO(s)
if !ok {
return 0, false
}
return t.UnixMilli(), true
}
// ---------- ids ----------
func NewUUID() string {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
panic(err)
}
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}
// NewID mirrors JS `id(prefix)` => `${prefix}_${crypto.randomUUID()}`.
func NewID(prefix string) string { return prefix + "_" + NewUUID() }
func StrPtr(s string) *string { return &s }
func BoolPtr(b bool) *bool { return &b }
// boolVal is JS truthiness for an optional boolean field.
func boolVal(p *bool) bool { return p != nil && *p }
func IntPtr(v int) *int { return &v }
// ---------- JSON helpers ----------
// marshalJSON is JSON.stringify: compact, no HTML escaping.
func marshalJSON(v any) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
return nil, err
}
return bytes.TrimRight(buf.Bytes(), "\n"), nil
}
// unmarshalJSONUseNumber decodes with UseNumber so generic `any` fields
// (activity detail, job payloads from agents) keep integer literals exact.
func unmarshalJSONUseNumber(b []byte, v any) error {
dec := json.NewDecoder(bytes.NewReader(b))
dec.UseNumber()
return dec.Decode(v)
}
// cloneJSONValue is structuredClone for JSON-shaped values.
func cloneJSONValue[T any](v T) T {
raw, err := marshalJSON(v)
if err != nil {
panic(err)
}
var out T
if err := unmarshalJSONUseNumber(raw, &out); err != nil {
panic(err)
}
return out
}
// ---------- unknown-field passthrough ----------
type extraFields map[string]json.RawMessage
var knownKeyCache sync.Map // reflect.Type -> map[string]struct{}
func knownJSONKeys(t reflect.Type) map[string]struct{} {
if cached, ok := knownKeyCache.Load(t); ok {
return cached.(map[string]struct{})
}
keys := map[string]struct{}{}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.PkgPath != "" { // unexported
continue
}
tag := f.Tag.Get("json")
if tag == "-" {
continue
}
name := strings.Split(tag, ",")[0]
if name == "" {
name = f.Name
}
keys[name] = struct{}{}
}
knownKeyCache.Store(t, keys)
return keys
}
func captureExtra(raw []byte, sample any) extraFields {
var all map[string]json.RawMessage
if err := json.Unmarshal(raw, &all); err != nil || len(all) == 0 {
return nil
}
known := knownJSONKeys(reflect.TypeOf(sample))
var extra extraFields
for k, v := range all {
if _, ok := known[k]; !ok {
if extra == nil {
extra = extraFields{}
}
extra[k] = v
}
}
return extra
}
// mergeMarshal marshals a record and re-attaches its unknown fields.
func mergeMarshal(sample any, extra extraFields) ([]byte, error) {
base, err := marshalJSON(sample)
if err != nil || len(extra) == 0 {
return base, err
}
var merged map[string]json.RawMessage
if err := json.Unmarshal(base, &merged); err != nil {
return nil, err
}
for k, v := range extra {
if _, ok := merged[k]; !ok {
merged[k] = v
}
}
return marshalJSON(merged)
}
// recordJSON implements the UnmarshalJSON half shared by every record type.
func recordJSON[T any](raw []byte, dst *T, extra *extraFields) error {
var plain T
if err := unmarshalJSONUseNumber(raw, &plain); err != nil {
return err
}
*dst = plain
*extra = captureExtra(raw, plain)
return nil
}
// ---------- database ----------
type Data struct {
SchemaVersion int `json:"schemaVersion"`
CreatedAt string `json:"createdAt"`
Settings Settings `json:"settings"`
Users []*User `json:"users"`
Servers []*Server `json:"servers"`
Agents []*Agent `json:"agents"`
EnrollmentTokens []*EnrollmentToken `json:"enrollmentTokens"`
Customers []*Customer `json:"customers"`
Nodes []*Node `json:"nodes"`
NodeClients []*NodeClient `json:"nodeClients"`
Jobs []*Job `json:"jobs"`
Activity []*Activity `json:"activity"`
Observations []*Observation `json:"observations"`
SubscriptionAccess []*SubscriptionAccess `json:"subscriptionAccess"`
SubscriptionClients []*SubscriptionClient `json:"subscriptionClients"`
KeyStore map[string]*KeyEntry `json:"keyStore"`
Extra extraFields `json:"-"`
}
func (d *Data) UnmarshalJSON(raw []byte) error {
type plain Data
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*d = Data(p)
d.Extra = extra
return nil
}
func (d Data) MarshalJSON() ([]byte, error) {
type plain Data
return mergeMarshal(plain(d), d.Extra)
}
// Clone is structuredClone(this.data).
func (d *Data) Clone() *Data {
raw, err := marshalJSON(d)
if err != nil {
panic(err)
}
var out Data
if err := unmarshalJSONUseNumber(raw, &out); err != nil {
panic(err)
}
return &out
}
type Settings struct {
BrandName string `json:"brandName"`
SessionHours float64 `json:"sessionHours"`
ObservationTtlMinutes float64 `json:"observationTtlMinutes"`
ActivityRetentionDays float64 `json:"activityRetentionDays"`
CompletedJobRetentionDays float64 `json:"completedJobRetentionDays"`
Extra extraFields `json:"-"`
}
func (s *Settings) UnmarshalJSON(raw []byte) error {
type plain Settings
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*s = Settings(p)
s.Extra = extra
return nil
}
func (s Settings) MarshalJSON() ([]byte, error) {
type plain Settings
return mergeMarshal(plain(s), s.Extra)
}
type User struct {
ID string `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"passwordHash"`
Role string `json:"role"`
Status string `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt *string `json:"updatedAt,omitempty"`
Extra extraFields `json:"-"`
}
func (u *User) UnmarshalJSON(raw []byte) error {
type plain User
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*u = User(p)
u.Extra = extra
return nil
}
func (u User) MarshalJSON() ([]byte, error) {
type plain User
return mergeMarshal(plain(u), u.Extra)
}
type EngineState struct {
Status string `json:"status"`
Detail string `json:"detail"`
SingBoxInstalled bool `json:"singBoxInstalled"`
Certificates []string `json:"certificates"`
SingBoxStatus string `json:"singBoxStatus"`
At string `json:"at"`
Extra extraFields `json:"-"`
}
func (e *EngineState) UnmarshalJSON(raw []byte) error {
type plain EngineState
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*e = EngineState(p)
e.Extra = extra
return nil
}
func (e EngineState) MarshalJSON() ([]byte, error) {
type plain EngineState
return mergeMarshal(plain(e), e.Extra)
}
type ServerUsage struct {
LastReportAt string `json:"lastReportAt"`
SampleCount int `json:"sampleCount"`
Error *string `json:"error"`
Extra extraFields `json:"-"`
}
func (u *ServerUsage) UnmarshalJSON(raw []byte) error {
type plain ServerUsage
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*u = ServerUsage(p)
u.Extra = extra
return nil
}
func (u ServerUsage) MarshalJSON() ([]byte, error) {
type plain ServerUsage
return mergeMarshal(plain(u), u.Extra)
}
type Server struct {
ID string `json:"id"`
Name string `json:"name"`
Role string `json:"role"`
Region string `json:"region"`
PublicAddress string `json:"publicAddress"`
PublicAddressV6 string `json:"publicAddressV6"`
TLSDomain string `json:"tlsDomain"`
PortRangeStart int `json:"portRangeStart"`
PortRangeEnd int `json:"portRangeEnd"`
Labels []string `json:"labels"`
Status string `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
LastSeenAt *string `json:"lastSeenAt,omitempty"`
System json.RawMessage `json:"system,omitempty"`
Engine *EngineState `json:"engine,omitempty"`
EnginePresent bool `json:"-"`
Usage *ServerUsage `json:"usage,omitempty"`
Extra extraFields `json:"-"`
}
func (s *Server) UnmarshalJSON(raw []byte) error {
type plain Server
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*s = Server(p)
s.Extra = extra
var probe map[string]json.RawMessage
if json.Unmarshal(raw, &probe) == nil {
_, s.EnginePresent = probe["engine"]
}
return nil
}
func (s Server) MarshalJSON() ([]byte, error) {
type plain Server
base, err := mergeMarshal(plain(s), s.Extra)
if err != nil || s.Engine != nil || !s.EnginePresent {
return base, err
}
// JS assigns server.engine = ... || null on heartbeat: once the key
// exists it stays present with value null.
var merged map[string]json.RawMessage
if err := json.Unmarshal(base, &merged); err != nil {
return nil, err
}
merged["engine"] = json.RawMessage("null")
return marshalJSON(merged)
}
type Agent struct {
ID string `json:"id"`
ServerID string `json:"serverId"`
KeyHash string `json:"keyHash"`
KeyFingerprint *string `json:"keyFingerprint,omitempty"`
Status string `json:"status"`
Hostname string `json:"hostname"`
Version string `json:"version"`
CreatedAt string `json:"createdAt"`
LastSeenAt string `json:"lastSeenAt"`
Extra extraFields `json:"-"`
}
func (a *Agent) UnmarshalJSON(raw []byte) error {
type plain Agent
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*a = Agent(p)
a.Extra = extra
return nil
}
func (a Agent) MarshalJSON() ([]byte, error) {
type plain Agent
return mergeMarshal(plain(a), a.Extra)
}
type EnrollmentToken struct {
ID string `json:"id"`
ServerID string `json:"serverId"`
TokenHash string `json:"tokenHash"`
CreatedAt string `json:"createdAt"`
ExpiresAt string `json:"expiresAt"`
UsedAt *string `json:"usedAt"`
Extra extraFields `json:"-"`
}
func (t *EnrollmentToken) UnmarshalJSON(raw []byte) error {
type plain EnrollmentToken
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*t = EnrollmentToken(p)
t.Extra = extra
return nil
}
func (t EnrollmentToken) MarshalJSON() ([]byte, error) {
type plain EnrollmentToken
return mergeMarshal(plain(t), t.Extra)
}
type UsageCounters struct {
Uplink int64 `json:"uplink"`
Downlink int64 `json:"downlink"`
Epoch *string `json:"epoch"`
Extra extraFields `json:"-"`
}
func (c *UsageCounters) UnmarshalJSON(raw []byte) error {
type plain UsageCounters
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*c = UsageCounters(p)
c.Extra = extra
return nil
}
func (c UsageCounters) MarshalJSON() ([]byte, error) {
type plain UsageCounters
return mergeMarshal(plain(c), c.Extra)
}
type UsageSlot struct {
UsageCounters *UsageCounters `json:"usageCounters,omitempty"`
RetiredUsageEpochs []string `json:"retiredUsageEpochs,omitempty"`
Extra extraFields `json:"-"`
}
func (s *UsageSlot) UnmarshalJSON(raw []byte) error {
type plain UsageSlot
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*s = UsageSlot(p)
s.Extra = extra
return nil
}
func (s UsageSlot) MarshalJSON() ([]byte, error) {
type plain UsageSlot
return mergeMarshal(plain(s), s.Extra)
}
type Customer struct {
ID string `json:"id"`
SubscriptionToken string `json:"subscriptionToken"`
Name string `json:"name"`
Group string `json:"group"`
Status string `json:"status"`
SuspendReason *string `json:"suspendReason,omitempty"`
SuspendReasonPresent bool `json:"-"`
TrafficLimitBytes float64 `json:"trafficLimitBytes"`
UsedBytes int64 `json:"usedBytes"`
UsedUplinkBytes int64 `json:"usedUplinkBytes"`
UsedDownlinkBytes int64 `json:"usedDownlinkBytes"`
ExpiresAt *string `json:"expiresAt"`
IPLimit int64 `json:"ipLimit"`
DeviceLimit int64 `json:"deviceLimit"`
Tags []string `json:"tags"`
Notes string `json:"notes"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
ServerUsage map[string]*UsageSlot `json:"serverUsage,omitempty"`
LastUsageAt *string `json:"lastUsageAt,omitempty"`
LastOnlineAt *string `json:"lastOnlineAt,omitempty"`
Extra extraFields `json:"-"`
}
func (c *Customer) UnmarshalJSON(raw []byte) error {
type plain Customer
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*c = Customer(p)
c.Extra = extra
var probe map[string]json.RawMessage
if json.Unmarshal(raw, &probe) == nil {
_, c.SuspendReasonPresent = probe["suspendReason"]
}
return nil
}
func (c Customer) MarshalJSON() ([]byte, error) {
type plain Customer
base, err := mergeMarshal(plain(c), c.Extra)
if err != nil || c.SuspendReason != nil || !c.SuspendReasonPresent {
return base, err
}
// JS writes suspendReason: null explicitly on resume paths.
var merged map[string]json.RawMessage
if err := json.Unmarshal(base, &merged); err != nil {
return nil, err
}
merged["suspendReason"] = json.RawMessage("null")
return marshalJSON(merged)
}
type RealityConfig struct {
KeyID string `json:"keyId"`
ServerName string `json:"serverName"`
DestPort int `json:"destPort"`
Extra extraFields `json:"-"`
}
func (r *RealityConfig) UnmarshalJSON(raw []byte) error {
type plain RealityConfig
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*r = RealityConfig(p)
r.Extra = extra
return nil
}
func (r RealityConfig) MarshalJSON() ([]byte, error) {
type plain RealityConfig
return mergeMarshal(plain(r), r.Extra)
}
type ExitCredentials struct {
ClientID string `json:"clientId"`
Password string `json:"password"`
SocksUser string `json:"socksUser"`
Extra extraFields `json:"-"`
}
func (c *ExitCredentials) UnmarshalJSON(raw []byte) error {
type plain ExitCredentials
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*c = ExitCredentials(p)
c.Extra = extra
return nil
}
func (c ExitCredentials) MarshalJSON() ([]byte, error) {
type plain ExitCredentials
return mergeMarshal(plain(c), c.Extra)
}
type MeteredTraffic struct {
Uplink int64 `json:"uplink"`
Downlink int64 `json:"downlink"`
Extra extraFields `json:"-"`
}
func (m *MeteredTraffic) UnmarshalJSON(raw []byte) error {
type plain MeteredTraffic
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*m = MeteredTraffic(p)
m.Extra = extra
return nil
}
func (m MeteredTraffic) MarshalJSON() ([]byte, error) {
type plain MeteredTraffic
return mergeMarshal(plain(m), m.Extra)
}
type Node struct {
ID string `json:"id"`
Name string `json:"name"`
ServerID string `json:"serverId"`
ExitServerID *string `json:"exitServerId"`
Protocol string `json:"protocol"`
Topology string `json:"topology"`
ExitProtocol *string `json:"exitProtocol"`
NetworkMode string `json:"networkMode"`
RealityServerName string `json:"realityServerName"`
RealityDestPort int `json:"realityDestPort"`
PortMode string `json:"portMode"`
Port int `json:"port"`
ExitPortMode *string `json:"exitPortMode"`
ExitPort *int `json:"exitPort"`
Status string `json:"status"`
EntryReady bool `json:"entryReady"`
ExitReady bool `json:"exitReady"`
ResourceID string `json:"resourceId"`
ExitResourceID string `json:"exitResourceId"`
Artifacts map[string]json.RawMessage `json:"artifacts"`
Error *string `json:"error"`
PendingDelete bool `json:"pendingDelete"`
AutoRemoving *bool `json:"autoRemoving,omitempty"`
WSPath *string `json:"wsPath,omitempty"`
SSServerKey *string `json:"ssServerKey,omitempty"`
Reality *RealityConfig `json:"reality,omitempty"`
ExitCredentials *ExitCredentials `json:"exitCredentials,omitempty"`
UsageCounters *UsageCounters `json:"usageCounters"`
RetiredUsageEpochs []string `json:"retiredUsageEpochs"`
MeteredTraffic *MeteredTraffic `json:"meteredTraffic"`
LastUsageAt *string `json:"lastUsageAt"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Extra extraFields `json:"-"`
}
func (n *Node) UnmarshalJSON(raw []byte) error {
type plain Node
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*n = Node(p)
n.Extra = extra
return nil
}
func (n Node) MarshalJSON() ([]byte, error) {
type plain Node
return mergeMarshal(plain(n), n.Extra)
}
type Credentials struct {
ClientID string `json:"clientId"`
RelayPassword string `json:"relayPassword"`
ShortID string `json:"shortId"`
Extra extraFields `json:"-"`
}
func (c *Credentials) UnmarshalJSON(raw []byte) error {
type plain Credentials
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*c = Credentials(p)
c.Extra = extra
return nil
}
func (c Credentials) MarshalJSON() ([]byte, error) {
type plain Credentials
return mergeMarshal(plain(c), c.Extra)
}
type NodeClient struct {
ID string `json:"id"`
NodeID string `json:"nodeId"`
CustomerID string `json:"customerId"`
Credentials Credentials `json:"credentials"`
Status string `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Extra extraFields `json:"-"`
}
func (n *NodeClient) UnmarshalJSON(raw []byte) error {
type plain NodeClient
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*n = NodeClient(p)
n.Extra = extra
return nil
}
func (n NodeClient) MarshalJSON() ([]byte, error) {
type plain NodeClient
return mergeMarshal(plain(n), n.Extra)
}
// KeyPair is a Reality keypair as shipped in job payload keyMaterial.
type KeyPair struct {
PrivateKey string `json:"privateKey"`
PublicKey string `json:"publicKey"`
}
// KeyEntry is a keyStore record (KeyPair + updatedAt).
type KeyEntry struct {
PrivateKey string `json:"privateKey"`
PublicKey string `json:"publicKey"`
UpdatedAt string `json:"updatedAt"`
Extra extraFields `json:"-"`
}
func (k *KeyEntry) UnmarshalJSON(raw []byte) error {
type plain KeyEntry
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*k = KeyEntry(p)
k.Extra = extra
return nil
}
func (k KeyEntry) MarshalJSON() ([]byte, error) {
type plain KeyEntry
return mergeMarshal(plain(k), k.Extra)
}
type JobPayload struct {
Resource json.RawMessage `json:"resource,omitempty"`
Kind string `json:"kind,omitempty"`
KeyMaterial map[string]KeyPair `json:"keyMaterial,omitempty"`
ResourceID string `json:"resourceId,omitempty"`
Extra extraFields `json:"-"`
}
func (p *JobPayload) UnmarshalJSON(raw []byte) error {
type plain JobPayload
var q plain
var extra extraFields
if err := recordJSON(raw, &q, &extra); err != nil {
return err
}
*p = JobPayload(q)
p.Extra = extra
return nil
}
func (p JobPayload) MarshalJSON() ([]byte, error) {
type plain JobPayload
return mergeMarshal(plain(p), p.Extra)
}
// KindOf mirrors JS `payload.kind || payload.resource?.meta?.kind`.
func (p *JobPayload) KindOf() string {
if p == nil {
return ""
}
if p.Kind != "" {
return p.Kind
}
if len(p.Resource) > 0 {
var probe struct {
Meta struct {
Kind string `json:"kind"`
} `json:"meta"`
}
if err := json.Unmarshal(p.Resource, &probe); err == nil {
return probe.Meta.Kind
}
}
return ""
}
type Job struct {
ID string `json:"id"`
ServerID string `json:"serverId"`
NodeID string `json:"nodeId"`
Action string `json:"action"`
Payload *JobPayload `json:"payload"`
Status string `json:"status"`
Attempts int `json:"attempts"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Error *string `json:"error"`
LeaseUntil *string `json:"leaseUntil"`
StartedAt *string `json:"startedAt,omitempty"`
CompletedAt *string `json:"completedAt,omitempty"`
Extra extraFields `json:"-"`
}
func (j *Job) UnmarshalJSON(raw []byte) error {
type plain Job
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*j = Job(p)
j.Extra = extra
return nil
}
func (j Job) MarshalJSON() ([]byte, error) {
type plain Job
return mergeMarshal(plain(j), j.Extra)
}
type Activity struct {
ID string `json:"id"`
At string `json:"at"`
Actor string `json:"actor"`
Action string `json:"action"`
Target string `json:"target"`
Detail map[string]any `json:"detail"`
Extra extraFields `json:"-"`
}
func (a *Activity) UnmarshalJSON(raw []byte) error {
type plain Activity
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*a = Activity(p)
a.Extra = extra
return nil
}
func (a Activity) MarshalJSON() ([]byte, error) {
type plain Activity
return mergeMarshal(plain(a), a.Extra)
}
type Observation struct {
ID string `json:"id"`
CustomerID string `json:"customerId"`
IP string `json:"ip"`
FirstSeen string `json:"firstSeenAt"`
LastSeenAt string `json:"lastSeenAt"`
Extra extraFields `json:"-"`
}
func (o *Observation) UnmarshalJSON(raw []byte) error {
type plain Observation
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*o = Observation(p)
o.Extra = extra
return nil
}
func (o Observation) MarshalJSON() ([]byte, error) {
type plain Observation
return mergeMarshal(plain(o), o.Extra)
}
type SubscriptionAccess struct {
ID string `json:"id"`
CustomerID string `json:"customerId"`
At string `json:"at"`
IP string `json:"ip"`
UserAgent string `json:"userAgent"`
IdentityType string `json:"identityType"`
ClientKey string `json:"clientKey"`
Format string `json:"format"`
Status int `json:"status"`
Reason string `json:"reason"`
Bytes int64 `json:"bytes"`
Extra extraFields `json:"-"`
}
func (a *SubscriptionAccess) UnmarshalJSON(raw []byte) error {
type plain SubscriptionAccess
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*a = SubscriptionAccess(p)
a.Extra = extra
return nil
}
func (a SubscriptionAccess) MarshalJSON() ([]byte, error) {
type plain SubscriptionAccess
return mergeMarshal(plain(a), a.Extra)
}
type SubscriptionClient struct {
CustomerID string `json:"customerId"`
ClientKey string `json:"clientKey"`
IdentityType string `json:"identityType"`
FirstSeenAt string `json:"firstSeenAt"`
LastSeenAt string `json:"lastSeenAt"`
Extra extraFields `json:"-"`
}
func (c *SubscriptionClient) UnmarshalJSON(raw []byte) error {
type plain SubscriptionClient
var p plain
var extra extraFields
if err := recordJSON(raw, &p, &extra); err != nil {
return err
}
*c = SubscriptionClient(p)
c.Extra = extra
return nil
}
func (c SubscriptionClient) MarshalJSON() ([]byte, error) {
type plain SubscriptionClient
return mergeMarshal(plain(c), c.Extra)
}