package model import ( "strings" "time" ) type User struct { ID string `json:"id"` DID string `json:"did"` } type ATProtoSession struct { UserID string `json:"user_id"` DID string `json:"did"` PDSURL string `json:"pds_url"` AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` DPoPKeyJWK string `json:"dpop_key_jwk"` DPoPNonce string `json:"dpop_nonce"` TokenEndpoint string `json:"token_endpoint"` ExpiresAt time.Time `json:"expires_at"` UpdatedAt time.Time `json:"updated_at"` } // --- Document model (Diffdown lexicon) --- type Document struct { // ATProto metadata URI string `json:"uri,omitempty"` CID string `json:"cid,omitempty"` RKey string `json:"rkey,omitempty"` // Document fields Title string `json:"title"` Content *MarkdownContent `json:"content,omitempty"` TextContent string `json:"textContent,omitempty"` Collaborators []string `json:"collaborators,omitempty"` Comments []EmbeddedComment `json:"comments,omitempty"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt,omitempty"` } type MarkdownContent struct { Type string `json:"$type"` Flavor string `json:"flavor"` Text MarkdownText `json:"text"` } type MarkdownText struct { RawMarkdown string `json:"rawMarkdown"` } type EmbeddedComment struct { ID string `json:"id"` // random 8-char hex for dedup ThreadID string `json:"threadId"` // links to ProseMirror comment mark QuotedText string `json:"quotedText"` // text the comment was anchored to Text string `json:"text"` Author string `json:"author"` // DID AuthorHandle string `json:"authorHandle"` // resolved handle, may be empty CreatedAt string `json:"createdAt"` // RFC3339 } type CommentRecord struct { ID string `json:"id"` // record key (rkey) ThreadID string `json:"threadId"` // groups replies into threads DocRKey string `json:"docRKey"` // document rkey this comment belongs to DocOwnerDID string `json:"docOwnerDid"` // owner DID for quick filtering QuotedText string `json:"quotedText"` // text the comment was anchored to Text string `json:"text"` // comment content Author string `json:"author"` // DID AuthorHandle string `json:"authorHandle"` // resolved handle CreatedAt string `json:"createdAt"` // RFC3339 ReplyTo string `json:"replyTo,omitempty"` // parent comment URI (null for root) Resolved bool `json:"resolved"` // thread resolution state } type Invite struct { ID string `json:"id"` DocumentRKey string `json:"document_rkey"` Token string `json:"token"` CreatedBy string `json:"created_by"` CreatedAt time.Time `json:"created_at"` ExpiresAt time.Time `json:"expires_at"` } // RKeyFromURI extracts the rkey (last path segment) from an at:// URI. func RKeyFromURI(uri string) string { parts := strings.Split(uri, "/") if len(parts) > 0 { return parts[len(parts)-1] } return "" } const ( CollectionDocument = "com.diffdown.document" CollectionComment = "com.diffdown.comment" )