A community based topic aggregation platform built on atproto
1package utils
2
3import (
4 "database/sql"
5 "strings"
6 "time"
7)
8
9// ExtractRKeyFromURI extracts the record key from an AT-URI
10// Format: at://did/collection/rkey -> rkey
11func ExtractRKeyFromURI(uri string) string {
12 parts := strings.Split(uri, "/")
13 if len(parts) >= 4 {
14 return parts[len(parts)-1]
15 }
16 return ""
17}
18
19// StringFromNull converts sql.NullString to string
20// Returns empty string if the NullString is not valid
21func StringFromNull(ns sql.NullString) string {
22 if ns.Valid {
23 return ns.String
24 }
25 return ""
26}
27
28// ParseCreatedAt extracts and parses the createdAt timestamp from an atProto record
29// Falls back to time.Now() if the field is missing or invalid
30// This preserves chronological ordering during Jetstream replays and backfills
31func ParseCreatedAt(record map[string]interface{}) time.Time {
32 if record == nil {
33 return time.Now()
34 }
35
36 createdAtStr, ok := record["createdAt"].(string)
37 if !ok || createdAtStr == "" {
38 return time.Now()
39 }
40
41 // atProto uses RFC3339 format for datetime fields
42 createdAt, err := time.Parse(time.RFC3339, createdAtStr)
43 if err != nil {
44 // Fallback to now if parsing fails
45 return time.Now()
46 }
47
48 return createdAt
49}