wip
1package plc
2
3import (
4 "regexp"
5 "strings"
6)
7
8// MaxHandleLength is the maximum allowed handle length for database storage
9const MaxHandleLength = 500
10
11// Handle validation regex per AT Protocol spec
12// Ensures proper domain format: alphanumeric labels separated by dots, TLD starts with letter
13var handleRegex = regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)
14
15// ExtractHandle safely extracts the handle from a PLC operation
16func ExtractHandle(op *PLCOperation) string {
17 if op == nil || op.Operation == nil {
18 return ""
19 }
20
21 // Get "alsoKnownAs"
22 aka, ok := op.Operation["alsoKnownAs"].([]interface{})
23 if !ok {
24 return ""
25 }
26
27 // Find the handle (e.g., "at://handle.bsky.social")
28 for _, item := range aka {
29 if handle, ok := item.(string); ok {
30 if strings.HasPrefix(handle, "at://") {
31 return strings.TrimPrefix(handle, "at://")
32 }
33 }
34 }
35 return ""
36}
37
38// ValidateHandle checks if a handle is valid for database storage
39// Returns empty string if handle is invalid (too long or wrong format)
40func ValidateHandle(handle string) string {
41 if handle == "" {
42 return ""
43 }
44
45 // Check length first (faster)
46 if len(handle) > MaxHandleLength {
47 return ""
48 }
49
50 // Validate format using regex
51 if !handleRegex.MatchString(handle) {
52 return ""
53 }
54
55 return handle
56}
57
58// ExtractPDS safely extracts the PDS endpoint from a PLC operation
59func ExtractPDS(op *PLCOperation) string {
60 if op == nil || op.Operation == nil {
61 return ""
62 }
63
64 // Get "services"
65 services, ok := op.Operation["services"].(map[string]interface{})
66 if !ok {
67 return ""
68 }
69
70 // Get "atproto_pds"
71 pdsService, ok := services["atproto_pds"].(map[string]interface{})
72 if !ok {
73 return ""
74 }
75
76 // Get "endpoint"
77 if endpoint, ok := pdsService["endpoint"].(string); ok {
78 return endpoint
79 }
80
81 return ""
82}
83
84// DIDInfo contains extracted metadata from a PLC operation
85type DIDInfo struct {
86 Handle string
87 PDS string
88}
89
90// ExtractDIDInfo extracts both handle and PDS from an operation
91func ExtractDIDInfo(op *PLCOperation) DIDInfo {
92 return DIDInfo{
93 Handle: ExtractHandle(op),
94 PDS: ExtractPDS(op),
95 }
96}
97
98// ExtractDIDInfoMap creates a map of DID -> info from operations
99// Processes in reverse order to get the latest state for each DID
100func ExtractDIDInfoMap(ops []PLCOperation) map[string]DIDInfo {
101 infoMap := make(map[string]DIDInfo)
102
103 // Process in reverse to get latest state
104 for i := len(ops) - 1; i >= 0; i-- {
105 op := ops[i]
106 if _, exists := infoMap[op.DID]; !exists {
107 infoMap[op.DID] = ExtractDIDInfo(&op)
108 }
109 }
110
111 return infoMap
112}