1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package statusstate
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/plc"
)
type PLCIdentity struct {
Found bool
Head string
OpCount int64
Tombstoned bool
}
type HTTPPLCClient struct {
baseURL string
client *http.Client
}
func NewHTTPPLCClient(baseURL string, client *http.Client) (*HTTPPLCClient, error) {
parsed, err := sourceBaseURL(baseURL, "PLC")
if err != nil {
return nil, err
}
return &HTTPPLCClient{baseURL: parsed, client: httpClient(client)}, nil
}
func (c *HTTPPLCClient) Identity(ctx context.Context, did string) (PLCIdentity, error) {
entries, err := c.auditLog(ctx, did)
var plcErr *plc.Error
if errors.As(err, &plcErr) && plcErr.Status == http.StatusNotFound {
return PLCIdentity{}, nil
}
if err != nil {
return PLCIdentity{}, err
}
return Summarize(entries), nil
}
func Summarize(entries []plc.LogEntry) PLCIdentity {
var out PLCIdentity
for i := range entries {
if entries[i].Nullified {
continue
}
out.OpCount++
out.Head = entries[i].CID
out.Tombstoned = entries[i].Operation.Type == plc.TypeTombstone
}
out.Found = out.OpCount > 0
return out
}
func (c *HTTPPLCClient) auditLog(ctx context.Context, did string) ([]plc.LogEntry, error) {
if c == nil {
return nil, fmt.Errorf("statusstate: the HTTP PLC client is nil")
}
did = strings.TrimSpace(did)
if did == "" {
return nil, fmt.Errorf("statusstate: the PLC audit source needs a did")
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/"+url.PathEscape(did)+"/log/audit", nil)
if err != nil {
return nil, err
}
response, err := c.client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
body, err := io.ReadAll(io.LimitReader(response.Body, maxSourceBody))
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, plcStatusError(response.StatusCode, body)
}
var entries []plc.LogEntry
if err := json.Unmarshal(body, &entries); err != nil {
return nil, fmt.Errorf("statusstate: the PLC audit log is not JSON: %w", err)
}
return entries, nil
}
func plcStatusError(status int, body []byte) error {
var payload struct {
Message string `json:"message"`
}
_ = json.Unmarshal(body, &payload)
message := strings.TrimSpace(payload.Message)
if message == "" {
message = strings.TrimSpace(string(body))
}
if message == "" {
message = fmt.Sprintf("HTTP %d", status)
}
return &plc.Error{Status: status, Message: message}
}