1package records23import (4 "context"5 "encoding/json"6 "errors"7 "fmt"8 "time"910 "github.com/FelineStateMachine/tinyrelay/internal/policy"11 "github.com/FelineStateMachine/tinyrelay/internal/storage"12)1314// Execute covers record-owned management operations.15func (s *Service) Execute(ctx context.Context, actor, method string, params []json.RawMessage) (any, error) {16 p := s.policy()17 if actor != "" && actor != p.Owner {18 return nil, errors.New("restricted: owner record operation")19 }20 now := time.Now().Unix()21 _ = s.audit(ctx, actor, method, "", "", now)22 switch method {23 case "successionstatus":24 var seen, warning, last int6425 _ = s.store.GetSetting(ctx, "records.owner_seen", &seen)26 _ = s.store.GetSetting(ctx, "records.succession_warning", &warning)27 _ = s.store.GetSetting(ctx, "records.succession_notify", &last)28 var log []successionLog29 _ = s.store.GetSetting(ctx, "records.succession_log", &log)30 var warningState any31 if warning > 0 {32 warningState = map[string]any{"since": warning, "lastNotified": last}33 }34 return map[string]any{"ownerSeenAt": seen, "warning": warningState, "handoverAt": func() int64 {35 if warning > 0 {36 return warning + 30*8640037 }38 if p.Succession != nil {39 return seen + int64(p.Succession.AfterDays)*86400 + 30*8640040 }41 return 042 }(), "log": log, "succession": p.Succession}, nil43 case "setsuccession":44 if len(params) == 0 {45 return nil, errors.New("invalid: missing succession")46 }47 var v policy.Succession48 if err := json.Unmarshal(params[0], &v); err != nil {49 return nil, err50 }51 if v.AfterDays != 90 && v.AfterDays != 180 && v.AfterDays != 365 {52 return nil, errors.New("invalid: succession delay")53 }54 if s.setPolicy != nil {55 next := p56 next.Succession = &v57 next.Notify.Succession = true58 if err := s.setPolicy(next); err != nil {59 return nil, err60 }61 }62 return v, s.store.PutSetting(ctx, "records.succession", v)63 case "clearsuccession":64 if s.setPolicy != nil {65 next := p66 next.Succession = nil67 next.Notify.Succession = false68 if err := s.setPolicy(next); err != nil {69 return nil, err70 }71 }72 return true, s.store.PutSetting(ctx, "records.succession", nil)73 case "listpins":74 return s.store.DB().QueryContext(ctx, `SELECT ref FROM records_pins ORDER BY position`)75 case "pinevent", "unpinevent":76 if len(params) == 0 {77 return nil, errors.New("invalid: missing pin")78 }79 var ref string80 if err := json.Unmarshal(params[0], &ref); err != nil {81 return nil, err82 }83 var refs []string84 rows, _ := s.store.DB().QueryContext(ctx, `SELECT ref FROM records_pins ORDER BY position`)85 for rows.Next() {86 var x string87 _ = rows.Scan(&x)88 if x != ref {89 refs = append(refs, x)90 }91 }92 _ = rows.Close()93 if method == "pinevent" {94 refs = append(refs, ref)95 }96 return s.SetPins(ctx, refs, now)97 case "listviews":98 return s.ViewSummaries(ctx)99 case "notifytest":100 return true, s.notify(ctx, "test", p.Owner, p.Owner, now)101 case "notifystatus":102 return map[string]any{"reports": p.Notify.Reports, "jobs": p.Notify.Jobs, "succession": p.Notify.Succession, "digest": p.Notify.Digest}, nil103 default:104 return nil, fmt.Errorf("unsupported: records method %q", method)105 }106}107108func (s *Service) Clock(ctx context.Context) (int64, error) {109 var v int64110 err := s.store.GetSetting(ctx, "records.clock", &v)111 return v, err112}113114func parseIntSetting(ctx context.Context, s *storage.Store, key string) int64 {115 var v int64116 _ = s.GetSetting(ctx, key, &v)117 return v118}