new lexicons, bunch of other build-out

Orual 19fc059b 856e420f

+8356 -260
+35
crates/weaver-api/lexicons/sh_weaver_collab_accept.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.collab.accept", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Acceptance of a collaboration invite. Completes the two-way agreement.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "invite", 13 + "resource", 14 + "createdAt" 15 + ], 16 + "properties": { 17 + "createdAt": { 18 + "type": "string", 19 + "format": "datetime" 20 + }, 21 + "invite": { 22 + "type": "ref", 23 + "description": "Reference to the invite record being accepted.", 24 + "ref": "com.atproto.repo.strongRef" 25 + }, 26 + "resource": { 27 + "type": "string", 28 + "description": "URI of the resource (denormalized for easier querying).", 29 + "format": "at-uri" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + }
+18
crates/weaver-api/lexicons/sh_weaver_collab_defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.collab.defs", 4 + "defs": { 5 + "chapter": { 6 + "type": "token", 7 + "description": "Collaboration scoped to a chapter." 8 + }, 9 + "entry": { 10 + "type": "token", 11 + "description": "Collaboration scoped to a single entry." 12 + }, 13 + "notebook": { 14 + "type": "token", 15 + "description": "Collaboration scoped to an entire notebook." 16 + } 17 + } 18 + }
+60
crates/weaver-api/lexicons/sh_weaver_collab_invite.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.collab.invite", 4 + "defs": { 5 + "collabScope": { 6 + "type": "string", 7 + "description": "The scope/type of collaboration.", 8 + "knownValues": [ 9 + "sh.weaver.collab.defs#notebook", 10 + "sh.weaver.collab.defs#entry", 11 + "sh.weaver.collab.defs#chapter" 12 + ] 13 + }, 14 + "main": { 15 + "type": "record", 16 + "description": "Invitation to collaborate on a resource (notebook, entry, chapter, etc.). Creates half of a two-way agreement.", 17 + "key": "tid", 18 + "record": { 19 + "type": "object", 20 + "required": [ 21 + "resource", 22 + "invitee", 23 + "createdAt" 24 + ], 25 + "properties": { 26 + "createdAt": { 27 + "type": "string", 28 + "format": "datetime" 29 + }, 30 + "expiresAt": { 31 + "type": "string", 32 + "description": "Optional expiration for the invite.", 33 + "format": "datetime" 34 + }, 35 + "invitee": { 36 + "type": "string", 37 + "description": "DID of the user being invited.", 38 + "format": "did" 39 + }, 40 + "message": { 41 + "type": "string", 42 + "description": "Optional message to the invitee.", 43 + "maxLength": 3000, 44 + "maxGraphemes": 300 45 + }, 46 + "resource": { 47 + "type": "ref", 48 + "description": "The resource to collaborate on (notebook, entry, chapter, etc.).", 49 + "ref": "com.atproto.repo.strongRef" 50 + }, 51 + "scope": { 52 + "type": "ref", 53 + "description": "Optional explicit scope type. If omitted, inferred from resource lexicon.", 54 + "ref": "#collabScope" 55 + } 56 + } 57 + } 58 + } 59 + } 60 + }
+4
crates/weaver-api/lexicons/sh_weaver_edit_diff.json
··· 13 13 "doc" 14 14 ], 15 15 "properties": { 16 + "createdAt": { 17 + "type": "string", 18 + "format": "datetime" 19 + }, 16 20 "doc": { 17 21 "type": "ref", 18 22 "ref": "sh.weaver.edit.defs#docRef"
+35
crates/weaver-api/lexicons/sh_weaver_graph_bookmark.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.bookmark", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Bookmark a notebook or entry for later reading.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "subject", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "createdAt": { 17 + "type": "string", 18 + "format": "datetime" 19 + }, 20 + "note": { 21 + "type": "string", 22 + "description": "Optional private note about why you saved this.", 23 + "maxLength": 3000, 24 + "maxGraphemes": 300 25 + }, 26 + "subject": { 27 + "type": "ref", 28 + "description": "The notebook or entry being bookmarked.", 29 + "ref": "com.atproto.repo.strongRef" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + }
+14
crates/weaver-api/lexicons/sh_weaver_graph_defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.defs", 4 + "defs": { 5 + "curatelist": { 6 + "type": "token", 7 + "description": "A curated collection of notebooks/entries for sharing." 8 + }, 9 + "readinglist": { 10 + "type": "token", 11 + "description": "A personal reading list." 12 + } 13 + } 14 + }
+29
crates/weaver-api/lexicons/sh_weaver_graph_follow.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.follow", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Request to follow an author. Requires acceptance to be active.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "subject", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "createdAt": { 17 + "type": "string", 18 + "format": "datetime" 19 + }, 20 + "subject": { 21 + "type": "string", 22 + "description": "DID of the author to follow.", 23 + "format": "did" 24 + } 25 + } 26 + } 27 + } 28 + } 29 + }
+29
crates/weaver-api/lexicons/sh_weaver_graph_followAccept.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.followAccept", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Acceptance of a follow request. Completes the two-way agreement.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "follow", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "createdAt": { 17 + "type": "string", 18 + "format": "datetime" 19 + }, 20 + "follow": { 21 + "type": "ref", 22 + "description": "Reference to the follow record being accepted.", 23 + "ref": "com.atproto.repo.strongRef" 24 + } 25 + } 26 + } 27 + } 28 + } 29 + }
+33
crates/weaver-api/lexicons/sh_weaver_graph_followGate.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.followGate", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Settings controlling follow approval behavior. Absence means auto-accept.", 8 + "key": "self", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "createdAt": { 16 + "type": "string", 17 + "format": "datetime" 18 + }, 19 + "invalidatePrior": { 20 + "type": "boolean", 21 + "description": "If true, previously auto-accepted follows are invalidated when requireApproval is enabled. Appview should treat followAccept records created before this gate's createdAt as invalid.", 22 + "default": false 23 + }, 24 + "requireApproval": { 25 + "type": "boolean", 26 + "description": "If true, follows require manual acceptance.", 27 + "default": false 28 + } 29 + } 30 + } 31 + } 32 + } 33 + }
+29
crates/weaver-api/lexicons/sh_weaver_graph_like.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.like", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record declaring a 'like' of a notebook or entry.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "subject", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "createdAt": { 17 + "type": "string", 18 + "format": "datetime" 19 + }, 20 + "subject": { 21 + "type": "ref", 22 + "description": "The notebook or entry being liked.", 23 + "ref": "com.atproto.repo.strongRef" 24 + } 25 + } 26 + } 27 + } 28 + } 29 + }
+56
crates/weaver-api/lexicons/sh_weaver_graph_list.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.list", 4 + "defs": { 5 + "listPurpose": { 6 + "type": "string", 7 + "knownValues": [ 8 + "sh.weaver.graph.defs#curatelist", 9 + "sh.weaver.graph.defs#readinglist" 10 + ] 11 + }, 12 + "main": { 13 + "type": "record", 14 + "description": "A curated list of notebooks and/or entries.", 15 + "key": "tid", 16 + "record": { 17 + "type": "object", 18 + "required": [ 19 + "name", 20 + "purpose", 21 + "createdAt" 22 + ], 23 + "properties": { 24 + "avatar": { 25 + "type": "blob", 26 + "accept": [ 27 + "image/png", 28 + "image/jpeg" 29 + ], 30 + "maxSize": 1000000 31 + }, 32 + "createdAt": { 33 + "type": "string", 34 + "format": "datetime" 35 + }, 36 + "description": { 37 + "type": "string", 38 + "maxLength": 3000, 39 + "maxGraphemes": 300 40 + }, 41 + "name": { 42 + "type": "string", 43 + "description": "Display name for the list.", 44 + "minLength": 1, 45 + "maxLength": 64 46 + }, 47 + "purpose": { 48 + "type": "ref", 49 + "description": "The purpose/type of list.", 50 + "ref": "#listPurpose" 51 + } 52 + } 53 + } 54 + } 55 + } 56 + }
+35
crates/weaver-api/lexicons/sh_weaver_graph_listitem.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.listitem", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "An item in a list.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "subject", 13 + "list", 14 + "createdAt" 15 + ], 16 + "properties": { 17 + "createdAt": { 18 + "type": "string", 19 + "format": "datetime" 20 + }, 21 + "list": { 22 + "type": "string", 23 + "description": "Reference to the list record.", 24 + "format": "at-uri" 25 + }, 26 + "subject": { 27 + "type": "ref", 28 + "description": "The notebook or entry being added to the list.", 29 + "ref": "com.atproto.repo.strongRef" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + }
+29
crates/weaver-api/lexicons/sh_weaver_graph_subscribe.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.subscribe", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Request to subscribe to a notebook. Requires acceptance to be active.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "notebook", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "createdAt": { 17 + "type": "string", 18 + "format": "datetime" 19 + }, 20 + "notebook": { 21 + "type": "string", 22 + "description": "URI of the notebook to subscribe to.", 23 + "format": "at-uri" 24 + } 25 + } 26 + } 27 + } 28 + } 29 + }
+29
crates/weaver-api/lexicons/sh_weaver_graph_subscribeAccept.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.subscribeAccept", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Acceptance of a subscription request.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "subscribe", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "createdAt": { 17 + "type": "string", 18 + "format": "datetime" 19 + }, 20 + "subscribe": { 21 + "type": "ref", 22 + "description": "Reference to the subscribe record being accepted.", 23 + "ref": "com.atproto.repo.strongRef" 24 + } 25 + } 26 + } 27 + } 28 + } 29 + }
+5
crates/weaver-api/lexicons/sh_weaver_notebook_book.json
··· 47 47 "title": { 48 48 "type": "ref", 49 49 "ref": "sh.weaver.notebook.defs#title" 50 + }, 51 + "updatedAt": { 52 + "type": "string", 53 + "description": "Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios.", 54 + "format": "datetime" 50 55 } 51 56 } 52 57 }
+5
crates/weaver-api/lexicons/sh_weaver_notebook_entry.json
··· 65 65 "title": { 66 66 "type": "ref", 67 67 "ref": "sh.weaver.notebook.defs#title" 68 + }, 69 + "updatedAt": { 70 + "type": "string", 71 + "description": "Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios.", 72 + "format": "datetime" 68 73 } 69 74 } 70 75 }
+44
crates/weaver-api/lexicons/sh_weaver_notebook_getEntry.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.getEntry", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get an entry view by notebook URI and index, including prev/next navigation.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "notebook" 12 + ], 13 + "properties": { 14 + "index": { 15 + "type": "integer", 16 + "description": "Zero-based index of the entry in the notebook's entry list.", 17 + "default": 0, 18 + "minimum": 0 19 + }, 20 + "notebook": { 21 + "type": "string", 22 + "description": "AT-URI of the notebook containing the entry.", 23 + "format": "at-uri" 24 + } 25 + } 26 + }, 27 + "output": { 28 + "encoding": "application/json", 29 + "schema": { 30 + "type": "ref", 31 + "ref": "sh.weaver.notebook.defs#bookEntryView" 32 + } 33 + }, 34 + "errors": [ 35 + { 36 + "name": "NotebookNotFound" 37 + }, 38 + { 39 + "name": "EntryNotFound" 40 + } 41 + ] 42 + } 43 + } 44 + }
+57
crates/weaver-api/lexicons/sh_weaver_notebook_getEntryByTitle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.getEntryByTitle", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get an entry view by notebook URI and title. Matches on either the entry's path or title field.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "notebook", 12 + "title" 13 + ], 14 + "properties": { 15 + "notebook": { 16 + "type": "string", 17 + "description": "AT-URI of the notebook containing the entry.", 18 + "format": "at-uri" 19 + }, 20 + "title": { 21 + "type": "string", 22 + "description": "Title or path of the entry to fetch.", 23 + "maxLength": 300 24 + } 25 + } 26 + }, 27 + "output": { 28 + "encoding": "application/json", 29 + "schema": { 30 + "type": "object", 31 + "required": [ 32 + "entry", 33 + "record" 34 + ], 35 + "properties": { 36 + "entry": { 37 + "type": "ref", 38 + "ref": "sh.weaver.notebook.defs#bookEntryView" 39 + }, 40 + "record": { 41 + "type": "unknown", 42 + "description": "The raw entry record data." 43 + } 44 + } 45 + } 46 + }, 47 + "errors": [ 48 + { 49 + "name": "NotebookNotFound" 50 + }, 51 + { 52 + "name": "EntryNotFound" 53 + } 54 + ] 55 + } 56 + } 57 + }
+46
crates/weaver-api/lexicons/sh_weaver_notebook_getNotebook.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.getNotebook", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a notebook view by its AT-URI, including hydrated author profiles and entry list.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "notebook" 12 + ], 13 + "properties": { 14 + "notebook": { 15 + "type": "string", 16 + "description": "AT-URI of the notebook to fetch.", 17 + "format": "at-uri" 18 + } 19 + } 20 + }, 21 + "output": { 22 + "encoding": "application/json", 23 + "schema": { 24 + "type": "object", 25 + "required": [ 26 + "notebook", 27 + "entries" 28 + ], 29 + "properties": { 30 + "entries": { 31 + "type": "array", 32 + "items": { 33 + "type": "ref", 34 + "ref": "com.atproto.repo.strongRef" 35 + } 36 + }, 37 + "notebook": { 38 + "type": "ref", 39 + "ref": "sh.weaver.notebook.defs#notebookView" 40 + } 41 + } 42 + } 43 + } 44 + } 45 + } 46 + }
+57
crates/weaver-api/lexicons/sh_weaver_notebook_getNotebookByTitle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.getNotebookByTitle", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a notebook view by actor and title. Matches on either the notebook's path or title field.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "actor", 12 + "title" 13 + ], 14 + "properties": { 15 + "actor": { 16 + "type": "string", 17 + "description": "Handle or DID of the notebook owner.", 18 + "format": "at-identifier" 19 + }, 20 + "title": { 21 + "type": "string", 22 + "description": "Title or path of the notebook to fetch.", 23 + "maxLength": 300 24 + } 25 + } 26 + }, 27 + "output": { 28 + "encoding": "application/json", 29 + "schema": { 30 + "type": "object", 31 + "required": [ 32 + "notebook", 33 + "entries" 34 + ], 35 + "properties": { 36 + "entries": { 37 + "type": "array", 38 + "items": { 39 + "type": "ref", 40 + "ref": "com.atproto.repo.strongRef" 41 + } 42 + }, 43 + "notebook": { 44 + "type": "ref", 45 + "ref": "sh.weaver.notebook.defs#notebookView" 46 + } 47 + } 48 + } 49 + }, 50 + "errors": [ 51 + { 52 + "name": "NotebookNotFound" 53 + } 54 + ] 55 + } 56 + } 57 + }
+2
crates/weaver-api/src/sh_weaver.rs
··· 4 4 // Any manual changes will be overwritten on the next regeneration. 5 5 6 6 pub mod actor; 7 + pub mod collab; 7 8 pub mod edit; 8 9 pub mod embed; 10 + pub mod graph; 9 11 pub mod notebook; 10 12 pub mod publish;
+63
crates/weaver-api/src/sh_weaver/collab.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.collab.defs 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + pub mod accept; 9 + pub mod invite; 10 + 11 + /// Collaboration scoped to a chapter. 12 + #[derive( 13 + serde::Serialize, 14 + serde::Deserialize, 15 + Debug, 16 + Clone, 17 + PartialEq, 18 + Eq, 19 + Hash, 20 + jacquard_derive::IntoStatic 21 + )] 22 + pub struct Chapter; 23 + impl std::fmt::Display for Chapter { 24 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 + write!(f, "chapter") 26 + } 27 + } 28 + 29 + /// Collaboration scoped to a single entry. 30 + #[derive( 31 + serde::Serialize, 32 + serde::Deserialize, 33 + Debug, 34 + Clone, 35 + PartialEq, 36 + Eq, 37 + Hash, 38 + jacquard_derive::IntoStatic 39 + )] 40 + pub struct Entry; 41 + impl std::fmt::Display for Entry { 42 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 + write!(f, "entry") 44 + } 45 + } 46 + 47 + /// Collaboration scoped to an entire notebook. 48 + #[derive( 49 + serde::Serialize, 50 + serde::Deserialize, 51 + Debug, 52 + Clone, 53 + PartialEq, 54 + Eq, 55 + Hash, 56 + jacquard_derive::IntoStatic 57 + )] 58 + pub struct Notebook; 59 + impl std::fmt::Display for Notebook { 60 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 + write!(f, "notebook") 62 + } 63 + }
+376
crates/weaver-api/src/sh_weaver/collab/accept.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.collab.accept 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// Acceptance of a collaboration invite. Completes the two-way agreement. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct Accept<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// Reference to the invite record being accepted. 23 + #[serde(borrow)] 24 + pub invite: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 25 + /// URI of the resource (denormalized for easier querying). 26 + #[serde(borrow)] 27 + pub resource: jacquard_common::types::string::AtUri<'a>, 28 + } 29 + 30 + pub mod accept_state { 31 + 32 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 33 + #[allow(unused)] 34 + use ::core::marker::PhantomData; 35 + mod sealed { 36 + pub trait Sealed {} 37 + } 38 + /// State trait tracking which required fields have been set 39 + pub trait State: sealed::Sealed { 40 + type Invite; 41 + type Resource; 42 + type CreatedAt; 43 + } 44 + /// Empty state - all required fields are unset 45 + pub struct Empty(()); 46 + impl sealed::Sealed for Empty {} 47 + impl State for Empty { 48 + type Invite = Unset; 49 + type Resource = Unset; 50 + type CreatedAt = Unset; 51 + } 52 + ///State transition - sets the `invite` field to Set 53 + pub struct SetInvite<S: State = Empty>(PhantomData<fn() -> S>); 54 + impl<S: State> sealed::Sealed for SetInvite<S> {} 55 + impl<S: State> State for SetInvite<S> { 56 + type Invite = Set<members::invite>; 57 + type Resource = S::Resource; 58 + type CreatedAt = S::CreatedAt; 59 + } 60 + ///State transition - sets the `resource` field to Set 61 + pub struct SetResource<S: State = Empty>(PhantomData<fn() -> S>); 62 + impl<S: State> sealed::Sealed for SetResource<S> {} 63 + impl<S: State> State for SetResource<S> { 64 + type Invite = S::Invite; 65 + type Resource = Set<members::resource>; 66 + type CreatedAt = S::CreatedAt; 67 + } 68 + ///State transition - sets the `created_at` field to Set 69 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 70 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 71 + impl<S: State> State for SetCreatedAt<S> { 72 + type Invite = S::Invite; 73 + type Resource = S::Resource; 74 + type CreatedAt = Set<members::created_at>; 75 + } 76 + /// Marker types for field names 77 + #[allow(non_camel_case_types)] 78 + pub mod members { 79 + ///Marker type for the `invite` field 80 + pub struct invite(()); 81 + ///Marker type for the `resource` field 82 + pub struct resource(()); 83 + ///Marker type for the `created_at` field 84 + pub struct created_at(()); 85 + } 86 + } 87 + 88 + /// Builder for constructing an instance of this type 89 + pub struct AcceptBuilder<'a, S: accept_state::State> { 90 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 91 + __unsafe_private_named: ( 92 + ::core::option::Option<jacquard_common::types::string::Datetime>, 93 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 94 + ::core::option::Option<jacquard_common::types::string::AtUri<'a>>, 95 + ), 96 + _phantom: ::core::marker::PhantomData<&'a ()>, 97 + } 98 + 99 + impl<'a> Accept<'a> { 100 + /// Create a new builder for this type 101 + pub fn new() -> AcceptBuilder<'a, accept_state::Empty> { 102 + AcceptBuilder::new() 103 + } 104 + } 105 + 106 + impl<'a> AcceptBuilder<'a, accept_state::Empty> { 107 + /// Create a new builder with all fields unset 108 + pub fn new() -> Self { 109 + AcceptBuilder { 110 + _phantom_state: ::core::marker::PhantomData, 111 + __unsafe_private_named: (None, None, None), 112 + _phantom: ::core::marker::PhantomData, 113 + } 114 + } 115 + } 116 + 117 + impl<'a, S> AcceptBuilder<'a, S> 118 + where 119 + S: accept_state::State, 120 + S::CreatedAt: accept_state::IsUnset, 121 + { 122 + /// Set the `createdAt` field (required) 123 + pub fn created_at( 124 + mut self, 125 + value: impl Into<jacquard_common::types::string::Datetime>, 126 + ) -> AcceptBuilder<'a, accept_state::SetCreatedAt<S>> { 127 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 128 + AcceptBuilder { 129 + _phantom_state: ::core::marker::PhantomData, 130 + __unsafe_private_named: self.__unsafe_private_named, 131 + _phantom: ::core::marker::PhantomData, 132 + } 133 + } 134 + } 135 + 136 + impl<'a, S> AcceptBuilder<'a, S> 137 + where 138 + S: accept_state::State, 139 + S::Invite: accept_state::IsUnset, 140 + { 141 + /// Set the `invite` field (required) 142 + pub fn invite( 143 + mut self, 144 + value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 145 + ) -> AcceptBuilder<'a, accept_state::SetInvite<S>> { 146 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 147 + AcceptBuilder { 148 + _phantom_state: ::core::marker::PhantomData, 149 + __unsafe_private_named: self.__unsafe_private_named, 150 + _phantom: ::core::marker::PhantomData, 151 + } 152 + } 153 + } 154 + 155 + impl<'a, S> AcceptBuilder<'a, S> 156 + where 157 + S: accept_state::State, 158 + S::Resource: accept_state::IsUnset, 159 + { 160 + /// Set the `resource` field (required) 161 + pub fn resource( 162 + mut self, 163 + value: impl Into<jacquard_common::types::string::AtUri<'a>>, 164 + ) -> AcceptBuilder<'a, accept_state::SetResource<S>> { 165 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 166 + AcceptBuilder { 167 + _phantom_state: ::core::marker::PhantomData, 168 + __unsafe_private_named: self.__unsafe_private_named, 169 + _phantom: ::core::marker::PhantomData, 170 + } 171 + } 172 + } 173 + 174 + impl<'a, S> AcceptBuilder<'a, S> 175 + where 176 + S: accept_state::State, 177 + S::Invite: accept_state::IsSet, 178 + S::Resource: accept_state::IsSet, 179 + S::CreatedAt: accept_state::IsSet, 180 + { 181 + /// Build the final struct 182 + pub fn build(self) -> Accept<'a> { 183 + Accept { 184 + created_at: self.__unsafe_private_named.0.unwrap(), 185 + invite: self.__unsafe_private_named.1.unwrap(), 186 + resource: self.__unsafe_private_named.2.unwrap(), 187 + extra_data: Default::default(), 188 + } 189 + } 190 + /// Build the final struct with custom extra_data 191 + pub fn build_with_data( 192 + self, 193 + extra_data: std::collections::BTreeMap< 194 + jacquard_common::smol_str::SmolStr, 195 + jacquard_common::types::value::Data<'a>, 196 + >, 197 + ) -> Accept<'a> { 198 + Accept { 199 + created_at: self.__unsafe_private_named.0.unwrap(), 200 + invite: self.__unsafe_private_named.1.unwrap(), 201 + resource: self.__unsafe_private_named.2.unwrap(), 202 + extra_data: Some(extra_data), 203 + } 204 + } 205 + } 206 + 207 + impl<'a> Accept<'a> { 208 + pub fn uri( 209 + uri: impl Into<jacquard_common::CowStr<'a>>, 210 + ) -> Result< 211 + jacquard_common::types::uri::RecordUri<'a, AcceptRecord>, 212 + jacquard_common::types::uri::UriError, 213 + > { 214 + jacquard_common::types::uri::RecordUri::try_from_uri( 215 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 216 + ) 217 + } 218 + } 219 + 220 + /// Typed wrapper for GetRecord response with this collection's record type. 221 + #[derive( 222 + serde::Serialize, 223 + serde::Deserialize, 224 + Debug, 225 + Clone, 226 + PartialEq, 227 + Eq, 228 + jacquard_derive::IntoStatic 229 + )] 230 + #[serde(rename_all = "camelCase")] 231 + pub struct AcceptGetRecordOutput<'a> { 232 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 233 + #[serde(borrow)] 234 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 235 + #[serde(borrow)] 236 + pub uri: jacquard_common::types::string::AtUri<'a>, 237 + #[serde(borrow)] 238 + pub value: Accept<'a>, 239 + } 240 + 241 + impl From<AcceptGetRecordOutput<'_>> for Accept<'_> { 242 + fn from(output: AcceptGetRecordOutput<'_>) -> Self { 243 + use jacquard_common::IntoStatic; 244 + output.value.into_static() 245 + } 246 + } 247 + 248 + impl jacquard_common::types::collection::Collection for Accept<'_> { 249 + const NSID: &'static str = "sh.weaver.collab.accept"; 250 + type Record = AcceptRecord; 251 + } 252 + 253 + /// Marker type for deserializing records from this collection. 254 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 255 + pub struct AcceptRecord; 256 + impl jacquard_common::xrpc::XrpcResp for AcceptRecord { 257 + const NSID: &'static str = "sh.weaver.collab.accept"; 258 + const ENCODING: &'static str = "application/json"; 259 + type Output<'de> = AcceptGetRecordOutput<'de>; 260 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 261 + } 262 + 263 + impl jacquard_common::types::collection::Collection for AcceptRecord { 264 + const NSID: &'static str = "sh.weaver.collab.accept"; 265 + type Record = AcceptRecord; 266 + } 267 + 268 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Accept<'a> { 269 + fn nsid() -> &'static str { 270 + "sh.weaver.collab.accept" 271 + } 272 + fn def_name() -> &'static str { 273 + "main" 274 + } 275 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 276 + lexicon_doc_sh_weaver_collab_accept() 277 + } 278 + fn validate( 279 + &self, 280 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 281 + Ok(()) 282 + } 283 + } 284 + 285 + fn lexicon_doc_sh_weaver_collab_accept() -> ::jacquard_lexicon::lexicon::LexiconDoc< 286 + 'static, 287 + > { 288 + ::jacquard_lexicon::lexicon::LexiconDoc { 289 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 290 + id: ::jacquard_common::CowStr::new_static("sh.weaver.collab.accept"), 291 + revision: None, 292 + description: None, 293 + defs: { 294 + let mut map = ::std::collections::BTreeMap::new(); 295 + map.insert( 296 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 297 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 298 + description: Some( 299 + ::jacquard_common::CowStr::new_static( 300 + "Acceptance of a collaboration invite. Completes the two-way agreement.", 301 + ), 302 + ), 303 + key: Some(::jacquard_common::CowStr::new_static("tid")), 304 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 305 + description: None, 306 + required: Some( 307 + vec![ 308 + ::jacquard_common::smol_str::SmolStr::new_static("invite"), 309 + ::jacquard_common::smol_str::SmolStr::new_static("resource"), 310 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 311 + ], 312 + ), 313 + nullable: None, 314 + properties: { 315 + #[allow(unused_mut)] 316 + let mut map = ::std::collections::BTreeMap::new(); 317 + map.insert( 318 + ::jacquard_common::smol_str::SmolStr::new_static( 319 + "createdAt", 320 + ), 321 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 322 + description: None, 323 + format: Some( 324 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 325 + ), 326 + default: None, 327 + min_length: None, 328 + max_length: None, 329 + min_graphemes: None, 330 + max_graphemes: None, 331 + r#enum: None, 332 + r#const: None, 333 + known_values: None, 334 + }), 335 + ); 336 + map.insert( 337 + ::jacquard_common::smol_str::SmolStr::new_static("invite"), 338 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 339 + description: None, 340 + r#ref: ::jacquard_common::CowStr::new_static( 341 + "com.atproto.repo.strongRef", 342 + ), 343 + }), 344 + ); 345 + map.insert( 346 + ::jacquard_common::smol_str::SmolStr::new_static( 347 + "resource", 348 + ), 349 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 350 + description: Some( 351 + ::jacquard_common::CowStr::new_static( 352 + "URI of the resource (denormalized for easier querying).", 353 + ), 354 + ), 355 + format: Some( 356 + ::jacquard_lexicon::lexicon::LexStringFormat::AtUri, 357 + ), 358 + default: None, 359 + min_length: None, 360 + max_length: None, 361 + min_graphemes: None, 362 + max_graphemes: None, 363 + r#enum: None, 364 + r#const: None, 365 + known_values: None, 366 + }), 367 + ); 368 + map 369 + }, 370 + }), 371 + }), 372 + ); 373 + map 374 + }, 375 + } 376 + }
+634
crates/weaver-api/src/sh_weaver/collab/invite.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.collab.invite 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// The scope/type of collaboration. 9 + #[derive(Debug, Clone, PartialEq, Eq, Hash)] 10 + pub enum CollabScope<'a> { 11 + ShWeaverCollabDefsNotebook, 12 + ShWeaverCollabDefsEntry, 13 + ShWeaverCollabDefsChapter, 14 + Other(jacquard_common::CowStr<'a>), 15 + } 16 + 17 + impl<'a> CollabScope<'a> { 18 + pub fn as_str(&self) -> &str { 19 + match self { 20 + Self::ShWeaverCollabDefsNotebook => "sh.weaver.collab.defs#notebook", 21 + Self::ShWeaverCollabDefsEntry => "sh.weaver.collab.defs#entry", 22 + Self::ShWeaverCollabDefsChapter => "sh.weaver.collab.defs#chapter", 23 + Self::Other(s) => s.as_ref(), 24 + } 25 + } 26 + } 27 + 28 + impl<'a> From<&'a str> for CollabScope<'a> { 29 + fn from(s: &'a str) -> Self { 30 + match s { 31 + "sh.weaver.collab.defs#notebook" => Self::ShWeaverCollabDefsNotebook, 32 + "sh.weaver.collab.defs#entry" => Self::ShWeaverCollabDefsEntry, 33 + "sh.weaver.collab.defs#chapter" => Self::ShWeaverCollabDefsChapter, 34 + _ => Self::Other(jacquard_common::CowStr::from(s)), 35 + } 36 + } 37 + } 38 + 39 + impl<'a> From<String> for CollabScope<'a> { 40 + fn from(s: String) -> Self { 41 + match s.as_str() { 42 + "sh.weaver.collab.defs#notebook" => Self::ShWeaverCollabDefsNotebook, 43 + "sh.weaver.collab.defs#entry" => Self::ShWeaverCollabDefsEntry, 44 + "sh.weaver.collab.defs#chapter" => Self::ShWeaverCollabDefsChapter, 45 + _ => Self::Other(jacquard_common::CowStr::from(s)), 46 + } 47 + } 48 + } 49 + 50 + impl<'a> AsRef<str> for CollabScope<'a> { 51 + fn as_ref(&self) -> &str { 52 + self.as_str() 53 + } 54 + } 55 + 56 + impl<'a> serde::Serialize for CollabScope<'a> { 57 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 58 + where 59 + S: serde::Serializer, 60 + { 61 + serializer.serialize_str(self.as_str()) 62 + } 63 + } 64 + 65 + impl<'de, 'a> serde::Deserialize<'de> for CollabScope<'a> 66 + where 67 + 'de: 'a, 68 + { 69 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 70 + where 71 + D: serde::Deserializer<'de>, 72 + { 73 + let s = <&'de str>::deserialize(deserializer)?; 74 + Ok(Self::from(s)) 75 + } 76 + } 77 + 78 + impl jacquard_common::IntoStatic for CollabScope<'_> { 79 + type Output = CollabScope<'static>; 80 + fn into_static(self) -> Self::Output { 81 + match self { 82 + CollabScope::ShWeaverCollabDefsNotebook => { 83 + CollabScope::ShWeaverCollabDefsNotebook 84 + } 85 + CollabScope::ShWeaverCollabDefsEntry => CollabScope::ShWeaverCollabDefsEntry, 86 + CollabScope::ShWeaverCollabDefsChapter => { 87 + CollabScope::ShWeaverCollabDefsChapter 88 + } 89 + CollabScope::Other(v) => CollabScope::Other(v.into_static()), 90 + } 91 + } 92 + } 93 + 94 + /// Invitation to collaborate on a resource (notebook, entry, chapter, etc.). Creates half of a two-way agreement. 95 + #[jacquard_derive::lexicon] 96 + #[derive( 97 + serde::Serialize, 98 + serde::Deserialize, 99 + Debug, 100 + Clone, 101 + PartialEq, 102 + Eq, 103 + jacquard_derive::IntoStatic 104 + )] 105 + #[serde(rename_all = "camelCase")] 106 + pub struct Invite<'a> { 107 + pub created_at: jacquard_common::types::string::Datetime, 108 + /// Optional expiration for the invite. 109 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 110 + pub expires_at: std::option::Option<jacquard_common::types::string::Datetime>, 111 + /// DID of the user being invited. 112 + #[serde(borrow)] 113 + pub invitee: jacquard_common::types::string::Did<'a>, 114 + /// Optional message to the invitee. 115 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 116 + #[serde(borrow)] 117 + pub message: std::option::Option<jacquard_common::CowStr<'a>>, 118 + /// The resource to collaborate on (notebook, entry, chapter, etc.). 119 + #[serde(borrow)] 120 + pub resource: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 121 + /// Optional explicit scope type. If omitted, inferred from resource lexicon. 122 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 123 + #[serde(borrow)] 124 + pub scope: std::option::Option<crate::sh_weaver::collab::invite::CollabScope<'a>>, 125 + } 126 + 127 + pub mod invite_state { 128 + 129 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 130 + #[allow(unused)] 131 + use ::core::marker::PhantomData; 132 + mod sealed { 133 + pub trait Sealed {} 134 + } 135 + /// State trait tracking which required fields have been set 136 + pub trait State: sealed::Sealed { 137 + type Resource; 138 + type Invitee; 139 + type CreatedAt; 140 + } 141 + /// Empty state - all required fields are unset 142 + pub struct Empty(()); 143 + impl sealed::Sealed for Empty {} 144 + impl State for Empty { 145 + type Resource = Unset; 146 + type Invitee = Unset; 147 + type CreatedAt = Unset; 148 + } 149 + ///State transition - sets the `resource` field to Set 150 + pub struct SetResource<S: State = Empty>(PhantomData<fn() -> S>); 151 + impl<S: State> sealed::Sealed for SetResource<S> {} 152 + impl<S: State> State for SetResource<S> { 153 + type Resource = Set<members::resource>; 154 + type Invitee = S::Invitee; 155 + type CreatedAt = S::CreatedAt; 156 + } 157 + ///State transition - sets the `invitee` field to Set 158 + pub struct SetInvitee<S: State = Empty>(PhantomData<fn() -> S>); 159 + impl<S: State> sealed::Sealed for SetInvitee<S> {} 160 + impl<S: State> State for SetInvitee<S> { 161 + type Resource = S::Resource; 162 + type Invitee = Set<members::invitee>; 163 + type CreatedAt = S::CreatedAt; 164 + } 165 + ///State transition - sets the `created_at` field to Set 166 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 167 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 168 + impl<S: State> State for SetCreatedAt<S> { 169 + type Resource = S::Resource; 170 + type Invitee = S::Invitee; 171 + type CreatedAt = Set<members::created_at>; 172 + } 173 + /// Marker types for field names 174 + #[allow(non_camel_case_types)] 175 + pub mod members { 176 + ///Marker type for the `resource` field 177 + pub struct resource(()); 178 + ///Marker type for the `invitee` field 179 + pub struct invitee(()); 180 + ///Marker type for the `created_at` field 181 + pub struct created_at(()); 182 + } 183 + } 184 + 185 + /// Builder for constructing an instance of this type 186 + pub struct InviteBuilder<'a, S: invite_state::State> { 187 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 188 + __unsafe_private_named: ( 189 + ::core::option::Option<jacquard_common::types::string::Datetime>, 190 + ::core::option::Option<jacquard_common::types::string::Datetime>, 191 + ::core::option::Option<jacquard_common::types::string::Did<'a>>, 192 + ::core::option::Option<jacquard_common::CowStr<'a>>, 193 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 194 + ::core::option::Option<crate::sh_weaver::collab::invite::CollabScope<'a>>, 195 + ), 196 + _phantom: ::core::marker::PhantomData<&'a ()>, 197 + } 198 + 199 + impl<'a> Invite<'a> { 200 + /// Create a new builder for this type 201 + pub fn new() -> InviteBuilder<'a, invite_state::Empty> { 202 + InviteBuilder::new() 203 + } 204 + } 205 + 206 + impl<'a> InviteBuilder<'a, invite_state::Empty> { 207 + /// Create a new builder with all fields unset 208 + pub fn new() -> Self { 209 + InviteBuilder { 210 + _phantom_state: ::core::marker::PhantomData, 211 + __unsafe_private_named: (None, None, None, None, None, None), 212 + _phantom: ::core::marker::PhantomData, 213 + } 214 + } 215 + } 216 + 217 + impl<'a, S> InviteBuilder<'a, S> 218 + where 219 + S: invite_state::State, 220 + S::CreatedAt: invite_state::IsUnset, 221 + { 222 + /// Set the `createdAt` field (required) 223 + pub fn created_at( 224 + mut self, 225 + value: impl Into<jacquard_common::types::string::Datetime>, 226 + ) -> InviteBuilder<'a, invite_state::SetCreatedAt<S>> { 227 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 228 + InviteBuilder { 229 + _phantom_state: ::core::marker::PhantomData, 230 + __unsafe_private_named: self.__unsafe_private_named, 231 + _phantom: ::core::marker::PhantomData, 232 + } 233 + } 234 + } 235 + 236 + impl<'a, S: invite_state::State> InviteBuilder<'a, S> { 237 + /// Set the `expiresAt` field (optional) 238 + pub fn expires_at( 239 + mut self, 240 + value: impl Into<Option<jacquard_common::types::string::Datetime>>, 241 + ) -> Self { 242 + self.__unsafe_private_named.1 = value.into(); 243 + self 244 + } 245 + /// Set the `expiresAt` field to an Option value (optional) 246 + pub fn maybe_expires_at( 247 + mut self, 248 + value: Option<jacquard_common::types::string::Datetime>, 249 + ) -> Self { 250 + self.__unsafe_private_named.1 = value; 251 + self 252 + } 253 + } 254 + 255 + impl<'a, S> InviteBuilder<'a, S> 256 + where 257 + S: invite_state::State, 258 + S::Invitee: invite_state::IsUnset, 259 + { 260 + /// Set the `invitee` field (required) 261 + pub fn invitee( 262 + mut self, 263 + value: impl Into<jacquard_common::types::string::Did<'a>>, 264 + ) -> InviteBuilder<'a, invite_state::SetInvitee<S>> { 265 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 266 + InviteBuilder { 267 + _phantom_state: ::core::marker::PhantomData, 268 + __unsafe_private_named: self.__unsafe_private_named, 269 + _phantom: ::core::marker::PhantomData, 270 + } 271 + } 272 + } 273 + 274 + impl<'a, S: invite_state::State> InviteBuilder<'a, S> { 275 + /// Set the `message` field (optional) 276 + pub fn message( 277 + mut self, 278 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 279 + ) -> Self { 280 + self.__unsafe_private_named.3 = value.into(); 281 + self 282 + } 283 + /// Set the `message` field to an Option value (optional) 284 + pub fn maybe_message(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 285 + self.__unsafe_private_named.3 = value; 286 + self 287 + } 288 + } 289 + 290 + impl<'a, S> InviteBuilder<'a, S> 291 + where 292 + S: invite_state::State, 293 + S::Resource: invite_state::IsUnset, 294 + { 295 + /// Set the `resource` field (required) 296 + pub fn resource( 297 + mut self, 298 + value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 299 + ) -> InviteBuilder<'a, invite_state::SetResource<S>> { 300 + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 301 + InviteBuilder { 302 + _phantom_state: ::core::marker::PhantomData, 303 + __unsafe_private_named: self.__unsafe_private_named, 304 + _phantom: ::core::marker::PhantomData, 305 + } 306 + } 307 + } 308 + 309 + impl<'a, S: invite_state::State> InviteBuilder<'a, S> { 310 + /// Set the `scope` field (optional) 311 + pub fn scope( 312 + mut self, 313 + value: impl Into<Option<crate::sh_weaver::collab::invite::CollabScope<'a>>>, 314 + ) -> Self { 315 + self.__unsafe_private_named.5 = value.into(); 316 + self 317 + } 318 + /// Set the `scope` field to an Option value (optional) 319 + pub fn maybe_scope( 320 + mut self, 321 + value: Option<crate::sh_weaver::collab::invite::CollabScope<'a>>, 322 + ) -> Self { 323 + self.__unsafe_private_named.5 = value; 324 + self 325 + } 326 + } 327 + 328 + impl<'a, S> InviteBuilder<'a, S> 329 + where 330 + S: invite_state::State, 331 + S::Resource: invite_state::IsSet, 332 + S::Invitee: invite_state::IsSet, 333 + S::CreatedAt: invite_state::IsSet, 334 + { 335 + /// Build the final struct 336 + pub fn build(self) -> Invite<'a> { 337 + Invite { 338 + created_at: self.__unsafe_private_named.0.unwrap(), 339 + expires_at: self.__unsafe_private_named.1, 340 + invitee: self.__unsafe_private_named.2.unwrap(), 341 + message: self.__unsafe_private_named.3, 342 + resource: self.__unsafe_private_named.4.unwrap(), 343 + scope: self.__unsafe_private_named.5, 344 + extra_data: Default::default(), 345 + } 346 + } 347 + /// Build the final struct with custom extra_data 348 + pub fn build_with_data( 349 + self, 350 + extra_data: std::collections::BTreeMap< 351 + jacquard_common::smol_str::SmolStr, 352 + jacquard_common::types::value::Data<'a>, 353 + >, 354 + ) -> Invite<'a> { 355 + Invite { 356 + created_at: self.__unsafe_private_named.0.unwrap(), 357 + expires_at: self.__unsafe_private_named.1, 358 + invitee: self.__unsafe_private_named.2.unwrap(), 359 + message: self.__unsafe_private_named.3, 360 + resource: self.__unsafe_private_named.4.unwrap(), 361 + scope: self.__unsafe_private_named.5, 362 + extra_data: Some(extra_data), 363 + } 364 + } 365 + } 366 + 367 + impl<'a> Invite<'a> { 368 + pub fn uri( 369 + uri: impl Into<jacquard_common::CowStr<'a>>, 370 + ) -> Result< 371 + jacquard_common::types::uri::RecordUri<'a, InviteRecord>, 372 + jacquard_common::types::uri::UriError, 373 + > { 374 + jacquard_common::types::uri::RecordUri::try_from_uri( 375 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 376 + ) 377 + } 378 + } 379 + 380 + /// Typed wrapper for GetRecord response with this collection's record type. 381 + #[derive( 382 + serde::Serialize, 383 + serde::Deserialize, 384 + Debug, 385 + Clone, 386 + PartialEq, 387 + Eq, 388 + jacquard_derive::IntoStatic 389 + )] 390 + #[serde(rename_all = "camelCase")] 391 + pub struct InviteGetRecordOutput<'a> { 392 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 393 + #[serde(borrow)] 394 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 395 + #[serde(borrow)] 396 + pub uri: jacquard_common::types::string::AtUri<'a>, 397 + #[serde(borrow)] 398 + pub value: Invite<'a>, 399 + } 400 + 401 + impl From<InviteGetRecordOutput<'_>> for Invite<'_> { 402 + fn from(output: InviteGetRecordOutput<'_>) -> Self { 403 + use jacquard_common::IntoStatic; 404 + output.value.into_static() 405 + } 406 + } 407 + 408 + impl jacquard_common::types::collection::Collection for Invite<'_> { 409 + const NSID: &'static str = "sh.weaver.collab.invite"; 410 + type Record = InviteRecord; 411 + } 412 + 413 + /// Marker type for deserializing records from this collection. 414 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 415 + pub struct InviteRecord; 416 + impl jacquard_common::xrpc::XrpcResp for InviteRecord { 417 + const NSID: &'static str = "sh.weaver.collab.invite"; 418 + const ENCODING: &'static str = "application/json"; 419 + type Output<'de> = InviteGetRecordOutput<'de>; 420 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 421 + } 422 + 423 + impl jacquard_common::types::collection::Collection for InviteRecord { 424 + const NSID: &'static str = "sh.weaver.collab.invite"; 425 + type Record = InviteRecord; 426 + } 427 + 428 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Invite<'a> { 429 + fn nsid() -> &'static str { 430 + "sh.weaver.collab.invite" 431 + } 432 + fn def_name() -> &'static str { 433 + "main" 434 + } 435 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 436 + lexicon_doc_sh_weaver_collab_invite() 437 + } 438 + fn validate( 439 + &self, 440 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 441 + if let Some(ref value) = self.message { 442 + #[allow(unused_comparisons)] 443 + if <str>::len(value.as_ref()) > 3000usize { 444 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 445 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 446 + "message", 447 + ), 448 + max: 3000usize, 449 + actual: <str>::len(value.as_ref()), 450 + }); 451 + } 452 + } 453 + if let Some(ref value) = self.message { 454 + { 455 + let count = ::unicode_segmentation::UnicodeSegmentation::graphemes( 456 + value.as_ref(), 457 + true, 458 + ) 459 + .count(); 460 + if count > 300usize { 461 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxGraphemes { 462 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 463 + "message", 464 + ), 465 + max: 300usize, 466 + actual: count, 467 + }); 468 + } 469 + } 470 + } 471 + Ok(()) 472 + } 473 + } 474 + 475 + fn lexicon_doc_sh_weaver_collab_invite() -> ::jacquard_lexicon::lexicon::LexiconDoc< 476 + 'static, 477 + > { 478 + ::jacquard_lexicon::lexicon::LexiconDoc { 479 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 480 + id: ::jacquard_common::CowStr::new_static("sh.weaver.collab.invite"), 481 + revision: None, 482 + description: None, 483 + defs: { 484 + let mut map = ::std::collections::BTreeMap::new(); 485 + map.insert( 486 + ::jacquard_common::smol_str::SmolStr::new_static("collabScope"), 487 + ::jacquard_lexicon::lexicon::LexUserType::String(::jacquard_lexicon::lexicon::LexString { 488 + description: Some( 489 + ::jacquard_common::CowStr::new_static( 490 + "The scope/type of collaboration.", 491 + ), 492 + ), 493 + format: None, 494 + default: None, 495 + min_length: None, 496 + max_length: None, 497 + min_graphemes: None, 498 + max_graphemes: None, 499 + r#enum: None, 500 + r#const: None, 501 + known_values: None, 502 + }), 503 + ); 504 + map.insert( 505 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 506 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 507 + description: Some( 508 + ::jacquard_common::CowStr::new_static( 509 + "Invitation to collaborate on a resource (notebook, entry, chapter, etc.). Creates half of a two-way agreement.", 510 + ), 511 + ), 512 + key: Some(::jacquard_common::CowStr::new_static("tid")), 513 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 514 + description: None, 515 + required: Some( 516 + vec![ 517 + ::jacquard_common::smol_str::SmolStr::new_static("resource"), 518 + ::jacquard_common::smol_str::SmolStr::new_static("invitee"), 519 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 520 + ], 521 + ), 522 + nullable: None, 523 + properties: { 524 + #[allow(unused_mut)] 525 + let mut map = ::std::collections::BTreeMap::new(); 526 + map.insert( 527 + ::jacquard_common::smol_str::SmolStr::new_static( 528 + "createdAt", 529 + ), 530 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 531 + description: None, 532 + format: Some( 533 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 534 + ), 535 + default: None, 536 + min_length: None, 537 + max_length: None, 538 + min_graphemes: None, 539 + max_graphemes: None, 540 + r#enum: None, 541 + r#const: None, 542 + known_values: None, 543 + }), 544 + ); 545 + map.insert( 546 + ::jacquard_common::smol_str::SmolStr::new_static( 547 + "expiresAt", 548 + ), 549 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 550 + description: Some( 551 + ::jacquard_common::CowStr::new_static( 552 + "Optional expiration for the invite.", 553 + ), 554 + ), 555 + format: Some( 556 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 557 + ), 558 + default: None, 559 + min_length: None, 560 + max_length: None, 561 + min_graphemes: None, 562 + max_graphemes: None, 563 + r#enum: None, 564 + r#const: None, 565 + known_values: None, 566 + }), 567 + ); 568 + map.insert( 569 + ::jacquard_common::smol_str::SmolStr::new_static("invitee"), 570 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 571 + description: Some( 572 + ::jacquard_common::CowStr::new_static( 573 + "DID of the user being invited.", 574 + ), 575 + ), 576 + format: Some( 577 + ::jacquard_lexicon::lexicon::LexStringFormat::Did, 578 + ), 579 + default: None, 580 + min_length: None, 581 + max_length: None, 582 + min_graphemes: None, 583 + max_graphemes: None, 584 + r#enum: None, 585 + r#const: None, 586 + known_values: None, 587 + }), 588 + ); 589 + map.insert( 590 + ::jacquard_common::smol_str::SmolStr::new_static("message"), 591 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 592 + description: Some( 593 + ::jacquard_common::CowStr::new_static( 594 + "Optional message to the invitee.", 595 + ), 596 + ), 597 + format: None, 598 + default: None, 599 + min_length: None, 600 + max_length: Some(3000usize), 601 + min_graphemes: None, 602 + max_graphemes: Some(300usize), 603 + r#enum: None, 604 + r#const: None, 605 + known_values: None, 606 + }), 607 + ); 608 + map.insert( 609 + ::jacquard_common::smol_str::SmolStr::new_static( 610 + "resource", 611 + ), 612 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 613 + description: None, 614 + r#ref: ::jacquard_common::CowStr::new_static( 615 + "com.atproto.repo.strongRef", 616 + ), 617 + }), 618 + ); 619 + map.insert( 620 + ::jacquard_common::smol_str::SmolStr::new_static("scope"), 621 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 622 + description: None, 623 + r#ref: ::jacquard_common::CowStr::new_static("#collabScope"), 624 + }), 625 + ); 626 + map 627 + }, 628 + }), 629 + }), 630 + ); 631 + map 632 + }, 633 + } 634 + }
+62 -19
crates/weaver-api/src/sh_weaver/edit/diff.rs
··· 18 18 )] 19 19 #[serde(rename_all = "camelCase")] 20 20 pub struct Diff<'a> { 21 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 21 23 #[serde(borrow)] 22 24 pub doc: crate::sh_weaver::edit::DocRef<'a>, 23 25 /// An inline diff for for small edit batches. Either this or snapshot must be present to be valid ··· 82 84 pub struct DiffBuilder<'a, S: diff_state::State> { 83 85 _phantom_state: ::core::marker::PhantomData<fn() -> S>, 84 86 __unsafe_private_named: ( 87 + ::core::option::Option<jacquard_common::types::string::Datetime>, 85 88 ::core::option::Option<crate::sh_weaver::edit::DocRef<'a>>, 86 89 ::core::option::Option<bytes::Bytes>, 87 90 ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, ··· 103 106 pub fn new() -> Self { 104 107 DiffBuilder { 105 108 _phantom_state: ::core::marker::PhantomData, 106 - __unsafe_private_named: (None, None, None, None, None), 109 + __unsafe_private_named: (None, None, None, None, None, None), 107 110 _phantom: ::core::marker::PhantomData, 108 111 } 109 112 } 110 113 } 111 114 115 + impl<'a, S: diff_state::State> DiffBuilder<'a, S> { 116 + /// Set the `createdAt` field (optional) 117 + pub fn created_at( 118 + mut self, 119 + value: impl Into<Option<jacquard_common::types::string::Datetime>>, 120 + ) -> Self { 121 + self.__unsafe_private_named.0 = value.into(); 122 + self 123 + } 124 + /// Set the `createdAt` field to an Option value (optional) 125 + pub fn maybe_created_at( 126 + mut self, 127 + value: Option<jacquard_common::types::string::Datetime>, 128 + ) -> Self { 129 + self.__unsafe_private_named.0 = value; 130 + self 131 + } 132 + } 133 + 112 134 impl<'a, S> DiffBuilder<'a, S> 113 135 where 114 136 S: diff_state::State, ··· 119 141 mut self, 120 142 value: impl Into<crate::sh_weaver::edit::DocRef<'a>>, 121 143 ) -> DiffBuilder<'a, diff_state::SetDoc<S>> { 122 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 144 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 123 145 DiffBuilder { 124 146 _phantom_state: ::core::marker::PhantomData, 125 147 __unsafe_private_named: self.__unsafe_private_named, ··· 131 153 impl<'a, S: diff_state::State> DiffBuilder<'a, S> { 132 154 /// Set the `inlineDiff` field (optional) 133 155 pub fn inline_diff(mut self, value: impl Into<Option<bytes::Bytes>>) -> Self { 134 - self.__unsafe_private_named.1 = value.into(); 156 + self.__unsafe_private_named.2 = value.into(); 135 157 self 136 158 } 137 159 /// Set the `inlineDiff` field to an Option value (optional) 138 160 pub fn maybe_inline_diff(mut self, value: Option<bytes::Bytes>) -> Self { 139 - self.__unsafe_private_named.1 = value; 161 + self.__unsafe_private_named.2 = value; 140 162 self 141 163 } 142 164 } ··· 147 169 mut self, 148 170 value: impl Into<Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>>, 149 171 ) -> Self { 150 - self.__unsafe_private_named.2 = value.into(); 172 + self.__unsafe_private_named.3 = value.into(); 151 173 self 152 174 } 153 175 /// Set the `prev` field to an Option value (optional) ··· 155 177 mut self, 156 178 value: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 157 179 ) -> Self { 158 - self.__unsafe_private_named.2 = value; 180 + self.__unsafe_private_named.3 = value; 159 181 self 160 182 } 161 183 } ··· 170 192 mut self, 171 193 value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 172 194 ) -> DiffBuilder<'a, diff_state::SetRoot<S>> { 173 - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 195 + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 174 196 DiffBuilder { 175 197 _phantom_state: ::core::marker::PhantomData, 176 198 __unsafe_private_named: self.__unsafe_private_named, ··· 185 207 mut self, 186 208 value: impl Into<Option<jacquard_common::types::blob::BlobRef<'a>>>, 187 209 ) -> Self { 188 - self.__unsafe_private_named.4 = value.into(); 210 + self.__unsafe_private_named.5 = value.into(); 189 211 self 190 212 } 191 213 /// Set the `snapshot` field to an Option value (optional) ··· 193 215 mut self, 194 216 value: Option<jacquard_common::types::blob::BlobRef<'a>>, 195 217 ) -> Self { 196 - self.__unsafe_private_named.4 = value; 218 + self.__unsafe_private_named.5 = value; 197 219 self 198 220 } 199 221 } ··· 207 229 /// Build the final struct 208 230 pub fn build(self) -> Diff<'a> { 209 231 Diff { 210 - doc: self.__unsafe_private_named.0.unwrap(), 211 - inline_diff: self.__unsafe_private_named.1, 212 - prev: self.__unsafe_private_named.2, 213 - root: self.__unsafe_private_named.3.unwrap(), 214 - snapshot: self.__unsafe_private_named.4, 232 + created_at: self.__unsafe_private_named.0, 233 + doc: self.__unsafe_private_named.1.unwrap(), 234 + inline_diff: self.__unsafe_private_named.2, 235 + prev: self.__unsafe_private_named.3, 236 + root: self.__unsafe_private_named.4.unwrap(), 237 + snapshot: self.__unsafe_private_named.5, 215 238 extra_data: Default::default(), 216 239 } 217 240 } ··· 224 247 >, 225 248 ) -> Diff<'a> { 226 249 Diff { 227 - doc: self.__unsafe_private_named.0.unwrap(), 228 - inline_diff: self.__unsafe_private_named.1, 229 - prev: self.__unsafe_private_named.2, 230 - root: self.__unsafe_private_named.3.unwrap(), 231 - snapshot: self.__unsafe_private_named.4, 250 + created_at: self.__unsafe_private_named.0, 251 + doc: self.__unsafe_private_named.1.unwrap(), 252 + inline_diff: self.__unsafe_private_named.2, 253 + prev: self.__unsafe_private_named.3, 254 + root: self.__unsafe_private_named.4.unwrap(), 255 + snapshot: self.__unsafe_private_named.5, 232 256 extra_data: Some(extra_data), 233 257 } 234 258 } ··· 343 367 properties: { 344 368 #[allow(unused_mut)] 345 369 let mut map = ::std::collections::BTreeMap::new(); 370 + map.insert( 371 + ::jacquard_common::smol_str::SmolStr::new_static( 372 + "createdAt", 373 + ), 374 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 375 + description: None, 376 + format: Some( 377 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 378 + ), 379 + default: None, 380 + min_length: None, 381 + max_length: None, 382 + min_graphemes: None, 383 + max_graphemes: None, 384 + r#enum: None, 385 + r#const: None, 386 + known_values: None, 387 + }), 388 + ); 346 389 map.insert( 347 390 ::jacquard_common::smol_str::SmolStr::new_static("doc"), 348 391 ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
+52
crates/weaver-api/src/sh_weaver/graph.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.defs 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + pub mod bookmark; 9 + pub mod follow; 10 + pub mod follow_accept; 11 + pub mod follow_gate; 12 + pub mod like; 13 + pub mod list; 14 + pub mod listitem; 15 + pub mod subscribe; 16 + pub mod subscribe_accept; 17 + 18 + /// A curated collection of notebooks/entries for sharing. 19 + #[derive( 20 + serde::Serialize, 21 + serde::Deserialize, 22 + Debug, 23 + Clone, 24 + PartialEq, 25 + Eq, 26 + Hash, 27 + jacquard_derive::IntoStatic 28 + )] 29 + pub struct Curatelist; 30 + impl std::fmt::Display for Curatelist { 31 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 + write!(f, "curatelist") 33 + } 34 + } 35 + 36 + /// A personal reading list. 37 + #[derive( 38 + serde::Serialize, 39 + serde::Deserialize, 40 + Debug, 41 + Clone, 42 + PartialEq, 43 + Eq, 44 + Hash, 45 + jacquard_derive::IntoStatic 46 + )] 47 + pub struct Readinglist; 48 + impl std::fmt::Display for Readinglist { 49 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 + write!(f, "readinglist") 51 + } 52 + }
+384
crates/weaver-api/src/sh_weaver/graph/bookmark.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.bookmark 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// Bookmark a notebook or entry for later reading. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct Bookmark<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// Optional private note about why you saved this. 23 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 + #[serde(borrow)] 25 + pub note: std::option::Option<jacquard_common::CowStr<'a>>, 26 + /// The notebook or entry being bookmarked. 27 + #[serde(borrow)] 28 + pub subject: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 29 + } 30 + 31 + pub mod bookmark_state { 32 + 33 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 34 + #[allow(unused)] 35 + use ::core::marker::PhantomData; 36 + mod sealed { 37 + pub trait Sealed {} 38 + } 39 + /// State trait tracking which required fields have been set 40 + pub trait State: sealed::Sealed { 41 + type Subject; 42 + type CreatedAt; 43 + } 44 + /// Empty state - all required fields are unset 45 + pub struct Empty(()); 46 + impl sealed::Sealed for Empty {} 47 + impl State for Empty { 48 + type Subject = Unset; 49 + type CreatedAt = Unset; 50 + } 51 + ///State transition - sets the `subject` field to Set 52 + pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>); 53 + impl<S: State> sealed::Sealed for SetSubject<S> {} 54 + impl<S: State> State for SetSubject<S> { 55 + type Subject = Set<members::subject>; 56 + type CreatedAt = S::CreatedAt; 57 + } 58 + ///State transition - sets the `created_at` field to Set 59 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 60 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 61 + impl<S: State> State for SetCreatedAt<S> { 62 + type Subject = S::Subject; 63 + type CreatedAt = Set<members::created_at>; 64 + } 65 + /// Marker types for field names 66 + #[allow(non_camel_case_types)] 67 + pub mod members { 68 + ///Marker type for the `subject` field 69 + pub struct subject(()); 70 + ///Marker type for the `created_at` field 71 + pub struct created_at(()); 72 + } 73 + } 74 + 75 + /// Builder for constructing an instance of this type 76 + pub struct BookmarkBuilder<'a, S: bookmark_state::State> { 77 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 78 + __unsafe_private_named: ( 79 + ::core::option::Option<jacquard_common::types::string::Datetime>, 80 + ::core::option::Option<jacquard_common::CowStr<'a>>, 81 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 82 + ), 83 + _phantom: ::core::marker::PhantomData<&'a ()>, 84 + } 85 + 86 + impl<'a> Bookmark<'a> { 87 + /// Create a new builder for this type 88 + pub fn new() -> BookmarkBuilder<'a, bookmark_state::Empty> { 89 + BookmarkBuilder::new() 90 + } 91 + } 92 + 93 + impl<'a> BookmarkBuilder<'a, bookmark_state::Empty> { 94 + /// Create a new builder with all fields unset 95 + pub fn new() -> Self { 96 + BookmarkBuilder { 97 + _phantom_state: ::core::marker::PhantomData, 98 + __unsafe_private_named: (None, None, None), 99 + _phantom: ::core::marker::PhantomData, 100 + } 101 + } 102 + } 103 + 104 + impl<'a, S> BookmarkBuilder<'a, S> 105 + where 106 + S: bookmark_state::State, 107 + S::CreatedAt: bookmark_state::IsUnset, 108 + { 109 + /// Set the `createdAt` field (required) 110 + pub fn created_at( 111 + mut self, 112 + value: impl Into<jacquard_common::types::string::Datetime>, 113 + ) -> BookmarkBuilder<'a, bookmark_state::SetCreatedAt<S>> { 114 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 115 + BookmarkBuilder { 116 + _phantom_state: ::core::marker::PhantomData, 117 + __unsafe_private_named: self.__unsafe_private_named, 118 + _phantom: ::core::marker::PhantomData, 119 + } 120 + } 121 + } 122 + 123 + impl<'a, S: bookmark_state::State> BookmarkBuilder<'a, S> { 124 + /// Set the `note` field (optional) 125 + pub fn note( 126 + mut self, 127 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 128 + ) -> Self { 129 + self.__unsafe_private_named.1 = value.into(); 130 + self 131 + } 132 + /// Set the `note` field to an Option value (optional) 133 + pub fn maybe_note(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 134 + self.__unsafe_private_named.1 = value; 135 + self 136 + } 137 + } 138 + 139 + impl<'a, S> BookmarkBuilder<'a, S> 140 + where 141 + S: bookmark_state::State, 142 + S::Subject: bookmark_state::IsUnset, 143 + { 144 + /// Set the `subject` field (required) 145 + pub fn subject( 146 + mut self, 147 + value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 148 + ) -> BookmarkBuilder<'a, bookmark_state::SetSubject<S>> { 149 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 150 + BookmarkBuilder { 151 + _phantom_state: ::core::marker::PhantomData, 152 + __unsafe_private_named: self.__unsafe_private_named, 153 + _phantom: ::core::marker::PhantomData, 154 + } 155 + } 156 + } 157 + 158 + impl<'a, S> BookmarkBuilder<'a, S> 159 + where 160 + S: bookmark_state::State, 161 + S::Subject: bookmark_state::IsSet, 162 + S::CreatedAt: bookmark_state::IsSet, 163 + { 164 + /// Build the final struct 165 + pub fn build(self) -> Bookmark<'a> { 166 + Bookmark { 167 + created_at: self.__unsafe_private_named.0.unwrap(), 168 + note: self.__unsafe_private_named.1, 169 + subject: self.__unsafe_private_named.2.unwrap(), 170 + extra_data: Default::default(), 171 + } 172 + } 173 + /// Build the final struct with custom extra_data 174 + pub fn build_with_data( 175 + self, 176 + extra_data: std::collections::BTreeMap< 177 + jacquard_common::smol_str::SmolStr, 178 + jacquard_common::types::value::Data<'a>, 179 + >, 180 + ) -> Bookmark<'a> { 181 + Bookmark { 182 + created_at: self.__unsafe_private_named.0.unwrap(), 183 + note: self.__unsafe_private_named.1, 184 + subject: self.__unsafe_private_named.2.unwrap(), 185 + extra_data: Some(extra_data), 186 + } 187 + } 188 + } 189 + 190 + impl<'a> Bookmark<'a> { 191 + pub fn uri( 192 + uri: impl Into<jacquard_common::CowStr<'a>>, 193 + ) -> Result< 194 + jacquard_common::types::uri::RecordUri<'a, BookmarkRecord>, 195 + jacquard_common::types::uri::UriError, 196 + > { 197 + jacquard_common::types::uri::RecordUri::try_from_uri( 198 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 199 + ) 200 + } 201 + } 202 + 203 + /// Typed wrapper for GetRecord response with this collection's record type. 204 + #[derive( 205 + serde::Serialize, 206 + serde::Deserialize, 207 + Debug, 208 + Clone, 209 + PartialEq, 210 + Eq, 211 + jacquard_derive::IntoStatic 212 + )] 213 + #[serde(rename_all = "camelCase")] 214 + pub struct BookmarkGetRecordOutput<'a> { 215 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 216 + #[serde(borrow)] 217 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 218 + #[serde(borrow)] 219 + pub uri: jacquard_common::types::string::AtUri<'a>, 220 + #[serde(borrow)] 221 + pub value: Bookmark<'a>, 222 + } 223 + 224 + impl From<BookmarkGetRecordOutput<'_>> for Bookmark<'_> { 225 + fn from(output: BookmarkGetRecordOutput<'_>) -> Self { 226 + use jacquard_common::IntoStatic; 227 + output.value.into_static() 228 + } 229 + } 230 + 231 + impl jacquard_common::types::collection::Collection for Bookmark<'_> { 232 + const NSID: &'static str = "sh.weaver.graph.bookmark"; 233 + type Record = BookmarkRecord; 234 + } 235 + 236 + /// Marker type for deserializing records from this collection. 237 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 238 + pub struct BookmarkRecord; 239 + impl jacquard_common::xrpc::XrpcResp for BookmarkRecord { 240 + const NSID: &'static str = "sh.weaver.graph.bookmark"; 241 + const ENCODING: &'static str = "application/json"; 242 + type Output<'de> = BookmarkGetRecordOutput<'de>; 243 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 244 + } 245 + 246 + impl jacquard_common::types::collection::Collection for BookmarkRecord { 247 + const NSID: &'static str = "sh.weaver.graph.bookmark"; 248 + type Record = BookmarkRecord; 249 + } 250 + 251 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Bookmark<'a> { 252 + fn nsid() -> &'static str { 253 + "sh.weaver.graph.bookmark" 254 + } 255 + fn def_name() -> &'static str { 256 + "main" 257 + } 258 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 259 + lexicon_doc_sh_weaver_graph_bookmark() 260 + } 261 + fn validate( 262 + &self, 263 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 264 + if let Some(ref value) = self.note { 265 + #[allow(unused_comparisons)] 266 + if <str>::len(value.as_ref()) > 3000usize { 267 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 268 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 269 + "note", 270 + ), 271 + max: 3000usize, 272 + actual: <str>::len(value.as_ref()), 273 + }); 274 + } 275 + } 276 + if let Some(ref value) = self.note { 277 + { 278 + let count = ::unicode_segmentation::UnicodeSegmentation::graphemes( 279 + value.as_ref(), 280 + true, 281 + ) 282 + .count(); 283 + if count > 300usize { 284 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxGraphemes { 285 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 286 + "note", 287 + ), 288 + max: 300usize, 289 + actual: count, 290 + }); 291 + } 292 + } 293 + } 294 + Ok(()) 295 + } 296 + } 297 + 298 + fn lexicon_doc_sh_weaver_graph_bookmark() -> ::jacquard_lexicon::lexicon::LexiconDoc< 299 + 'static, 300 + > { 301 + ::jacquard_lexicon::lexicon::LexiconDoc { 302 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 303 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.bookmark"), 304 + revision: None, 305 + description: None, 306 + defs: { 307 + let mut map = ::std::collections::BTreeMap::new(); 308 + map.insert( 309 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 310 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 311 + description: Some( 312 + ::jacquard_common::CowStr::new_static( 313 + "Bookmark a notebook or entry for later reading.", 314 + ), 315 + ), 316 + key: Some(::jacquard_common::CowStr::new_static("tid")), 317 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 318 + description: None, 319 + required: Some( 320 + vec![ 321 + ::jacquard_common::smol_str::SmolStr::new_static("subject"), 322 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 323 + ], 324 + ), 325 + nullable: None, 326 + properties: { 327 + #[allow(unused_mut)] 328 + let mut map = ::std::collections::BTreeMap::new(); 329 + map.insert( 330 + ::jacquard_common::smol_str::SmolStr::new_static( 331 + "createdAt", 332 + ), 333 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 334 + description: None, 335 + format: Some( 336 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 337 + ), 338 + default: None, 339 + min_length: None, 340 + max_length: None, 341 + min_graphemes: None, 342 + max_graphemes: None, 343 + r#enum: None, 344 + r#const: None, 345 + known_values: None, 346 + }), 347 + ); 348 + map.insert( 349 + ::jacquard_common::smol_str::SmolStr::new_static("note"), 350 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 351 + description: Some( 352 + ::jacquard_common::CowStr::new_static( 353 + "Optional private note about why you saved this.", 354 + ), 355 + ), 356 + format: None, 357 + default: None, 358 + min_length: None, 359 + max_length: Some(3000usize), 360 + min_graphemes: None, 361 + max_graphemes: Some(300usize), 362 + r#enum: None, 363 + r#const: None, 364 + known_values: None, 365 + }), 366 + ); 367 + map.insert( 368 + ::jacquard_common::smol_str::SmolStr::new_static("subject"), 369 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 370 + description: None, 371 + r#ref: ::jacquard_common::CowStr::new_static( 372 + "com.atproto.repo.strongRef", 373 + ), 374 + }), 375 + ); 376 + map 377 + }, 378 + }), 379 + }), 380 + ); 381 + map 382 + }, 383 + } 384 + }
+324
crates/weaver-api/src/sh_weaver/graph/follow.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.follow 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// Request to follow an author. Requires acceptance to be active. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct Follow<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// DID of the author to follow. 23 + #[serde(borrow)] 24 + pub subject: jacquard_common::types::string::Did<'a>, 25 + } 26 + 27 + pub mod follow_state { 28 + 29 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 30 + #[allow(unused)] 31 + use ::core::marker::PhantomData; 32 + mod sealed { 33 + pub trait Sealed {} 34 + } 35 + /// State trait tracking which required fields have been set 36 + pub trait State: sealed::Sealed { 37 + type Subject; 38 + type CreatedAt; 39 + } 40 + /// Empty state - all required fields are unset 41 + pub struct Empty(()); 42 + impl sealed::Sealed for Empty {} 43 + impl State for Empty { 44 + type Subject = Unset; 45 + type CreatedAt = Unset; 46 + } 47 + ///State transition - sets the `subject` field to Set 48 + pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>); 49 + impl<S: State> sealed::Sealed for SetSubject<S> {} 50 + impl<S: State> State for SetSubject<S> { 51 + type Subject = Set<members::subject>; 52 + type CreatedAt = S::CreatedAt; 53 + } 54 + ///State transition - sets the `created_at` field to Set 55 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 56 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 57 + impl<S: State> State for SetCreatedAt<S> { 58 + type Subject = S::Subject; 59 + type CreatedAt = Set<members::created_at>; 60 + } 61 + /// Marker types for field names 62 + #[allow(non_camel_case_types)] 63 + pub mod members { 64 + ///Marker type for the `subject` field 65 + pub struct subject(()); 66 + ///Marker type for the `created_at` field 67 + pub struct created_at(()); 68 + } 69 + } 70 + 71 + /// Builder for constructing an instance of this type 72 + pub struct FollowBuilder<'a, S: follow_state::State> { 73 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 74 + __unsafe_private_named: ( 75 + ::core::option::Option<jacquard_common::types::string::Datetime>, 76 + ::core::option::Option<jacquard_common::types::string::Did<'a>>, 77 + ), 78 + _phantom: ::core::marker::PhantomData<&'a ()>, 79 + } 80 + 81 + impl<'a> Follow<'a> { 82 + /// Create a new builder for this type 83 + pub fn new() -> FollowBuilder<'a, follow_state::Empty> { 84 + FollowBuilder::new() 85 + } 86 + } 87 + 88 + impl<'a> FollowBuilder<'a, follow_state::Empty> { 89 + /// Create a new builder with all fields unset 90 + pub fn new() -> Self { 91 + FollowBuilder { 92 + _phantom_state: ::core::marker::PhantomData, 93 + __unsafe_private_named: (None, None), 94 + _phantom: ::core::marker::PhantomData, 95 + } 96 + } 97 + } 98 + 99 + impl<'a, S> FollowBuilder<'a, S> 100 + where 101 + S: follow_state::State, 102 + S::CreatedAt: follow_state::IsUnset, 103 + { 104 + /// Set the `createdAt` field (required) 105 + pub fn created_at( 106 + mut self, 107 + value: impl Into<jacquard_common::types::string::Datetime>, 108 + ) -> FollowBuilder<'a, follow_state::SetCreatedAt<S>> { 109 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 110 + FollowBuilder { 111 + _phantom_state: ::core::marker::PhantomData, 112 + __unsafe_private_named: self.__unsafe_private_named, 113 + _phantom: ::core::marker::PhantomData, 114 + } 115 + } 116 + } 117 + 118 + impl<'a, S> FollowBuilder<'a, S> 119 + where 120 + S: follow_state::State, 121 + S::Subject: follow_state::IsUnset, 122 + { 123 + /// Set the `subject` field (required) 124 + pub fn subject( 125 + mut self, 126 + value: impl Into<jacquard_common::types::string::Did<'a>>, 127 + ) -> FollowBuilder<'a, follow_state::SetSubject<S>> { 128 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 129 + FollowBuilder { 130 + _phantom_state: ::core::marker::PhantomData, 131 + __unsafe_private_named: self.__unsafe_private_named, 132 + _phantom: ::core::marker::PhantomData, 133 + } 134 + } 135 + } 136 + 137 + impl<'a, S> FollowBuilder<'a, S> 138 + where 139 + S: follow_state::State, 140 + S::Subject: follow_state::IsSet, 141 + S::CreatedAt: follow_state::IsSet, 142 + { 143 + /// Build the final struct 144 + pub fn build(self) -> Follow<'a> { 145 + Follow { 146 + created_at: self.__unsafe_private_named.0.unwrap(), 147 + subject: self.__unsafe_private_named.1.unwrap(), 148 + extra_data: Default::default(), 149 + } 150 + } 151 + /// Build the final struct with custom extra_data 152 + pub fn build_with_data( 153 + self, 154 + extra_data: std::collections::BTreeMap< 155 + jacquard_common::smol_str::SmolStr, 156 + jacquard_common::types::value::Data<'a>, 157 + >, 158 + ) -> Follow<'a> { 159 + Follow { 160 + created_at: self.__unsafe_private_named.0.unwrap(), 161 + subject: self.__unsafe_private_named.1.unwrap(), 162 + extra_data: Some(extra_data), 163 + } 164 + } 165 + } 166 + 167 + impl<'a> Follow<'a> { 168 + pub fn uri( 169 + uri: impl Into<jacquard_common::CowStr<'a>>, 170 + ) -> Result< 171 + jacquard_common::types::uri::RecordUri<'a, FollowRecord>, 172 + jacquard_common::types::uri::UriError, 173 + > { 174 + jacquard_common::types::uri::RecordUri::try_from_uri( 175 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 176 + ) 177 + } 178 + } 179 + 180 + /// Typed wrapper for GetRecord response with this collection's record type. 181 + #[derive( 182 + serde::Serialize, 183 + serde::Deserialize, 184 + Debug, 185 + Clone, 186 + PartialEq, 187 + Eq, 188 + jacquard_derive::IntoStatic 189 + )] 190 + #[serde(rename_all = "camelCase")] 191 + pub struct FollowGetRecordOutput<'a> { 192 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 193 + #[serde(borrow)] 194 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 195 + #[serde(borrow)] 196 + pub uri: jacquard_common::types::string::AtUri<'a>, 197 + #[serde(borrow)] 198 + pub value: Follow<'a>, 199 + } 200 + 201 + impl From<FollowGetRecordOutput<'_>> for Follow<'_> { 202 + fn from(output: FollowGetRecordOutput<'_>) -> Self { 203 + use jacquard_common::IntoStatic; 204 + output.value.into_static() 205 + } 206 + } 207 + 208 + impl jacquard_common::types::collection::Collection for Follow<'_> { 209 + const NSID: &'static str = "sh.weaver.graph.follow"; 210 + type Record = FollowRecord; 211 + } 212 + 213 + /// Marker type for deserializing records from this collection. 214 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 215 + pub struct FollowRecord; 216 + impl jacquard_common::xrpc::XrpcResp for FollowRecord { 217 + const NSID: &'static str = "sh.weaver.graph.follow"; 218 + const ENCODING: &'static str = "application/json"; 219 + type Output<'de> = FollowGetRecordOutput<'de>; 220 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 221 + } 222 + 223 + impl jacquard_common::types::collection::Collection for FollowRecord { 224 + const NSID: &'static str = "sh.weaver.graph.follow"; 225 + type Record = FollowRecord; 226 + } 227 + 228 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Follow<'a> { 229 + fn nsid() -> &'static str { 230 + "sh.weaver.graph.follow" 231 + } 232 + fn def_name() -> &'static str { 233 + "main" 234 + } 235 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 236 + lexicon_doc_sh_weaver_graph_follow() 237 + } 238 + fn validate( 239 + &self, 240 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 241 + Ok(()) 242 + } 243 + } 244 + 245 + fn lexicon_doc_sh_weaver_graph_follow() -> ::jacquard_lexicon::lexicon::LexiconDoc< 246 + 'static, 247 + > { 248 + ::jacquard_lexicon::lexicon::LexiconDoc { 249 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 250 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.follow"), 251 + revision: None, 252 + description: None, 253 + defs: { 254 + let mut map = ::std::collections::BTreeMap::new(); 255 + map.insert( 256 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 257 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 258 + description: Some( 259 + ::jacquard_common::CowStr::new_static( 260 + "Request to follow an author. Requires acceptance to be active.", 261 + ), 262 + ), 263 + key: Some(::jacquard_common::CowStr::new_static("tid")), 264 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 265 + description: None, 266 + required: Some( 267 + vec![ 268 + ::jacquard_common::smol_str::SmolStr::new_static("subject"), 269 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 270 + ], 271 + ), 272 + nullable: None, 273 + properties: { 274 + #[allow(unused_mut)] 275 + let mut map = ::std::collections::BTreeMap::new(); 276 + map.insert( 277 + ::jacquard_common::smol_str::SmolStr::new_static( 278 + "createdAt", 279 + ), 280 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 281 + description: None, 282 + format: Some( 283 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 284 + ), 285 + default: None, 286 + min_length: None, 287 + max_length: None, 288 + min_graphemes: None, 289 + max_graphemes: None, 290 + r#enum: None, 291 + r#const: None, 292 + known_values: None, 293 + }), 294 + ); 295 + map.insert( 296 + ::jacquard_common::smol_str::SmolStr::new_static("subject"), 297 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 298 + description: Some( 299 + ::jacquard_common::CowStr::new_static( 300 + "DID of the author to follow.", 301 + ), 302 + ), 303 + format: Some( 304 + ::jacquard_lexicon::lexicon::LexStringFormat::Did, 305 + ), 306 + default: None, 307 + min_length: None, 308 + max_length: None, 309 + min_graphemes: None, 310 + max_graphemes: None, 311 + r#enum: None, 312 + r#const: None, 313 + known_values: None, 314 + }), 315 + ); 316 + map 317 + }, 318 + }), 319 + }), 320 + ); 321 + map 322 + }, 323 + } 324 + }
+312
crates/weaver-api/src/sh_weaver/graph/follow_accept.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.followAccept 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// Acceptance of a follow request. Completes the two-way agreement. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct FollowAccept<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// Reference to the follow record being accepted. 23 + #[serde(borrow)] 24 + pub follow: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 25 + } 26 + 27 + pub mod follow_accept_state { 28 + 29 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 30 + #[allow(unused)] 31 + use ::core::marker::PhantomData; 32 + mod sealed { 33 + pub trait Sealed {} 34 + } 35 + /// State trait tracking which required fields have been set 36 + pub trait State: sealed::Sealed { 37 + type Follow; 38 + type CreatedAt; 39 + } 40 + /// Empty state - all required fields are unset 41 + pub struct Empty(()); 42 + impl sealed::Sealed for Empty {} 43 + impl State for Empty { 44 + type Follow = Unset; 45 + type CreatedAt = Unset; 46 + } 47 + ///State transition - sets the `follow` field to Set 48 + pub struct SetFollow<S: State = Empty>(PhantomData<fn() -> S>); 49 + impl<S: State> sealed::Sealed for SetFollow<S> {} 50 + impl<S: State> State for SetFollow<S> { 51 + type Follow = Set<members::follow>; 52 + type CreatedAt = S::CreatedAt; 53 + } 54 + ///State transition - sets the `created_at` field to Set 55 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 56 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 57 + impl<S: State> State for SetCreatedAt<S> { 58 + type Follow = S::Follow; 59 + type CreatedAt = Set<members::created_at>; 60 + } 61 + /// Marker types for field names 62 + #[allow(non_camel_case_types)] 63 + pub mod members { 64 + ///Marker type for the `follow` field 65 + pub struct follow(()); 66 + ///Marker type for the `created_at` field 67 + pub struct created_at(()); 68 + } 69 + } 70 + 71 + /// Builder for constructing an instance of this type 72 + pub struct FollowAcceptBuilder<'a, S: follow_accept_state::State> { 73 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 74 + __unsafe_private_named: ( 75 + ::core::option::Option<jacquard_common::types::string::Datetime>, 76 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 77 + ), 78 + _phantom: ::core::marker::PhantomData<&'a ()>, 79 + } 80 + 81 + impl<'a> FollowAccept<'a> { 82 + /// Create a new builder for this type 83 + pub fn new() -> FollowAcceptBuilder<'a, follow_accept_state::Empty> { 84 + FollowAcceptBuilder::new() 85 + } 86 + } 87 + 88 + impl<'a> FollowAcceptBuilder<'a, follow_accept_state::Empty> { 89 + /// Create a new builder with all fields unset 90 + pub fn new() -> Self { 91 + FollowAcceptBuilder { 92 + _phantom_state: ::core::marker::PhantomData, 93 + __unsafe_private_named: (None, None), 94 + _phantom: ::core::marker::PhantomData, 95 + } 96 + } 97 + } 98 + 99 + impl<'a, S> FollowAcceptBuilder<'a, S> 100 + where 101 + S: follow_accept_state::State, 102 + S::CreatedAt: follow_accept_state::IsUnset, 103 + { 104 + /// Set the `createdAt` field (required) 105 + pub fn created_at( 106 + mut self, 107 + value: impl Into<jacquard_common::types::string::Datetime>, 108 + ) -> FollowAcceptBuilder<'a, follow_accept_state::SetCreatedAt<S>> { 109 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 110 + FollowAcceptBuilder { 111 + _phantom_state: ::core::marker::PhantomData, 112 + __unsafe_private_named: self.__unsafe_private_named, 113 + _phantom: ::core::marker::PhantomData, 114 + } 115 + } 116 + } 117 + 118 + impl<'a, S> FollowAcceptBuilder<'a, S> 119 + where 120 + S: follow_accept_state::State, 121 + S::Follow: follow_accept_state::IsUnset, 122 + { 123 + /// Set the `follow` field (required) 124 + pub fn follow( 125 + mut self, 126 + value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 127 + ) -> FollowAcceptBuilder<'a, follow_accept_state::SetFollow<S>> { 128 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 129 + FollowAcceptBuilder { 130 + _phantom_state: ::core::marker::PhantomData, 131 + __unsafe_private_named: self.__unsafe_private_named, 132 + _phantom: ::core::marker::PhantomData, 133 + } 134 + } 135 + } 136 + 137 + impl<'a, S> FollowAcceptBuilder<'a, S> 138 + where 139 + S: follow_accept_state::State, 140 + S::Follow: follow_accept_state::IsSet, 141 + S::CreatedAt: follow_accept_state::IsSet, 142 + { 143 + /// Build the final struct 144 + pub fn build(self) -> FollowAccept<'a> { 145 + FollowAccept { 146 + created_at: self.__unsafe_private_named.0.unwrap(), 147 + follow: self.__unsafe_private_named.1.unwrap(), 148 + extra_data: Default::default(), 149 + } 150 + } 151 + /// Build the final struct with custom extra_data 152 + pub fn build_with_data( 153 + self, 154 + extra_data: std::collections::BTreeMap< 155 + jacquard_common::smol_str::SmolStr, 156 + jacquard_common::types::value::Data<'a>, 157 + >, 158 + ) -> FollowAccept<'a> { 159 + FollowAccept { 160 + created_at: self.__unsafe_private_named.0.unwrap(), 161 + follow: self.__unsafe_private_named.1.unwrap(), 162 + extra_data: Some(extra_data), 163 + } 164 + } 165 + } 166 + 167 + impl<'a> FollowAccept<'a> { 168 + pub fn uri( 169 + uri: impl Into<jacquard_common::CowStr<'a>>, 170 + ) -> Result< 171 + jacquard_common::types::uri::RecordUri<'a, FollowAcceptRecord>, 172 + jacquard_common::types::uri::UriError, 173 + > { 174 + jacquard_common::types::uri::RecordUri::try_from_uri( 175 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 176 + ) 177 + } 178 + } 179 + 180 + /// Typed wrapper for GetRecord response with this collection's record type. 181 + #[derive( 182 + serde::Serialize, 183 + serde::Deserialize, 184 + Debug, 185 + Clone, 186 + PartialEq, 187 + Eq, 188 + jacquard_derive::IntoStatic 189 + )] 190 + #[serde(rename_all = "camelCase")] 191 + pub struct FollowAcceptGetRecordOutput<'a> { 192 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 193 + #[serde(borrow)] 194 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 195 + #[serde(borrow)] 196 + pub uri: jacquard_common::types::string::AtUri<'a>, 197 + #[serde(borrow)] 198 + pub value: FollowAccept<'a>, 199 + } 200 + 201 + impl From<FollowAcceptGetRecordOutput<'_>> for FollowAccept<'_> { 202 + fn from(output: FollowAcceptGetRecordOutput<'_>) -> Self { 203 + use jacquard_common::IntoStatic; 204 + output.value.into_static() 205 + } 206 + } 207 + 208 + impl jacquard_common::types::collection::Collection for FollowAccept<'_> { 209 + const NSID: &'static str = "sh.weaver.graph.followAccept"; 210 + type Record = FollowAcceptRecord; 211 + } 212 + 213 + /// Marker type for deserializing records from this collection. 214 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 215 + pub struct FollowAcceptRecord; 216 + impl jacquard_common::xrpc::XrpcResp for FollowAcceptRecord { 217 + const NSID: &'static str = "sh.weaver.graph.followAccept"; 218 + const ENCODING: &'static str = "application/json"; 219 + type Output<'de> = FollowAcceptGetRecordOutput<'de>; 220 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 221 + } 222 + 223 + impl jacquard_common::types::collection::Collection for FollowAcceptRecord { 224 + const NSID: &'static str = "sh.weaver.graph.followAccept"; 225 + type Record = FollowAcceptRecord; 226 + } 227 + 228 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for FollowAccept<'a> { 229 + fn nsid() -> &'static str { 230 + "sh.weaver.graph.followAccept" 231 + } 232 + fn def_name() -> &'static str { 233 + "main" 234 + } 235 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 236 + lexicon_doc_sh_weaver_graph_followAccept() 237 + } 238 + fn validate( 239 + &self, 240 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 241 + Ok(()) 242 + } 243 + } 244 + 245 + fn lexicon_doc_sh_weaver_graph_followAccept() -> ::jacquard_lexicon::lexicon::LexiconDoc< 246 + 'static, 247 + > { 248 + ::jacquard_lexicon::lexicon::LexiconDoc { 249 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 250 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.followAccept"), 251 + revision: None, 252 + description: None, 253 + defs: { 254 + let mut map = ::std::collections::BTreeMap::new(); 255 + map.insert( 256 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 257 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 258 + description: Some( 259 + ::jacquard_common::CowStr::new_static( 260 + "Acceptance of a follow request. Completes the two-way agreement.", 261 + ), 262 + ), 263 + key: Some(::jacquard_common::CowStr::new_static("tid")), 264 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 265 + description: None, 266 + required: Some( 267 + vec![ 268 + ::jacquard_common::smol_str::SmolStr::new_static("follow"), 269 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 270 + ], 271 + ), 272 + nullable: None, 273 + properties: { 274 + #[allow(unused_mut)] 275 + let mut map = ::std::collections::BTreeMap::new(); 276 + map.insert( 277 + ::jacquard_common::smol_str::SmolStr::new_static( 278 + "createdAt", 279 + ), 280 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 281 + description: None, 282 + format: Some( 283 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 284 + ), 285 + default: None, 286 + min_length: None, 287 + max_length: None, 288 + min_graphemes: None, 289 + max_graphemes: None, 290 + r#enum: None, 291 + r#const: None, 292 + known_values: None, 293 + }), 294 + ); 295 + map.insert( 296 + ::jacquard_common::smol_str::SmolStr::new_static("follow"), 297 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 298 + description: None, 299 + r#ref: ::jacquard_common::CowStr::new_static( 300 + "com.atproto.repo.strongRef", 301 + ), 302 + }), 303 + ); 304 + map 305 + }, 306 + }), 307 + }), 308 + ); 309 + map 310 + }, 311 + } 312 + }
+322
crates/weaver-api/src/sh_weaver/graph/follow_gate.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.followGate 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// Settings controlling follow approval behavior. Absence means auto-accept. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct FollowGate<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// If true, previously auto-accepted follows are invalidated when requireApproval is enabled. Appview should treat followAccept records created before this gate's createdAt as invalid. 23 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 + pub invalidate_prior: std::option::Option<bool>, 25 + /// If true, follows require manual acceptance. 26 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 + pub require_approval: std::option::Option<bool>, 28 + } 29 + 30 + pub mod follow_gate_state { 31 + 32 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 33 + #[allow(unused)] 34 + use ::core::marker::PhantomData; 35 + mod sealed { 36 + pub trait Sealed {} 37 + } 38 + /// State trait tracking which required fields have been set 39 + pub trait State: sealed::Sealed { 40 + type CreatedAt; 41 + } 42 + /// Empty state - all required fields are unset 43 + pub struct Empty(()); 44 + impl sealed::Sealed for Empty {} 45 + impl State for Empty { 46 + type CreatedAt = Unset; 47 + } 48 + ///State transition - sets the `created_at` field to Set 49 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 50 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 51 + impl<S: State> State for SetCreatedAt<S> { 52 + type CreatedAt = Set<members::created_at>; 53 + } 54 + /// Marker types for field names 55 + #[allow(non_camel_case_types)] 56 + pub mod members { 57 + ///Marker type for the `created_at` field 58 + pub struct created_at(()); 59 + } 60 + } 61 + 62 + /// Builder for constructing an instance of this type 63 + pub struct FollowGateBuilder<'a, S: follow_gate_state::State> { 64 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 65 + __unsafe_private_named: ( 66 + ::core::option::Option<jacquard_common::types::string::Datetime>, 67 + ::core::option::Option<bool>, 68 + ::core::option::Option<bool>, 69 + ), 70 + _phantom: ::core::marker::PhantomData<&'a ()>, 71 + } 72 + 73 + impl<'a> FollowGate<'a> { 74 + /// Create a new builder for this type 75 + pub fn new() -> FollowGateBuilder<'a, follow_gate_state::Empty> { 76 + FollowGateBuilder::new() 77 + } 78 + } 79 + 80 + impl<'a> FollowGateBuilder<'a, follow_gate_state::Empty> { 81 + /// Create a new builder with all fields unset 82 + pub fn new() -> Self { 83 + FollowGateBuilder { 84 + _phantom_state: ::core::marker::PhantomData, 85 + __unsafe_private_named: (None, None, None), 86 + _phantom: ::core::marker::PhantomData, 87 + } 88 + } 89 + } 90 + 91 + impl<'a, S> FollowGateBuilder<'a, S> 92 + where 93 + S: follow_gate_state::State, 94 + S::CreatedAt: follow_gate_state::IsUnset, 95 + { 96 + /// Set the `createdAt` field (required) 97 + pub fn created_at( 98 + mut self, 99 + value: impl Into<jacquard_common::types::string::Datetime>, 100 + ) -> FollowGateBuilder<'a, follow_gate_state::SetCreatedAt<S>> { 101 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 102 + FollowGateBuilder { 103 + _phantom_state: ::core::marker::PhantomData, 104 + __unsafe_private_named: self.__unsafe_private_named, 105 + _phantom: ::core::marker::PhantomData, 106 + } 107 + } 108 + } 109 + 110 + impl<'a, S: follow_gate_state::State> FollowGateBuilder<'a, S> { 111 + /// Set the `invalidatePrior` field (optional) 112 + pub fn invalidate_prior(mut self, value: impl Into<Option<bool>>) -> Self { 113 + self.__unsafe_private_named.1 = value.into(); 114 + self 115 + } 116 + /// Set the `invalidatePrior` field to an Option value (optional) 117 + pub fn maybe_invalidate_prior(mut self, value: Option<bool>) -> Self { 118 + self.__unsafe_private_named.1 = value; 119 + self 120 + } 121 + } 122 + 123 + impl<'a, S: follow_gate_state::State> FollowGateBuilder<'a, S> { 124 + /// Set the `requireApproval` field (optional) 125 + pub fn require_approval(mut self, value: impl Into<Option<bool>>) -> Self { 126 + self.__unsafe_private_named.2 = value.into(); 127 + self 128 + } 129 + /// Set the `requireApproval` field to an Option value (optional) 130 + pub fn maybe_require_approval(mut self, value: Option<bool>) -> Self { 131 + self.__unsafe_private_named.2 = value; 132 + self 133 + } 134 + } 135 + 136 + impl<'a, S> FollowGateBuilder<'a, S> 137 + where 138 + S: follow_gate_state::State, 139 + S::CreatedAt: follow_gate_state::IsSet, 140 + { 141 + /// Build the final struct 142 + pub fn build(self) -> FollowGate<'a> { 143 + FollowGate { 144 + created_at: self.__unsafe_private_named.0.unwrap(), 145 + invalidate_prior: self.__unsafe_private_named.1, 146 + require_approval: self.__unsafe_private_named.2, 147 + extra_data: Default::default(), 148 + } 149 + } 150 + /// Build the final struct with custom extra_data 151 + pub fn build_with_data( 152 + self, 153 + extra_data: std::collections::BTreeMap< 154 + jacquard_common::smol_str::SmolStr, 155 + jacquard_common::types::value::Data<'a>, 156 + >, 157 + ) -> FollowGate<'a> { 158 + FollowGate { 159 + created_at: self.__unsafe_private_named.0.unwrap(), 160 + invalidate_prior: self.__unsafe_private_named.1, 161 + require_approval: self.__unsafe_private_named.2, 162 + extra_data: Some(extra_data), 163 + } 164 + } 165 + } 166 + 167 + impl<'a> FollowGate<'a> { 168 + pub fn uri( 169 + uri: impl Into<jacquard_common::CowStr<'a>>, 170 + ) -> Result< 171 + jacquard_common::types::uri::RecordUri<'a, FollowGateRecord>, 172 + jacquard_common::types::uri::UriError, 173 + > { 174 + jacquard_common::types::uri::RecordUri::try_from_uri( 175 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 176 + ) 177 + } 178 + } 179 + 180 + /// Typed wrapper for GetRecord response with this collection's record type. 181 + #[derive( 182 + serde::Serialize, 183 + serde::Deserialize, 184 + Debug, 185 + Clone, 186 + PartialEq, 187 + Eq, 188 + jacquard_derive::IntoStatic 189 + )] 190 + #[serde(rename_all = "camelCase")] 191 + pub struct FollowGateGetRecordOutput<'a> { 192 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 193 + #[serde(borrow)] 194 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 195 + #[serde(borrow)] 196 + pub uri: jacquard_common::types::string::AtUri<'a>, 197 + #[serde(borrow)] 198 + pub value: FollowGate<'a>, 199 + } 200 + 201 + impl From<FollowGateGetRecordOutput<'_>> for FollowGate<'_> { 202 + fn from(output: FollowGateGetRecordOutput<'_>) -> Self { 203 + use jacquard_common::IntoStatic; 204 + output.value.into_static() 205 + } 206 + } 207 + 208 + impl jacquard_common::types::collection::Collection for FollowGate<'_> { 209 + const NSID: &'static str = "sh.weaver.graph.followGate"; 210 + type Record = FollowGateRecord; 211 + } 212 + 213 + /// Marker type for deserializing records from this collection. 214 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 215 + pub struct FollowGateRecord; 216 + impl jacquard_common::xrpc::XrpcResp for FollowGateRecord { 217 + const NSID: &'static str = "sh.weaver.graph.followGate"; 218 + const ENCODING: &'static str = "application/json"; 219 + type Output<'de> = FollowGateGetRecordOutput<'de>; 220 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 221 + } 222 + 223 + impl jacquard_common::types::collection::Collection for FollowGateRecord { 224 + const NSID: &'static str = "sh.weaver.graph.followGate"; 225 + type Record = FollowGateRecord; 226 + } 227 + 228 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for FollowGate<'a> { 229 + fn nsid() -> &'static str { 230 + "sh.weaver.graph.followGate" 231 + } 232 + fn def_name() -> &'static str { 233 + "main" 234 + } 235 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 236 + lexicon_doc_sh_weaver_graph_followGate() 237 + } 238 + fn validate( 239 + &self, 240 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 241 + Ok(()) 242 + } 243 + } 244 + 245 + fn lexicon_doc_sh_weaver_graph_followGate() -> ::jacquard_lexicon::lexicon::LexiconDoc< 246 + 'static, 247 + > { 248 + ::jacquard_lexicon::lexicon::LexiconDoc { 249 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 250 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.followGate"), 251 + revision: None, 252 + description: None, 253 + defs: { 254 + let mut map = ::std::collections::BTreeMap::new(); 255 + map.insert( 256 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 257 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 258 + description: Some( 259 + ::jacquard_common::CowStr::new_static( 260 + "Settings controlling follow approval behavior. Absence means auto-accept.", 261 + ), 262 + ), 263 + key: Some(::jacquard_common::CowStr::new_static("self")), 264 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 265 + description: None, 266 + required: Some( 267 + vec![ 268 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 269 + ], 270 + ), 271 + nullable: None, 272 + properties: { 273 + #[allow(unused_mut)] 274 + let mut map = ::std::collections::BTreeMap::new(); 275 + map.insert( 276 + ::jacquard_common::smol_str::SmolStr::new_static( 277 + "createdAt", 278 + ), 279 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 280 + description: None, 281 + format: Some( 282 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 283 + ), 284 + default: None, 285 + min_length: None, 286 + max_length: None, 287 + min_graphemes: None, 288 + max_graphemes: None, 289 + r#enum: None, 290 + r#const: None, 291 + known_values: None, 292 + }), 293 + ); 294 + map.insert( 295 + ::jacquard_common::smol_str::SmolStr::new_static( 296 + "invalidatePrior", 297 + ), 298 + ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean { 299 + description: None, 300 + default: None, 301 + r#const: None, 302 + }), 303 + ); 304 + map.insert( 305 + ::jacquard_common::smol_str::SmolStr::new_static( 306 + "requireApproval", 307 + ), 308 + ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean { 309 + description: None, 310 + default: None, 311 + r#const: None, 312 + }), 313 + ); 314 + map 315 + }, 316 + }), 317 + }), 318 + ); 319 + map 320 + }, 321 + } 322 + }
+312
crates/weaver-api/src/sh_weaver/graph/like.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.like 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// Record declaring a 'like' of a notebook or entry. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct Like<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// The notebook or entry being liked. 23 + #[serde(borrow)] 24 + pub subject: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 25 + } 26 + 27 + pub mod like_state { 28 + 29 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 30 + #[allow(unused)] 31 + use ::core::marker::PhantomData; 32 + mod sealed { 33 + pub trait Sealed {} 34 + } 35 + /// State trait tracking which required fields have been set 36 + pub trait State: sealed::Sealed { 37 + type Subject; 38 + type CreatedAt; 39 + } 40 + /// Empty state - all required fields are unset 41 + pub struct Empty(()); 42 + impl sealed::Sealed for Empty {} 43 + impl State for Empty { 44 + type Subject = Unset; 45 + type CreatedAt = Unset; 46 + } 47 + ///State transition - sets the `subject` field to Set 48 + pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>); 49 + impl<S: State> sealed::Sealed for SetSubject<S> {} 50 + impl<S: State> State for SetSubject<S> { 51 + type Subject = Set<members::subject>; 52 + type CreatedAt = S::CreatedAt; 53 + } 54 + ///State transition - sets the `created_at` field to Set 55 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 56 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 57 + impl<S: State> State for SetCreatedAt<S> { 58 + type Subject = S::Subject; 59 + type CreatedAt = Set<members::created_at>; 60 + } 61 + /// Marker types for field names 62 + #[allow(non_camel_case_types)] 63 + pub mod members { 64 + ///Marker type for the `subject` field 65 + pub struct subject(()); 66 + ///Marker type for the `created_at` field 67 + pub struct created_at(()); 68 + } 69 + } 70 + 71 + /// Builder for constructing an instance of this type 72 + pub struct LikeBuilder<'a, S: like_state::State> { 73 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 74 + __unsafe_private_named: ( 75 + ::core::option::Option<jacquard_common::types::string::Datetime>, 76 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 77 + ), 78 + _phantom: ::core::marker::PhantomData<&'a ()>, 79 + } 80 + 81 + impl<'a> Like<'a> { 82 + /// Create a new builder for this type 83 + pub fn new() -> LikeBuilder<'a, like_state::Empty> { 84 + LikeBuilder::new() 85 + } 86 + } 87 + 88 + impl<'a> LikeBuilder<'a, like_state::Empty> { 89 + /// Create a new builder with all fields unset 90 + pub fn new() -> Self { 91 + LikeBuilder { 92 + _phantom_state: ::core::marker::PhantomData, 93 + __unsafe_private_named: (None, None), 94 + _phantom: ::core::marker::PhantomData, 95 + } 96 + } 97 + } 98 + 99 + impl<'a, S> LikeBuilder<'a, S> 100 + where 101 + S: like_state::State, 102 + S::CreatedAt: like_state::IsUnset, 103 + { 104 + /// Set the `createdAt` field (required) 105 + pub fn created_at( 106 + mut self, 107 + value: impl Into<jacquard_common::types::string::Datetime>, 108 + ) -> LikeBuilder<'a, like_state::SetCreatedAt<S>> { 109 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 110 + LikeBuilder { 111 + _phantom_state: ::core::marker::PhantomData, 112 + __unsafe_private_named: self.__unsafe_private_named, 113 + _phantom: ::core::marker::PhantomData, 114 + } 115 + } 116 + } 117 + 118 + impl<'a, S> LikeBuilder<'a, S> 119 + where 120 + S: like_state::State, 121 + S::Subject: like_state::IsUnset, 122 + { 123 + /// Set the `subject` field (required) 124 + pub fn subject( 125 + mut self, 126 + value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 127 + ) -> LikeBuilder<'a, like_state::SetSubject<S>> { 128 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 129 + LikeBuilder { 130 + _phantom_state: ::core::marker::PhantomData, 131 + __unsafe_private_named: self.__unsafe_private_named, 132 + _phantom: ::core::marker::PhantomData, 133 + } 134 + } 135 + } 136 + 137 + impl<'a, S> LikeBuilder<'a, S> 138 + where 139 + S: like_state::State, 140 + S::Subject: like_state::IsSet, 141 + S::CreatedAt: like_state::IsSet, 142 + { 143 + /// Build the final struct 144 + pub fn build(self) -> Like<'a> { 145 + Like { 146 + created_at: self.__unsafe_private_named.0.unwrap(), 147 + subject: self.__unsafe_private_named.1.unwrap(), 148 + extra_data: Default::default(), 149 + } 150 + } 151 + /// Build the final struct with custom extra_data 152 + pub fn build_with_data( 153 + self, 154 + extra_data: std::collections::BTreeMap< 155 + jacquard_common::smol_str::SmolStr, 156 + jacquard_common::types::value::Data<'a>, 157 + >, 158 + ) -> Like<'a> { 159 + Like { 160 + created_at: self.__unsafe_private_named.0.unwrap(), 161 + subject: self.__unsafe_private_named.1.unwrap(), 162 + extra_data: Some(extra_data), 163 + } 164 + } 165 + } 166 + 167 + impl<'a> Like<'a> { 168 + pub fn uri( 169 + uri: impl Into<jacquard_common::CowStr<'a>>, 170 + ) -> Result< 171 + jacquard_common::types::uri::RecordUri<'a, LikeRecord>, 172 + jacquard_common::types::uri::UriError, 173 + > { 174 + jacquard_common::types::uri::RecordUri::try_from_uri( 175 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 176 + ) 177 + } 178 + } 179 + 180 + /// Typed wrapper for GetRecord response with this collection's record type. 181 + #[derive( 182 + serde::Serialize, 183 + serde::Deserialize, 184 + Debug, 185 + Clone, 186 + PartialEq, 187 + Eq, 188 + jacquard_derive::IntoStatic 189 + )] 190 + #[serde(rename_all = "camelCase")] 191 + pub struct LikeGetRecordOutput<'a> { 192 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 193 + #[serde(borrow)] 194 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 195 + #[serde(borrow)] 196 + pub uri: jacquard_common::types::string::AtUri<'a>, 197 + #[serde(borrow)] 198 + pub value: Like<'a>, 199 + } 200 + 201 + impl From<LikeGetRecordOutput<'_>> for Like<'_> { 202 + fn from(output: LikeGetRecordOutput<'_>) -> Self { 203 + use jacquard_common::IntoStatic; 204 + output.value.into_static() 205 + } 206 + } 207 + 208 + impl jacquard_common::types::collection::Collection for Like<'_> { 209 + const NSID: &'static str = "sh.weaver.graph.like"; 210 + type Record = LikeRecord; 211 + } 212 + 213 + /// Marker type for deserializing records from this collection. 214 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 215 + pub struct LikeRecord; 216 + impl jacquard_common::xrpc::XrpcResp for LikeRecord { 217 + const NSID: &'static str = "sh.weaver.graph.like"; 218 + const ENCODING: &'static str = "application/json"; 219 + type Output<'de> = LikeGetRecordOutput<'de>; 220 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 221 + } 222 + 223 + impl jacquard_common::types::collection::Collection for LikeRecord { 224 + const NSID: &'static str = "sh.weaver.graph.like"; 225 + type Record = LikeRecord; 226 + } 227 + 228 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Like<'a> { 229 + fn nsid() -> &'static str { 230 + "sh.weaver.graph.like" 231 + } 232 + fn def_name() -> &'static str { 233 + "main" 234 + } 235 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 236 + lexicon_doc_sh_weaver_graph_like() 237 + } 238 + fn validate( 239 + &self, 240 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 241 + Ok(()) 242 + } 243 + } 244 + 245 + fn lexicon_doc_sh_weaver_graph_like() -> ::jacquard_lexicon::lexicon::LexiconDoc< 246 + 'static, 247 + > { 248 + ::jacquard_lexicon::lexicon::LexiconDoc { 249 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 250 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.like"), 251 + revision: None, 252 + description: None, 253 + defs: { 254 + let mut map = ::std::collections::BTreeMap::new(); 255 + map.insert( 256 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 257 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 258 + description: Some( 259 + ::jacquard_common::CowStr::new_static( 260 + "Record declaring a 'like' of a notebook or entry.", 261 + ), 262 + ), 263 + key: Some(::jacquard_common::CowStr::new_static("tid")), 264 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 265 + description: None, 266 + required: Some( 267 + vec![ 268 + ::jacquard_common::smol_str::SmolStr::new_static("subject"), 269 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 270 + ], 271 + ), 272 + nullable: None, 273 + properties: { 274 + #[allow(unused_mut)] 275 + let mut map = ::std::collections::BTreeMap::new(); 276 + map.insert( 277 + ::jacquard_common::smol_str::SmolStr::new_static( 278 + "createdAt", 279 + ), 280 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 281 + description: None, 282 + format: Some( 283 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 284 + ), 285 + default: None, 286 + min_length: None, 287 + max_length: None, 288 + min_graphemes: None, 289 + max_graphemes: None, 290 + r#enum: None, 291 + r#const: None, 292 + known_values: None, 293 + }), 294 + ); 295 + map.insert( 296 + ::jacquard_common::smol_str::SmolStr::new_static("subject"), 297 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 298 + description: None, 299 + r#ref: ::jacquard_common::CowStr::new_static( 300 + "com.atproto.repo.strongRef", 301 + ), 302 + }), 303 + ); 304 + map 305 + }, 306 + }), 307 + }), 308 + ); 309 + map 310 + }, 311 + } 312 + }
+596
crates/weaver-api/src/sh_weaver/graph/list.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.list 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[derive(Debug, Clone, PartialEq, Eq, Hash)] 9 + pub enum ListPurpose<'a> { 10 + ShWeaverGraphDefsCuratelist, 11 + ShWeaverGraphDefsReadinglist, 12 + Other(jacquard_common::CowStr<'a>), 13 + } 14 + 15 + impl<'a> ListPurpose<'a> { 16 + pub fn as_str(&self) -> &str { 17 + match self { 18 + Self::ShWeaverGraphDefsCuratelist => "sh.weaver.graph.defs#curatelist", 19 + Self::ShWeaverGraphDefsReadinglist => "sh.weaver.graph.defs#readinglist", 20 + Self::Other(s) => s.as_ref(), 21 + } 22 + } 23 + } 24 + 25 + impl<'a> From<&'a str> for ListPurpose<'a> { 26 + fn from(s: &'a str) -> Self { 27 + match s { 28 + "sh.weaver.graph.defs#curatelist" => Self::ShWeaverGraphDefsCuratelist, 29 + "sh.weaver.graph.defs#readinglist" => Self::ShWeaverGraphDefsReadinglist, 30 + _ => Self::Other(jacquard_common::CowStr::from(s)), 31 + } 32 + } 33 + } 34 + 35 + impl<'a> From<String> for ListPurpose<'a> { 36 + fn from(s: String) -> Self { 37 + match s.as_str() { 38 + "sh.weaver.graph.defs#curatelist" => Self::ShWeaverGraphDefsCuratelist, 39 + "sh.weaver.graph.defs#readinglist" => Self::ShWeaverGraphDefsReadinglist, 40 + _ => Self::Other(jacquard_common::CowStr::from(s)), 41 + } 42 + } 43 + } 44 + 45 + impl<'a> AsRef<str> for ListPurpose<'a> { 46 + fn as_ref(&self) -> &str { 47 + self.as_str() 48 + } 49 + } 50 + 51 + impl<'a> serde::Serialize for ListPurpose<'a> { 52 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 53 + where 54 + S: serde::Serializer, 55 + { 56 + serializer.serialize_str(self.as_str()) 57 + } 58 + } 59 + 60 + impl<'de, 'a> serde::Deserialize<'de> for ListPurpose<'a> 61 + where 62 + 'de: 'a, 63 + { 64 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 65 + where 66 + D: serde::Deserializer<'de>, 67 + { 68 + let s = <&'de str>::deserialize(deserializer)?; 69 + Ok(Self::from(s)) 70 + } 71 + } 72 + 73 + impl jacquard_common::IntoStatic for ListPurpose<'_> { 74 + type Output = ListPurpose<'static>; 75 + fn into_static(self) -> Self::Output { 76 + match self { 77 + ListPurpose::ShWeaverGraphDefsCuratelist => { 78 + ListPurpose::ShWeaverGraphDefsCuratelist 79 + } 80 + ListPurpose::ShWeaverGraphDefsReadinglist => { 81 + ListPurpose::ShWeaverGraphDefsReadinglist 82 + } 83 + ListPurpose::Other(v) => ListPurpose::Other(v.into_static()), 84 + } 85 + } 86 + } 87 + 88 + /// A curated list of notebooks and/or entries. 89 + #[jacquard_derive::lexicon] 90 + #[derive( 91 + serde::Serialize, 92 + serde::Deserialize, 93 + Debug, 94 + Clone, 95 + PartialEq, 96 + Eq, 97 + jacquard_derive::IntoStatic 98 + )] 99 + #[serde(rename_all = "camelCase")] 100 + pub struct List<'a> { 101 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 102 + #[serde(borrow)] 103 + pub avatar: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 104 + pub created_at: jacquard_common::types::string::Datetime, 105 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 106 + #[serde(borrow)] 107 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 108 + /// Display name for the list. 109 + #[serde(borrow)] 110 + pub name: jacquard_common::CowStr<'a>, 111 + /// The purpose/type of list. 112 + #[serde(borrow)] 113 + pub purpose: crate::sh_weaver::graph::list::ListPurpose<'a>, 114 + } 115 + 116 + pub mod list_state { 117 + 118 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 119 + #[allow(unused)] 120 + use ::core::marker::PhantomData; 121 + mod sealed { 122 + pub trait Sealed {} 123 + } 124 + /// State trait tracking which required fields have been set 125 + pub trait State: sealed::Sealed { 126 + type Name; 127 + type Purpose; 128 + type CreatedAt; 129 + } 130 + /// Empty state - all required fields are unset 131 + pub struct Empty(()); 132 + impl sealed::Sealed for Empty {} 133 + impl State for Empty { 134 + type Name = Unset; 135 + type Purpose = Unset; 136 + type CreatedAt = Unset; 137 + } 138 + ///State transition - sets the `name` field to Set 139 + pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>); 140 + impl<S: State> sealed::Sealed for SetName<S> {} 141 + impl<S: State> State for SetName<S> { 142 + type Name = Set<members::name>; 143 + type Purpose = S::Purpose; 144 + type CreatedAt = S::CreatedAt; 145 + } 146 + ///State transition - sets the `purpose` field to Set 147 + pub struct SetPurpose<S: State = Empty>(PhantomData<fn() -> S>); 148 + impl<S: State> sealed::Sealed for SetPurpose<S> {} 149 + impl<S: State> State for SetPurpose<S> { 150 + type Name = S::Name; 151 + type Purpose = Set<members::purpose>; 152 + type CreatedAt = S::CreatedAt; 153 + } 154 + ///State transition - sets the `created_at` field to Set 155 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 156 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 157 + impl<S: State> State for SetCreatedAt<S> { 158 + type Name = S::Name; 159 + type Purpose = S::Purpose; 160 + type CreatedAt = Set<members::created_at>; 161 + } 162 + /// Marker types for field names 163 + #[allow(non_camel_case_types)] 164 + pub mod members { 165 + ///Marker type for the `name` field 166 + pub struct name(()); 167 + ///Marker type for the `purpose` field 168 + pub struct purpose(()); 169 + ///Marker type for the `created_at` field 170 + pub struct created_at(()); 171 + } 172 + } 173 + 174 + /// Builder for constructing an instance of this type 175 + pub struct ListBuilder<'a, S: list_state::State> { 176 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 177 + __unsafe_private_named: ( 178 + ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 179 + ::core::option::Option<jacquard_common::types::string::Datetime>, 180 + ::core::option::Option<jacquard_common::CowStr<'a>>, 181 + ::core::option::Option<jacquard_common::CowStr<'a>>, 182 + ::core::option::Option<crate::sh_weaver::graph::list::ListPurpose<'a>>, 183 + ), 184 + _phantom: ::core::marker::PhantomData<&'a ()>, 185 + } 186 + 187 + impl<'a> List<'a> { 188 + /// Create a new builder for this type 189 + pub fn new() -> ListBuilder<'a, list_state::Empty> { 190 + ListBuilder::new() 191 + } 192 + } 193 + 194 + impl<'a> ListBuilder<'a, list_state::Empty> { 195 + /// Create a new builder with all fields unset 196 + pub fn new() -> Self { 197 + ListBuilder { 198 + _phantom_state: ::core::marker::PhantomData, 199 + __unsafe_private_named: (None, None, None, None, None), 200 + _phantom: ::core::marker::PhantomData, 201 + } 202 + } 203 + } 204 + 205 + impl<'a, S: list_state::State> ListBuilder<'a, S> { 206 + /// Set the `avatar` field (optional) 207 + pub fn avatar( 208 + mut self, 209 + value: impl Into<Option<jacquard_common::types::blob::BlobRef<'a>>>, 210 + ) -> Self { 211 + self.__unsafe_private_named.0 = value.into(); 212 + self 213 + } 214 + /// Set the `avatar` field to an Option value (optional) 215 + pub fn maybe_avatar( 216 + mut self, 217 + value: Option<jacquard_common::types::blob::BlobRef<'a>>, 218 + ) -> Self { 219 + self.__unsafe_private_named.0 = value; 220 + self 221 + } 222 + } 223 + 224 + impl<'a, S> ListBuilder<'a, S> 225 + where 226 + S: list_state::State, 227 + S::CreatedAt: list_state::IsUnset, 228 + { 229 + /// Set the `createdAt` field (required) 230 + pub fn created_at( 231 + mut self, 232 + value: impl Into<jacquard_common::types::string::Datetime>, 233 + ) -> ListBuilder<'a, list_state::SetCreatedAt<S>> { 234 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 235 + ListBuilder { 236 + _phantom_state: ::core::marker::PhantomData, 237 + __unsafe_private_named: self.__unsafe_private_named, 238 + _phantom: ::core::marker::PhantomData, 239 + } 240 + } 241 + } 242 + 243 + impl<'a, S: list_state::State> ListBuilder<'a, S> { 244 + /// Set the `description` field (optional) 245 + pub fn description( 246 + mut self, 247 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 248 + ) -> Self { 249 + self.__unsafe_private_named.2 = value.into(); 250 + self 251 + } 252 + /// Set the `description` field to an Option value (optional) 253 + pub fn maybe_description( 254 + mut self, 255 + value: Option<jacquard_common::CowStr<'a>>, 256 + ) -> Self { 257 + self.__unsafe_private_named.2 = value; 258 + self 259 + } 260 + } 261 + 262 + impl<'a, S> ListBuilder<'a, S> 263 + where 264 + S: list_state::State, 265 + S::Name: list_state::IsUnset, 266 + { 267 + /// Set the `name` field (required) 268 + pub fn name( 269 + mut self, 270 + value: impl Into<jacquard_common::CowStr<'a>>, 271 + ) -> ListBuilder<'a, list_state::SetName<S>> { 272 + self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 273 + ListBuilder { 274 + _phantom_state: ::core::marker::PhantomData, 275 + __unsafe_private_named: self.__unsafe_private_named, 276 + _phantom: ::core::marker::PhantomData, 277 + } 278 + } 279 + } 280 + 281 + impl<'a, S> ListBuilder<'a, S> 282 + where 283 + S: list_state::State, 284 + S::Purpose: list_state::IsUnset, 285 + { 286 + /// Set the `purpose` field (required) 287 + pub fn purpose( 288 + mut self, 289 + value: impl Into<crate::sh_weaver::graph::list::ListPurpose<'a>>, 290 + ) -> ListBuilder<'a, list_state::SetPurpose<S>> { 291 + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 292 + ListBuilder { 293 + _phantom_state: ::core::marker::PhantomData, 294 + __unsafe_private_named: self.__unsafe_private_named, 295 + _phantom: ::core::marker::PhantomData, 296 + } 297 + } 298 + } 299 + 300 + impl<'a, S> ListBuilder<'a, S> 301 + where 302 + S: list_state::State, 303 + S::Name: list_state::IsSet, 304 + S::Purpose: list_state::IsSet, 305 + S::CreatedAt: list_state::IsSet, 306 + { 307 + /// Build the final struct 308 + pub fn build(self) -> List<'a> { 309 + List { 310 + avatar: self.__unsafe_private_named.0, 311 + created_at: self.__unsafe_private_named.1.unwrap(), 312 + description: self.__unsafe_private_named.2, 313 + name: self.__unsafe_private_named.3.unwrap(), 314 + purpose: self.__unsafe_private_named.4.unwrap(), 315 + extra_data: Default::default(), 316 + } 317 + } 318 + /// Build the final struct with custom extra_data 319 + pub fn build_with_data( 320 + self, 321 + extra_data: std::collections::BTreeMap< 322 + jacquard_common::smol_str::SmolStr, 323 + jacquard_common::types::value::Data<'a>, 324 + >, 325 + ) -> List<'a> { 326 + List { 327 + avatar: self.__unsafe_private_named.0, 328 + created_at: self.__unsafe_private_named.1.unwrap(), 329 + description: self.__unsafe_private_named.2, 330 + name: self.__unsafe_private_named.3.unwrap(), 331 + purpose: self.__unsafe_private_named.4.unwrap(), 332 + extra_data: Some(extra_data), 333 + } 334 + } 335 + } 336 + 337 + impl<'a> List<'a> { 338 + pub fn uri( 339 + uri: impl Into<jacquard_common::CowStr<'a>>, 340 + ) -> Result< 341 + jacquard_common::types::uri::RecordUri<'a, ListRecord>, 342 + jacquard_common::types::uri::UriError, 343 + > { 344 + jacquard_common::types::uri::RecordUri::try_from_uri( 345 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 346 + ) 347 + } 348 + } 349 + 350 + /// Typed wrapper for GetRecord response with this collection's record type. 351 + #[derive( 352 + serde::Serialize, 353 + serde::Deserialize, 354 + Debug, 355 + Clone, 356 + PartialEq, 357 + Eq, 358 + jacquard_derive::IntoStatic 359 + )] 360 + #[serde(rename_all = "camelCase")] 361 + pub struct ListGetRecordOutput<'a> { 362 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 363 + #[serde(borrow)] 364 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 365 + #[serde(borrow)] 366 + pub uri: jacquard_common::types::string::AtUri<'a>, 367 + #[serde(borrow)] 368 + pub value: List<'a>, 369 + } 370 + 371 + impl From<ListGetRecordOutput<'_>> for List<'_> { 372 + fn from(output: ListGetRecordOutput<'_>) -> Self { 373 + use jacquard_common::IntoStatic; 374 + output.value.into_static() 375 + } 376 + } 377 + 378 + impl jacquard_common::types::collection::Collection for List<'_> { 379 + const NSID: &'static str = "sh.weaver.graph.list"; 380 + type Record = ListRecord; 381 + } 382 + 383 + /// Marker type for deserializing records from this collection. 384 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 385 + pub struct ListRecord; 386 + impl jacquard_common::xrpc::XrpcResp for ListRecord { 387 + const NSID: &'static str = "sh.weaver.graph.list"; 388 + const ENCODING: &'static str = "application/json"; 389 + type Output<'de> = ListGetRecordOutput<'de>; 390 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 391 + } 392 + 393 + impl jacquard_common::types::collection::Collection for ListRecord { 394 + const NSID: &'static str = "sh.weaver.graph.list"; 395 + type Record = ListRecord; 396 + } 397 + 398 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for List<'a> { 399 + fn nsid() -> &'static str { 400 + "sh.weaver.graph.list" 401 + } 402 + fn def_name() -> &'static str { 403 + "main" 404 + } 405 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 406 + lexicon_doc_sh_weaver_graph_list() 407 + } 408 + fn validate( 409 + &self, 410 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 411 + if let Some(ref value) = self.description { 412 + #[allow(unused_comparisons)] 413 + if <str>::len(value.as_ref()) > 3000usize { 414 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 415 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 416 + "description", 417 + ), 418 + max: 3000usize, 419 + actual: <str>::len(value.as_ref()), 420 + }); 421 + } 422 + } 423 + if let Some(ref value) = self.description { 424 + { 425 + let count = ::unicode_segmentation::UnicodeSegmentation::graphemes( 426 + value.as_ref(), 427 + true, 428 + ) 429 + .count(); 430 + if count > 300usize { 431 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxGraphemes { 432 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 433 + "description", 434 + ), 435 + max: 300usize, 436 + actual: count, 437 + }); 438 + } 439 + } 440 + } 441 + { 442 + let value = &self.name; 443 + #[allow(unused_comparisons)] 444 + if <str>::len(value.as_ref()) > 64usize { 445 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 446 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 447 + "name", 448 + ), 449 + max: 64usize, 450 + actual: <str>::len(value.as_ref()), 451 + }); 452 + } 453 + } 454 + { 455 + let value = &self.name; 456 + #[allow(unused_comparisons)] 457 + if <str>::len(value.as_ref()) < 1usize { 458 + return Err(::jacquard_lexicon::validation::ConstraintError::MinLength { 459 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 460 + "name", 461 + ), 462 + min: 1usize, 463 + actual: <str>::len(value.as_ref()), 464 + }); 465 + } 466 + } 467 + Ok(()) 468 + } 469 + } 470 + 471 + fn lexicon_doc_sh_weaver_graph_list() -> ::jacquard_lexicon::lexicon::LexiconDoc< 472 + 'static, 473 + > { 474 + ::jacquard_lexicon::lexicon::LexiconDoc { 475 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 476 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.list"), 477 + revision: None, 478 + description: None, 479 + defs: { 480 + let mut map = ::std::collections::BTreeMap::new(); 481 + map.insert( 482 + ::jacquard_common::smol_str::SmolStr::new_static("listPurpose"), 483 + ::jacquard_lexicon::lexicon::LexUserType::String(::jacquard_lexicon::lexicon::LexString { 484 + description: None, 485 + format: None, 486 + default: None, 487 + min_length: None, 488 + max_length: None, 489 + min_graphemes: None, 490 + max_graphemes: None, 491 + r#enum: None, 492 + r#const: None, 493 + known_values: None, 494 + }), 495 + ); 496 + map.insert( 497 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 498 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 499 + description: Some( 500 + ::jacquard_common::CowStr::new_static( 501 + "A curated list of notebooks and/or entries.", 502 + ), 503 + ), 504 + key: Some(::jacquard_common::CowStr::new_static("tid")), 505 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 506 + description: None, 507 + required: Some( 508 + vec![ 509 + ::jacquard_common::smol_str::SmolStr::new_static("name"), 510 + ::jacquard_common::smol_str::SmolStr::new_static("purpose"), 511 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 512 + ], 513 + ), 514 + nullable: None, 515 + properties: { 516 + #[allow(unused_mut)] 517 + let mut map = ::std::collections::BTreeMap::new(); 518 + map.insert( 519 + ::jacquard_common::smol_str::SmolStr::new_static("avatar"), 520 + ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { 521 + description: None, 522 + accept: None, 523 + max_size: None, 524 + }), 525 + ); 526 + map.insert( 527 + ::jacquard_common::smol_str::SmolStr::new_static( 528 + "createdAt", 529 + ), 530 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 531 + description: None, 532 + format: Some( 533 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 534 + ), 535 + default: None, 536 + min_length: None, 537 + max_length: None, 538 + min_graphemes: None, 539 + max_graphemes: None, 540 + r#enum: None, 541 + r#const: None, 542 + known_values: None, 543 + }), 544 + ); 545 + map.insert( 546 + ::jacquard_common::smol_str::SmolStr::new_static( 547 + "description", 548 + ), 549 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 550 + description: None, 551 + format: None, 552 + default: None, 553 + min_length: None, 554 + max_length: Some(3000usize), 555 + min_graphemes: None, 556 + max_graphemes: Some(300usize), 557 + r#enum: None, 558 + r#const: None, 559 + known_values: None, 560 + }), 561 + ); 562 + map.insert( 563 + ::jacquard_common::smol_str::SmolStr::new_static("name"), 564 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 565 + description: Some( 566 + ::jacquard_common::CowStr::new_static( 567 + "Display name for the list.", 568 + ), 569 + ), 570 + format: None, 571 + default: None, 572 + min_length: Some(1usize), 573 + max_length: Some(64usize), 574 + min_graphemes: None, 575 + max_graphemes: None, 576 + r#enum: None, 577 + r#const: None, 578 + known_values: None, 579 + }), 580 + ); 581 + map.insert( 582 + ::jacquard_common::smol_str::SmolStr::new_static("purpose"), 583 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 584 + description: None, 585 + r#ref: ::jacquard_common::CowStr::new_static("#listPurpose"), 586 + }), 587 + ); 588 + map 589 + }, 590 + }), 591 + }), 592 + ); 593 + map 594 + }, 595 + } 596 + }
+372
crates/weaver-api/src/sh_weaver/graph/listitem.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.listitem 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// An item in a list. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct Listitem<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// Reference to the list record. 23 + #[serde(borrow)] 24 + pub list: jacquard_common::types::string::AtUri<'a>, 25 + /// The notebook or entry being added to the list. 26 + #[serde(borrow)] 27 + pub subject: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 28 + } 29 + 30 + pub mod listitem_state { 31 + 32 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 33 + #[allow(unused)] 34 + use ::core::marker::PhantomData; 35 + mod sealed { 36 + pub trait Sealed {} 37 + } 38 + /// State trait tracking which required fields have been set 39 + pub trait State: sealed::Sealed { 40 + type Subject; 41 + type List; 42 + type CreatedAt; 43 + } 44 + /// Empty state - all required fields are unset 45 + pub struct Empty(()); 46 + impl sealed::Sealed for Empty {} 47 + impl State for Empty { 48 + type Subject = Unset; 49 + type List = Unset; 50 + type CreatedAt = Unset; 51 + } 52 + ///State transition - sets the `subject` field to Set 53 + pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>); 54 + impl<S: State> sealed::Sealed for SetSubject<S> {} 55 + impl<S: State> State for SetSubject<S> { 56 + type Subject = Set<members::subject>; 57 + type List = S::List; 58 + type CreatedAt = S::CreatedAt; 59 + } 60 + ///State transition - sets the `list` field to Set 61 + pub struct SetList<S: State = Empty>(PhantomData<fn() -> S>); 62 + impl<S: State> sealed::Sealed for SetList<S> {} 63 + impl<S: State> State for SetList<S> { 64 + type Subject = S::Subject; 65 + type List = Set<members::list>; 66 + type CreatedAt = S::CreatedAt; 67 + } 68 + ///State transition - sets the `created_at` field to Set 69 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 70 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 71 + impl<S: State> State for SetCreatedAt<S> { 72 + type Subject = S::Subject; 73 + type List = S::List; 74 + type CreatedAt = Set<members::created_at>; 75 + } 76 + /// Marker types for field names 77 + #[allow(non_camel_case_types)] 78 + pub mod members { 79 + ///Marker type for the `subject` field 80 + pub struct subject(()); 81 + ///Marker type for the `list` field 82 + pub struct list(()); 83 + ///Marker type for the `created_at` field 84 + pub struct created_at(()); 85 + } 86 + } 87 + 88 + /// Builder for constructing an instance of this type 89 + pub struct ListitemBuilder<'a, S: listitem_state::State> { 90 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 91 + __unsafe_private_named: ( 92 + ::core::option::Option<jacquard_common::types::string::Datetime>, 93 + ::core::option::Option<jacquard_common::types::string::AtUri<'a>>, 94 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 95 + ), 96 + _phantom: ::core::marker::PhantomData<&'a ()>, 97 + } 98 + 99 + impl<'a> Listitem<'a> { 100 + /// Create a new builder for this type 101 + pub fn new() -> ListitemBuilder<'a, listitem_state::Empty> { 102 + ListitemBuilder::new() 103 + } 104 + } 105 + 106 + impl<'a> ListitemBuilder<'a, listitem_state::Empty> { 107 + /// Create a new builder with all fields unset 108 + pub fn new() -> Self { 109 + ListitemBuilder { 110 + _phantom_state: ::core::marker::PhantomData, 111 + __unsafe_private_named: (None, None, None), 112 + _phantom: ::core::marker::PhantomData, 113 + } 114 + } 115 + } 116 + 117 + impl<'a, S> ListitemBuilder<'a, S> 118 + where 119 + S: listitem_state::State, 120 + S::CreatedAt: listitem_state::IsUnset, 121 + { 122 + /// Set the `createdAt` field (required) 123 + pub fn created_at( 124 + mut self, 125 + value: impl Into<jacquard_common::types::string::Datetime>, 126 + ) -> ListitemBuilder<'a, listitem_state::SetCreatedAt<S>> { 127 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 128 + ListitemBuilder { 129 + _phantom_state: ::core::marker::PhantomData, 130 + __unsafe_private_named: self.__unsafe_private_named, 131 + _phantom: ::core::marker::PhantomData, 132 + } 133 + } 134 + } 135 + 136 + impl<'a, S> ListitemBuilder<'a, S> 137 + where 138 + S: listitem_state::State, 139 + S::List: listitem_state::IsUnset, 140 + { 141 + /// Set the `list` field (required) 142 + pub fn list( 143 + mut self, 144 + value: impl Into<jacquard_common::types::string::AtUri<'a>>, 145 + ) -> ListitemBuilder<'a, listitem_state::SetList<S>> { 146 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 147 + ListitemBuilder { 148 + _phantom_state: ::core::marker::PhantomData, 149 + __unsafe_private_named: self.__unsafe_private_named, 150 + _phantom: ::core::marker::PhantomData, 151 + } 152 + } 153 + } 154 + 155 + impl<'a, S> ListitemBuilder<'a, S> 156 + where 157 + S: listitem_state::State, 158 + S::Subject: listitem_state::IsUnset, 159 + { 160 + /// Set the `subject` field (required) 161 + pub fn subject( 162 + mut self, 163 + value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 164 + ) -> ListitemBuilder<'a, listitem_state::SetSubject<S>> { 165 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 166 + ListitemBuilder { 167 + _phantom_state: ::core::marker::PhantomData, 168 + __unsafe_private_named: self.__unsafe_private_named, 169 + _phantom: ::core::marker::PhantomData, 170 + } 171 + } 172 + } 173 + 174 + impl<'a, S> ListitemBuilder<'a, S> 175 + where 176 + S: listitem_state::State, 177 + S::Subject: listitem_state::IsSet, 178 + S::List: listitem_state::IsSet, 179 + S::CreatedAt: listitem_state::IsSet, 180 + { 181 + /// Build the final struct 182 + pub fn build(self) -> Listitem<'a> { 183 + Listitem { 184 + created_at: self.__unsafe_private_named.0.unwrap(), 185 + list: self.__unsafe_private_named.1.unwrap(), 186 + subject: self.__unsafe_private_named.2.unwrap(), 187 + extra_data: Default::default(), 188 + } 189 + } 190 + /// Build the final struct with custom extra_data 191 + pub fn build_with_data( 192 + self, 193 + extra_data: std::collections::BTreeMap< 194 + jacquard_common::smol_str::SmolStr, 195 + jacquard_common::types::value::Data<'a>, 196 + >, 197 + ) -> Listitem<'a> { 198 + Listitem { 199 + created_at: self.__unsafe_private_named.0.unwrap(), 200 + list: self.__unsafe_private_named.1.unwrap(), 201 + subject: self.__unsafe_private_named.2.unwrap(), 202 + extra_data: Some(extra_data), 203 + } 204 + } 205 + } 206 + 207 + impl<'a> Listitem<'a> { 208 + pub fn uri( 209 + uri: impl Into<jacquard_common::CowStr<'a>>, 210 + ) -> Result< 211 + jacquard_common::types::uri::RecordUri<'a, ListitemRecord>, 212 + jacquard_common::types::uri::UriError, 213 + > { 214 + jacquard_common::types::uri::RecordUri::try_from_uri( 215 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 216 + ) 217 + } 218 + } 219 + 220 + /// Typed wrapper for GetRecord response with this collection's record type. 221 + #[derive( 222 + serde::Serialize, 223 + serde::Deserialize, 224 + Debug, 225 + Clone, 226 + PartialEq, 227 + Eq, 228 + jacquard_derive::IntoStatic 229 + )] 230 + #[serde(rename_all = "camelCase")] 231 + pub struct ListitemGetRecordOutput<'a> { 232 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 233 + #[serde(borrow)] 234 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 235 + #[serde(borrow)] 236 + pub uri: jacquard_common::types::string::AtUri<'a>, 237 + #[serde(borrow)] 238 + pub value: Listitem<'a>, 239 + } 240 + 241 + impl From<ListitemGetRecordOutput<'_>> for Listitem<'_> { 242 + fn from(output: ListitemGetRecordOutput<'_>) -> Self { 243 + use jacquard_common::IntoStatic; 244 + output.value.into_static() 245 + } 246 + } 247 + 248 + impl jacquard_common::types::collection::Collection for Listitem<'_> { 249 + const NSID: &'static str = "sh.weaver.graph.listitem"; 250 + type Record = ListitemRecord; 251 + } 252 + 253 + /// Marker type for deserializing records from this collection. 254 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 255 + pub struct ListitemRecord; 256 + impl jacquard_common::xrpc::XrpcResp for ListitemRecord { 257 + const NSID: &'static str = "sh.weaver.graph.listitem"; 258 + const ENCODING: &'static str = "application/json"; 259 + type Output<'de> = ListitemGetRecordOutput<'de>; 260 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 261 + } 262 + 263 + impl jacquard_common::types::collection::Collection for ListitemRecord { 264 + const NSID: &'static str = "sh.weaver.graph.listitem"; 265 + type Record = ListitemRecord; 266 + } 267 + 268 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Listitem<'a> { 269 + fn nsid() -> &'static str { 270 + "sh.weaver.graph.listitem" 271 + } 272 + fn def_name() -> &'static str { 273 + "main" 274 + } 275 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 276 + lexicon_doc_sh_weaver_graph_listitem() 277 + } 278 + fn validate( 279 + &self, 280 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 281 + Ok(()) 282 + } 283 + } 284 + 285 + fn lexicon_doc_sh_weaver_graph_listitem() -> ::jacquard_lexicon::lexicon::LexiconDoc< 286 + 'static, 287 + > { 288 + ::jacquard_lexicon::lexicon::LexiconDoc { 289 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 290 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.listitem"), 291 + revision: None, 292 + description: None, 293 + defs: { 294 + let mut map = ::std::collections::BTreeMap::new(); 295 + map.insert( 296 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 297 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 298 + description: Some( 299 + ::jacquard_common::CowStr::new_static("An item in a list."), 300 + ), 301 + key: Some(::jacquard_common::CowStr::new_static("tid")), 302 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 303 + description: None, 304 + required: Some( 305 + vec![ 306 + ::jacquard_common::smol_str::SmolStr::new_static("subject"), 307 + ::jacquard_common::smol_str::SmolStr::new_static("list"), 308 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 309 + ], 310 + ), 311 + nullable: None, 312 + properties: { 313 + #[allow(unused_mut)] 314 + let mut map = ::std::collections::BTreeMap::new(); 315 + map.insert( 316 + ::jacquard_common::smol_str::SmolStr::new_static( 317 + "createdAt", 318 + ), 319 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 320 + description: None, 321 + format: Some( 322 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 323 + ), 324 + default: None, 325 + min_length: None, 326 + max_length: None, 327 + min_graphemes: None, 328 + max_graphemes: None, 329 + r#enum: None, 330 + r#const: None, 331 + known_values: None, 332 + }), 333 + ); 334 + map.insert( 335 + ::jacquard_common::smol_str::SmolStr::new_static("list"), 336 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 337 + description: Some( 338 + ::jacquard_common::CowStr::new_static( 339 + "Reference to the list record.", 340 + ), 341 + ), 342 + format: Some( 343 + ::jacquard_lexicon::lexicon::LexStringFormat::AtUri, 344 + ), 345 + default: None, 346 + min_length: None, 347 + max_length: None, 348 + min_graphemes: None, 349 + max_graphemes: None, 350 + r#enum: None, 351 + r#const: None, 352 + known_values: None, 353 + }), 354 + ); 355 + map.insert( 356 + ::jacquard_common::smol_str::SmolStr::new_static("subject"), 357 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 358 + description: None, 359 + r#ref: ::jacquard_common::CowStr::new_static( 360 + "com.atproto.repo.strongRef", 361 + ), 362 + }), 363 + ); 364 + map 365 + }, 366 + }), 367 + }), 368 + ); 369 + map 370 + }, 371 + } 372 + }
+326
crates/weaver-api/src/sh_weaver/graph/subscribe.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.subscribe 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// Request to subscribe to a notebook. Requires acceptance to be active. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct Subscribe<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// URI of the notebook to subscribe to. 23 + #[serde(borrow)] 24 + pub notebook: jacquard_common::types::string::AtUri<'a>, 25 + } 26 + 27 + pub mod subscribe_state { 28 + 29 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 30 + #[allow(unused)] 31 + use ::core::marker::PhantomData; 32 + mod sealed { 33 + pub trait Sealed {} 34 + } 35 + /// State trait tracking which required fields have been set 36 + pub trait State: sealed::Sealed { 37 + type Notebook; 38 + type CreatedAt; 39 + } 40 + /// Empty state - all required fields are unset 41 + pub struct Empty(()); 42 + impl sealed::Sealed for Empty {} 43 + impl State for Empty { 44 + type Notebook = Unset; 45 + type CreatedAt = Unset; 46 + } 47 + ///State transition - sets the `notebook` field to Set 48 + pub struct SetNotebook<S: State = Empty>(PhantomData<fn() -> S>); 49 + impl<S: State> sealed::Sealed for SetNotebook<S> {} 50 + impl<S: State> State for SetNotebook<S> { 51 + type Notebook = Set<members::notebook>; 52 + type CreatedAt = S::CreatedAt; 53 + } 54 + ///State transition - sets the `created_at` field to Set 55 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 56 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 57 + impl<S: State> State for SetCreatedAt<S> { 58 + type Notebook = S::Notebook; 59 + type CreatedAt = Set<members::created_at>; 60 + } 61 + /// Marker types for field names 62 + #[allow(non_camel_case_types)] 63 + pub mod members { 64 + ///Marker type for the `notebook` field 65 + pub struct notebook(()); 66 + ///Marker type for the `created_at` field 67 + pub struct created_at(()); 68 + } 69 + } 70 + 71 + /// Builder for constructing an instance of this type 72 + pub struct SubscribeBuilder<'a, S: subscribe_state::State> { 73 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 74 + __unsafe_private_named: ( 75 + ::core::option::Option<jacquard_common::types::string::Datetime>, 76 + ::core::option::Option<jacquard_common::types::string::AtUri<'a>>, 77 + ), 78 + _phantom: ::core::marker::PhantomData<&'a ()>, 79 + } 80 + 81 + impl<'a> Subscribe<'a> { 82 + /// Create a new builder for this type 83 + pub fn new() -> SubscribeBuilder<'a, subscribe_state::Empty> { 84 + SubscribeBuilder::new() 85 + } 86 + } 87 + 88 + impl<'a> SubscribeBuilder<'a, subscribe_state::Empty> { 89 + /// Create a new builder with all fields unset 90 + pub fn new() -> Self { 91 + SubscribeBuilder { 92 + _phantom_state: ::core::marker::PhantomData, 93 + __unsafe_private_named: (None, None), 94 + _phantom: ::core::marker::PhantomData, 95 + } 96 + } 97 + } 98 + 99 + impl<'a, S> SubscribeBuilder<'a, S> 100 + where 101 + S: subscribe_state::State, 102 + S::CreatedAt: subscribe_state::IsUnset, 103 + { 104 + /// Set the `createdAt` field (required) 105 + pub fn created_at( 106 + mut self, 107 + value: impl Into<jacquard_common::types::string::Datetime>, 108 + ) -> SubscribeBuilder<'a, subscribe_state::SetCreatedAt<S>> { 109 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 110 + SubscribeBuilder { 111 + _phantom_state: ::core::marker::PhantomData, 112 + __unsafe_private_named: self.__unsafe_private_named, 113 + _phantom: ::core::marker::PhantomData, 114 + } 115 + } 116 + } 117 + 118 + impl<'a, S> SubscribeBuilder<'a, S> 119 + where 120 + S: subscribe_state::State, 121 + S::Notebook: subscribe_state::IsUnset, 122 + { 123 + /// Set the `notebook` field (required) 124 + pub fn notebook( 125 + mut self, 126 + value: impl Into<jacquard_common::types::string::AtUri<'a>>, 127 + ) -> SubscribeBuilder<'a, subscribe_state::SetNotebook<S>> { 128 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 129 + SubscribeBuilder { 130 + _phantom_state: ::core::marker::PhantomData, 131 + __unsafe_private_named: self.__unsafe_private_named, 132 + _phantom: ::core::marker::PhantomData, 133 + } 134 + } 135 + } 136 + 137 + impl<'a, S> SubscribeBuilder<'a, S> 138 + where 139 + S: subscribe_state::State, 140 + S::Notebook: subscribe_state::IsSet, 141 + S::CreatedAt: subscribe_state::IsSet, 142 + { 143 + /// Build the final struct 144 + pub fn build(self) -> Subscribe<'a> { 145 + Subscribe { 146 + created_at: self.__unsafe_private_named.0.unwrap(), 147 + notebook: self.__unsafe_private_named.1.unwrap(), 148 + extra_data: Default::default(), 149 + } 150 + } 151 + /// Build the final struct with custom extra_data 152 + pub fn build_with_data( 153 + self, 154 + extra_data: std::collections::BTreeMap< 155 + jacquard_common::smol_str::SmolStr, 156 + jacquard_common::types::value::Data<'a>, 157 + >, 158 + ) -> Subscribe<'a> { 159 + Subscribe { 160 + created_at: self.__unsafe_private_named.0.unwrap(), 161 + notebook: self.__unsafe_private_named.1.unwrap(), 162 + extra_data: Some(extra_data), 163 + } 164 + } 165 + } 166 + 167 + impl<'a> Subscribe<'a> { 168 + pub fn uri( 169 + uri: impl Into<jacquard_common::CowStr<'a>>, 170 + ) -> Result< 171 + jacquard_common::types::uri::RecordUri<'a, SubscribeRecord>, 172 + jacquard_common::types::uri::UriError, 173 + > { 174 + jacquard_common::types::uri::RecordUri::try_from_uri( 175 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 176 + ) 177 + } 178 + } 179 + 180 + /// Typed wrapper for GetRecord response with this collection's record type. 181 + #[derive( 182 + serde::Serialize, 183 + serde::Deserialize, 184 + Debug, 185 + Clone, 186 + PartialEq, 187 + Eq, 188 + jacquard_derive::IntoStatic 189 + )] 190 + #[serde(rename_all = "camelCase")] 191 + pub struct SubscribeGetRecordOutput<'a> { 192 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 193 + #[serde(borrow)] 194 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 195 + #[serde(borrow)] 196 + pub uri: jacquard_common::types::string::AtUri<'a>, 197 + #[serde(borrow)] 198 + pub value: Subscribe<'a>, 199 + } 200 + 201 + impl From<SubscribeGetRecordOutput<'_>> for Subscribe<'_> { 202 + fn from(output: SubscribeGetRecordOutput<'_>) -> Self { 203 + use jacquard_common::IntoStatic; 204 + output.value.into_static() 205 + } 206 + } 207 + 208 + impl jacquard_common::types::collection::Collection for Subscribe<'_> { 209 + const NSID: &'static str = "sh.weaver.graph.subscribe"; 210 + type Record = SubscribeRecord; 211 + } 212 + 213 + /// Marker type for deserializing records from this collection. 214 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 215 + pub struct SubscribeRecord; 216 + impl jacquard_common::xrpc::XrpcResp for SubscribeRecord { 217 + const NSID: &'static str = "sh.weaver.graph.subscribe"; 218 + const ENCODING: &'static str = "application/json"; 219 + type Output<'de> = SubscribeGetRecordOutput<'de>; 220 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 221 + } 222 + 223 + impl jacquard_common::types::collection::Collection for SubscribeRecord { 224 + const NSID: &'static str = "sh.weaver.graph.subscribe"; 225 + type Record = SubscribeRecord; 226 + } 227 + 228 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subscribe<'a> { 229 + fn nsid() -> &'static str { 230 + "sh.weaver.graph.subscribe" 231 + } 232 + fn def_name() -> &'static str { 233 + "main" 234 + } 235 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 236 + lexicon_doc_sh_weaver_graph_subscribe() 237 + } 238 + fn validate( 239 + &self, 240 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 241 + Ok(()) 242 + } 243 + } 244 + 245 + fn lexicon_doc_sh_weaver_graph_subscribe() -> ::jacquard_lexicon::lexicon::LexiconDoc< 246 + 'static, 247 + > { 248 + ::jacquard_lexicon::lexicon::LexiconDoc { 249 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 250 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.subscribe"), 251 + revision: None, 252 + description: None, 253 + defs: { 254 + let mut map = ::std::collections::BTreeMap::new(); 255 + map.insert( 256 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 257 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 258 + description: Some( 259 + ::jacquard_common::CowStr::new_static( 260 + "Request to subscribe to a notebook. Requires acceptance to be active.", 261 + ), 262 + ), 263 + key: Some(::jacquard_common::CowStr::new_static("tid")), 264 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 265 + description: None, 266 + required: Some( 267 + vec![ 268 + ::jacquard_common::smol_str::SmolStr::new_static("notebook"), 269 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 270 + ], 271 + ), 272 + nullable: None, 273 + properties: { 274 + #[allow(unused_mut)] 275 + let mut map = ::std::collections::BTreeMap::new(); 276 + map.insert( 277 + ::jacquard_common::smol_str::SmolStr::new_static( 278 + "createdAt", 279 + ), 280 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 281 + description: None, 282 + format: Some( 283 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 284 + ), 285 + default: None, 286 + min_length: None, 287 + max_length: None, 288 + min_graphemes: None, 289 + max_graphemes: None, 290 + r#enum: None, 291 + r#const: None, 292 + known_values: None, 293 + }), 294 + ); 295 + map.insert( 296 + ::jacquard_common::smol_str::SmolStr::new_static( 297 + "notebook", 298 + ), 299 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 300 + description: Some( 301 + ::jacquard_common::CowStr::new_static( 302 + "URI of the notebook to subscribe to.", 303 + ), 304 + ), 305 + format: Some( 306 + ::jacquard_lexicon::lexicon::LexStringFormat::AtUri, 307 + ), 308 + default: None, 309 + min_length: None, 310 + max_length: None, 311 + min_graphemes: None, 312 + max_graphemes: None, 313 + r#enum: None, 314 + r#const: None, 315 + known_values: None, 316 + }), 317 + ); 318 + map 319 + }, 320 + }), 321 + }), 322 + ); 323 + map 324 + }, 325 + } 326 + }
+314
crates/weaver-api/src/sh_weaver/graph/subscribe_accept.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.graph.subscribeAccept 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + /// Acceptance of a subscription request. 9 + #[jacquard_derive::lexicon] 10 + #[derive( 11 + serde::Serialize, 12 + serde::Deserialize, 13 + Debug, 14 + Clone, 15 + PartialEq, 16 + Eq, 17 + jacquard_derive::IntoStatic 18 + )] 19 + #[serde(rename_all = "camelCase")] 20 + pub struct SubscribeAccept<'a> { 21 + pub created_at: jacquard_common::types::string::Datetime, 22 + /// Reference to the subscribe record being accepted. 23 + #[serde(borrow)] 24 + pub subscribe: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 25 + } 26 + 27 + pub mod subscribe_accept_state { 28 + 29 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 30 + #[allow(unused)] 31 + use ::core::marker::PhantomData; 32 + mod sealed { 33 + pub trait Sealed {} 34 + } 35 + /// State trait tracking which required fields have been set 36 + pub trait State: sealed::Sealed { 37 + type Subscribe; 38 + type CreatedAt; 39 + } 40 + /// Empty state - all required fields are unset 41 + pub struct Empty(()); 42 + impl sealed::Sealed for Empty {} 43 + impl State for Empty { 44 + type Subscribe = Unset; 45 + type CreatedAt = Unset; 46 + } 47 + ///State transition - sets the `subscribe` field to Set 48 + pub struct SetSubscribe<S: State = Empty>(PhantomData<fn() -> S>); 49 + impl<S: State> sealed::Sealed for SetSubscribe<S> {} 50 + impl<S: State> State for SetSubscribe<S> { 51 + type Subscribe = Set<members::subscribe>; 52 + type CreatedAt = S::CreatedAt; 53 + } 54 + ///State transition - sets the `created_at` field to Set 55 + pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 56 + impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 57 + impl<S: State> State for SetCreatedAt<S> { 58 + type Subscribe = S::Subscribe; 59 + type CreatedAt = Set<members::created_at>; 60 + } 61 + /// Marker types for field names 62 + #[allow(non_camel_case_types)] 63 + pub mod members { 64 + ///Marker type for the `subscribe` field 65 + pub struct subscribe(()); 66 + ///Marker type for the `created_at` field 67 + pub struct created_at(()); 68 + } 69 + } 70 + 71 + /// Builder for constructing an instance of this type 72 + pub struct SubscribeAcceptBuilder<'a, S: subscribe_accept_state::State> { 73 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 74 + __unsafe_private_named: ( 75 + ::core::option::Option<jacquard_common::types::string::Datetime>, 76 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 77 + ), 78 + _phantom: ::core::marker::PhantomData<&'a ()>, 79 + } 80 + 81 + impl<'a> SubscribeAccept<'a> { 82 + /// Create a new builder for this type 83 + pub fn new() -> SubscribeAcceptBuilder<'a, subscribe_accept_state::Empty> { 84 + SubscribeAcceptBuilder::new() 85 + } 86 + } 87 + 88 + impl<'a> SubscribeAcceptBuilder<'a, subscribe_accept_state::Empty> { 89 + /// Create a new builder with all fields unset 90 + pub fn new() -> Self { 91 + SubscribeAcceptBuilder { 92 + _phantom_state: ::core::marker::PhantomData, 93 + __unsafe_private_named: (None, None), 94 + _phantom: ::core::marker::PhantomData, 95 + } 96 + } 97 + } 98 + 99 + impl<'a, S> SubscribeAcceptBuilder<'a, S> 100 + where 101 + S: subscribe_accept_state::State, 102 + S::CreatedAt: subscribe_accept_state::IsUnset, 103 + { 104 + /// Set the `createdAt` field (required) 105 + pub fn created_at( 106 + mut self, 107 + value: impl Into<jacquard_common::types::string::Datetime>, 108 + ) -> SubscribeAcceptBuilder<'a, subscribe_accept_state::SetCreatedAt<S>> { 109 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 110 + SubscribeAcceptBuilder { 111 + _phantom_state: ::core::marker::PhantomData, 112 + __unsafe_private_named: self.__unsafe_private_named, 113 + _phantom: ::core::marker::PhantomData, 114 + } 115 + } 116 + } 117 + 118 + impl<'a, S> SubscribeAcceptBuilder<'a, S> 119 + where 120 + S: subscribe_accept_state::State, 121 + S::Subscribe: subscribe_accept_state::IsUnset, 122 + { 123 + /// Set the `subscribe` field (required) 124 + pub fn subscribe( 125 + mut self, 126 + value: impl Into<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 127 + ) -> SubscribeAcceptBuilder<'a, subscribe_accept_state::SetSubscribe<S>> { 128 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 129 + SubscribeAcceptBuilder { 130 + _phantom_state: ::core::marker::PhantomData, 131 + __unsafe_private_named: self.__unsafe_private_named, 132 + _phantom: ::core::marker::PhantomData, 133 + } 134 + } 135 + } 136 + 137 + impl<'a, S> SubscribeAcceptBuilder<'a, S> 138 + where 139 + S: subscribe_accept_state::State, 140 + S::Subscribe: subscribe_accept_state::IsSet, 141 + S::CreatedAt: subscribe_accept_state::IsSet, 142 + { 143 + /// Build the final struct 144 + pub fn build(self) -> SubscribeAccept<'a> { 145 + SubscribeAccept { 146 + created_at: self.__unsafe_private_named.0.unwrap(), 147 + subscribe: self.__unsafe_private_named.1.unwrap(), 148 + extra_data: Default::default(), 149 + } 150 + } 151 + /// Build the final struct with custom extra_data 152 + pub fn build_with_data( 153 + self, 154 + extra_data: std::collections::BTreeMap< 155 + jacquard_common::smol_str::SmolStr, 156 + jacquard_common::types::value::Data<'a>, 157 + >, 158 + ) -> SubscribeAccept<'a> { 159 + SubscribeAccept { 160 + created_at: self.__unsafe_private_named.0.unwrap(), 161 + subscribe: self.__unsafe_private_named.1.unwrap(), 162 + extra_data: Some(extra_data), 163 + } 164 + } 165 + } 166 + 167 + impl<'a> SubscribeAccept<'a> { 168 + pub fn uri( 169 + uri: impl Into<jacquard_common::CowStr<'a>>, 170 + ) -> Result< 171 + jacquard_common::types::uri::RecordUri<'a, SubscribeAcceptRecord>, 172 + jacquard_common::types::uri::UriError, 173 + > { 174 + jacquard_common::types::uri::RecordUri::try_from_uri( 175 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 176 + ) 177 + } 178 + } 179 + 180 + /// Typed wrapper for GetRecord response with this collection's record type. 181 + #[derive( 182 + serde::Serialize, 183 + serde::Deserialize, 184 + Debug, 185 + Clone, 186 + PartialEq, 187 + Eq, 188 + jacquard_derive::IntoStatic 189 + )] 190 + #[serde(rename_all = "camelCase")] 191 + pub struct SubscribeAcceptGetRecordOutput<'a> { 192 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 193 + #[serde(borrow)] 194 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 195 + #[serde(borrow)] 196 + pub uri: jacquard_common::types::string::AtUri<'a>, 197 + #[serde(borrow)] 198 + pub value: SubscribeAccept<'a>, 199 + } 200 + 201 + impl From<SubscribeAcceptGetRecordOutput<'_>> for SubscribeAccept<'_> { 202 + fn from(output: SubscribeAcceptGetRecordOutput<'_>) -> Self { 203 + use jacquard_common::IntoStatic; 204 + output.value.into_static() 205 + } 206 + } 207 + 208 + impl jacquard_common::types::collection::Collection for SubscribeAccept<'_> { 209 + const NSID: &'static str = "sh.weaver.graph.subscribeAccept"; 210 + type Record = SubscribeAcceptRecord; 211 + } 212 + 213 + /// Marker type for deserializing records from this collection. 214 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 215 + pub struct SubscribeAcceptRecord; 216 + impl jacquard_common::xrpc::XrpcResp for SubscribeAcceptRecord { 217 + const NSID: &'static str = "sh.weaver.graph.subscribeAccept"; 218 + const ENCODING: &'static str = "application/json"; 219 + type Output<'de> = SubscribeAcceptGetRecordOutput<'de>; 220 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 221 + } 222 + 223 + impl jacquard_common::types::collection::Collection for SubscribeAcceptRecord { 224 + const NSID: &'static str = "sh.weaver.graph.subscribeAccept"; 225 + type Record = SubscribeAcceptRecord; 226 + } 227 + 228 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for SubscribeAccept<'a> { 229 + fn nsid() -> &'static str { 230 + "sh.weaver.graph.subscribeAccept" 231 + } 232 + fn def_name() -> &'static str { 233 + "main" 234 + } 235 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 236 + lexicon_doc_sh_weaver_graph_subscribeAccept() 237 + } 238 + fn validate( 239 + &self, 240 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 241 + Ok(()) 242 + } 243 + } 244 + 245 + fn lexicon_doc_sh_weaver_graph_subscribeAccept() -> ::jacquard_lexicon::lexicon::LexiconDoc< 246 + 'static, 247 + > { 248 + ::jacquard_lexicon::lexicon::LexiconDoc { 249 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 250 + id: ::jacquard_common::CowStr::new_static("sh.weaver.graph.subscribeAccept"), 251 + revision: None, 252 + description: None, 253 + defs: { 254 + let mut map = ::std::collections::BTreeMap::new(); 255 + map.insert( 256 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 257 + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 258 + description: Some( 259 + ::jacquard_common::CowStr::new_static( 260 + "Acceptance of a subscription request.", 261 + ), 262 + ), 263 + key: Some(::jacquard_common::CowStr::new_static("tid")), 264 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 265 + description: None, 266 + required: Some( 267 + vec![ 268 + ::jacquard_common::smol_str::SmolStr::new_static("subscribe"), 269 + ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 270 + ], 271 + ), 272 + nullable: None, 273 + properties: { 274 + #[allow(unused_mut)] 275 + let mut map = ::std::collections::BTreeMap::new(); 276 + map.insert( 277 + ::jacquard_common::smol_str::SmolStr::new_static( 278 + "createdAt", 279 + ), 280 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 281 + description: None, 282 + format: Some( 283 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 284 + ), 285 + default: None, 286 + min_length: None, 287 + max_length: None, 288 + min_graphemes: None, 289 + max_graphemes: None, 290 + r#enum: None, 291 + r#const: None, 292 + known_values: None, 293 + }), 294 + ); 295 + map.insert( 296 + ::jacquard_common::smol_str::SmolStr::new_static( 297 + "subscribe", 298 + ), 299 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 300 + description: None, 301 + r#ref: ::jacquard_common::CowStr::new_static( 302 + "com.atproto.repo.strongRef", 303 + ), 304 + }), 305 + ); 306 + map 307 + }, 308 + }), 309 + }), 310 + ); 311 + map 312 + }, 313 + } 314 + }
+4
crates/weaver-api/src/sh_weaver/notebook.rs
··· 10 10 pub mod chapter; 11 11 pub mod colour_scheme; 12 12 pub mod entry; 13 + pub mod get_entry; 14 + pub mod get_entry_by_title; 15 + pub mod get_notebook; 16 + pub mod get_notebook_by_title; 13 17 pub mod page; 14 18 pub mod theme; 15 19
+49 -1
crates/weaver-api/src/sh_weaver/notebook/book.rs
··· 37 37 #[serde(skip_serializing_if = "std::option::Option::is_none")] 38 38 #[serde(borrow)] 39 39 pub title: std::option::Option<crate::sh_weaver::notebook::Title<'a>>, 40 + /// Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios. 41 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 42 + pub updated_at: std::option::Option<jacquard_common::types::string::Datetime>, 40 43 } 41 44 42 45 pub mod book_state { ··· 94 97 ::core::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 95 98 ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 96 99 ::core::option::Option<crate::sh_weaver::notebook::Title<'a>>, 100 + ::core::option::Option<jacquard_common::types::string::Datetime>, 97 101 ), 98 102 _phantom: ::core::marker::PhantomData<&'a ()>, 99 103 } ··· 110 114 pub fn new() -> Self { 111 115 BookBuilder { 112 116 _phantom_state: ::core::marker::PhantomData, 113 - __unsafe_private_named: (None, None, None, None, None, None, None), 117 + __unsafe_private_named: (None, None, None, None, None, None, None, None), 114 118 _phantom: ::core::marker::PhantomData, 115 119 } 116 120 } ··· 245 249 value: Option<crate::sh_weaver::notebook::Title<'a>>, 246 250 ) -> Self { 247 251 self.__unsafe_private_named.6 = value; 252 + self 253 + } 254 + } 255 + 256 + impl<'a, S: book_state::State> BookBuilder<'a, S> { 257 + /// Set the `updatedAt` field (optional) 258 + pub fn updated_at( 259 + mut self, 260 + value: impl Into<Option<jacquard_common::types::string::Datetime>>, 261 + ) -> Self { 262 + self.__unsafe_private_named.7 = value.into(); 263 + self 264 + } 265 + /// Set the `updatedAt` field to an Option value (optional) 266 + pub fn maybe_updated_at( 267 + mut self, 268 + value: Option<jacquard_common::types::string::Datetime>, 269 + ) -> Self { 270 + self.__unsafe_private_named.7 = value; 248 271 self 249 272 } 250 273 } ··· 265 288 tags: self.__unsafe_private_named.4, 266 289 theme: self.__unsafe_private_named.5, 267 290 title: self.__unsafe_private_named.6, 291 + updated_at: self.__unsafe_private_named.7, 268 292 extra_data: Default::default(), 269 293 } 270 294 } ··· 284 308 tags: self.__unsafe_private_named.4, 285 309 theme: self.__unsafe_private_named.5, 286 310 title: self.__unsafe_private_named.6, 311 + updated_at: self.__unsafe_private_named.7, 287 312 extra_data: Some(extra_data), 288 313 } 289 314 } ··· 485 510 r#ref: ::jacquard_common::CowStr::new_static( 486 511 "sh.weaver.notebook.defs#title", 487 512 ), 513 + }), 514 + ); 515 + map.insert( 516 + ::jacquard_common::smol_str::SmolStr::new_static( 517 + "updatedAt", 518 + ), 519 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 520 + description: Some( 521 + ::jacquard_common::CowStr::new_static( 522 + "Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios.", 523 + ), 524 + ), 525 + format: Some( 526 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 527 + ), 528 + default: None, 529 + min_length: None, 530 + max_length: None, 531 + min_graphemes: None, 532 + max_graphemes: None, 533 + r#enum: None, 534 + r#const: None, 535 + known_values: None, 488 536 }), 489 537 ); 490 538 map
+49 -1
crates/weaver-api/src/sh_weaver/notebook/entry.rs
··· 34 34 pub tags: std::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 35 35 #[serde(borrow)] 36 36 pub title: crate::sh_weaver::notebook::Title<'a>, 37 + /// Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios. 38 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 + pub updated_at: std::option::Option<jacquard_common::types::string::Datetime>, 37 40 } 38 41 39 42 pub mod entry_state { ··· 120 123 ::core::option::Option<crate::sh_weaver::notebook::Path<'a>>, 121 124 ::core::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 122 125 ::core::option::Option<crate::sh_weaver::notebook::Title<'a>>, 126 + ::core::option::Option<jacquard_common::types::string::Datetime>, 123 127 ), 124 128 _phantom: ::core::marker::PhantomData<&'a ()>, 125 129 } ··· 136 140 pub fn new() -> Self { 137 141 EntryBuilder { 138 142 _phantom_state: ::core::marker::PhantomData, 139 - __unsafe_private_named: (None, None, None, None, None, None), 143 + __unsafe_private_named: (None, None, None, None, None, None, None), 140 144 _phantom: ::core::marker::PhantomData, 141 145 } 142 146 } ··· 247 251 __unsafe_private_named: self.__unsafe_private_named, 248 252 _phantom: ::core::marker::PhantomData, 249 253 } 254 + } 255 + } 256 + 257 + impl<'a, S: entry_state::State> EntryBuilder<'a, S> { 258 + /// Set the `updatedAt` field (optional) 259 + pub fn updated_at( 260 + mut self, 261 + value: impl Into<Option<jacquard_common::types::string::Datetime>>, 262 + ) -> Self { 263 + self.__unsafe_private_named.6 = value.into(); 264 + self 265 + } 266 + /// Set the `updatedAt` field to an Option value (optional) 267 + pub fn maybe_updated_at( 268 + mut self, 269 + value: Option<jacquard_common::types::string::Datetime>, 270 + ) -> Self { 271 + self.__unsafe_private_named.6 = value; 272 + self 250 273 } 251 274 } 252 275 ··· 267 290 path: self.__unsafe_private_named.3.unwrap(), 268 291 tags: self.__unsafe_private_named.4, 269 292 title: self.__unsafe_private_named.5.unwrap(), 293 + updated_at: self.__unsafe_private_named.6, 270 294 extra_data: Default::default(), 271 295 } 272 296 } ··· 285 309 path: self.__unsafe_private_named.3.unwrap(), 286 310 tags: self.__unsafe_private_named.4, 287 311 title: self.__unsafe_private_named.5.unwrap(), 312 + updated_at: self.__unsafe_private_named.6, 288 313 extra_data: Some(extra_data), 289 314 } 290 315 } ··· 505 530 r#ref: ::jacquard_common::CowStr::new_static( 506 531 "sh.weaver.notebook.defs#title", 507 532 ), 533 + }), 534 + ); 535 + map.insert( 536 + ::jacquard_common::smol_str::SmolStr::new_static( 537 + "updatedAt", 538 + ), 539 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 540 + description: Some( 541 + ::jacquard_common::CowStr::new_static( 542 + "Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios.", 543 + ), 544 + ), 545 + format: Some( 546 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 547 + ), 548 + default: None, 549 + min_length: None, 550 + max_length: None, 551 + min_graphemes: None, 552 + max_graphemes: None, 553 + r#enum: None, 554 + r#const: None, 555 + known_values: None, 508 556 }), 509 557 ); 510 558 map
+216
crates/weaver-api/src/sh_weaver/notebook/get_entry.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.notebook.getEntry 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[derive( 9 + serde::Serialize, 10 + serde::Deserialize, 11 + Debug, 12 + Clone, 13 + PartialEq, 14 + Eq, 15 + jacquard_derive::IntoStatic 16 + )] 17 + #[serde(rename_all = "camelCase")] 18 + pub struct GetEntry<'a> { 19 + ///(default: 0, min: 0) 20 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 + pub index: std::option::Option<i64>, 22 + #[serde(borrow)] 23 + pub notebook: jacquard_common::types::string::AtUri<'a>, 24 + } 25 + 26 + pub mod get_entry_state { 27 + 28 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 29 + #[allow(unused)] 30 + use ::core::marker::PhantomData; 31 + mod sealed { 32 + pub trait Sealed {} 33 + } 34 + /// State trait tracking which required fields have been set 35 + pub trait State: sealed::Sealed { 36 + type Notebook; 37 + } 38 + /// Empty state - all required fields are unset 39 + pub struct Empty(()); 40 + impl sealed::Sealed for Empty {} 41 + impl State for Empty { 42 + type Notebook = Unset; 43 + } 44 + ///State transition - sets the `notebook` field to Set 45 + pub struct SetNotebook<S: State = Empty>(PhantomData<fn() -> S>); 46 + impl<S: State> sealed::Sealed for SetNotebook<S> {} 47 + impl<S: State> State for SetNotebook<S> { 48 + type Notebook = Set<members::notebook>; 49 + } 50 + /// Marker types for field names 51 + #[allow(non_camel_case_types)] 52 + pub mod members { 53 + ///Marker type for the `notebook` field 54 + pub struct notebook(()); 55 + } 56 + } 57 + 58 + /// Builder for constructing an instance of this type 59 + pub struct GetEntryBuilder<'a, S: get_entry_state::State> { 60 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 61 + __unsafe_private_named: ( 62 + ::core::option::Option<i64>, 63 + ::core::option::Option<jacquard_common::types::string::AtUri<'a>>, 64 + ), 65 + _phantom: ::core::marker::PhantomData<&'a ()>, 66 + } 67 + 68 + impl<'a> GetEntry<'a> { 69 + /// Create a new builder for this type 70 + pub fn new() -> GetEntryBuilder<'a, get_entry_state::Empty> { 71 + GetEntryBuilder::new() 72 + } 73 + } 74 + 75 + impl<'a> GetEntryBuilder<'a, get_entry_state::Empty> { 76 + /// Create a new builder with all fields unset 77 + pub fn new() -> Self { 78 + GetEntryBuilder { 79 + _phantom_state: ::core::marker::PhantomData, 80 + __unsafe_private_named: (None, None), 81 + _phantom: ::core::marker::PhantomData, 82 + } 83 + } 84 + } 85 + 86 + impl<'a, S: get_entry_state::State> GetEntryBuilder<'a, S> { 87 + /// Set the `index` field (optional) 88 + pub fn index(mut self, value: impl Into<Option<i64>>) -> Self { 89 + self.__unsafe_private_named.0 = value.into(); 90 + self 91 + } 92 + /// Set the `index` field to an Option value (optional) 93 + pub fn maybe_index(mut self, value: Option<i64>) -> Self { 94 + self.__unsafe_private_named.0 = value; 95 + self 96 + } 97 + } 98 + 99 + impl<'a, S> GetEntryBuilder<'a, S> 100 + where 101 + S: get_entry_state::State, 102 + S::Notebook: get_entry_state::IsUnset, 103 + { 104 + /// Set the `notebook` field (required) 105 + pub fn notebook( 106 + mut self, 107 + value: impl Into<jacquard_common::types::string::AtUri<'a>>, 108 + ) -> GetEntryBuilder<'a, get_entry_state::SetNotebook<S>> { 109 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 110 + GetEntryBuilder { 111 + _phantom_state: ::core::marker::PhantomData, 112 + __unsafe_private_named: self.__unsafe_private_named, 113 + _phantom: ::core::marker::PhantomData, 114 + } 115 + } 116 + } 117 + 118 + impl<'a, S> GetEntryBuilder<'a, S> 119 + where 120 + S: get_entry_state::State, 121 + S::Notebook: get_entry_state::IsSet, 122 + { 123 + /// Build the final struct 124 + pub fn build(self) -> GetEntry<'a> { 125 + GetEntry { 126 + index: self.__unsafe_private_named.0, 127 + notebook: self.__unsafe_private_named.1.unwrap(), 128 + } 129 + } 130 + } 131 + 132 + #[jacquard_derive::lexicon] 133 + #[derive( 134 + serde::Serialize, 135 + serde::Deserialize, 136 + Debug, 137 + Clone, 138 + PartialEq, 139 + Eq, 140 + jacquard_derive::IntoStatic 141 + )] 142 + #[serde(rename_all = "camelCase")] 143 + pub struct GetEntryOutput<'a> { 144 + #[serde(flatten)] 145 + #[serde(borrow)] 146 + pub value: crate::sh_weaver::notebook::BookEntryView<'a>, 147 + } 148 + 149 + #[jacquard_derive::open_union] 150 + #[derive( 151 + serde::Serialize, 152 + serde::Deserialize, 153 + Debug, 154 + Clone, 155 + PartialEq, 156 + Eq, 157 + thiserror::Error, 158 + miette::Diagnostic, 159 + jacquard_derive::IntoStatic 160 + )] 161 + #[serde(tag = "error", content = "message")] 162 + #[serde(bound(deserialize = "'de: 'a"))] 163 + pub enum GetEntryError<'a> { 164 + #[serde(rename = "NotebookNotFound")] 165 + NotebookNotFound(std::option::Option<String>), 166 + #[serde(rename = "EntryNotFound")] 167 + EntryNotFound(std::option::Option<String>), 168 + } 169 + 170 + impl std::fmt::Display for GetEntryError<'_> { 171 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 172 + match self { 173 + Self::NotebookNotFound(msg) => { 174 + write!(f, "NotebookNotFound")?; 175 + if let Some(msg) = msg { 176 + write!(f, ": {}", msg)?; 177 + } 178 + Ok(()) 179 + } 180 + Self::EntryNotFound(msg) => { 181 + write!(f, "EntryNotFound")?; 182 + if let Some(msg) = msg { 183 + write!(f, ": {}", msg)?; 184 + } 185 + Ok(()) 186 + } 187 + Self::Unknown(err) => write!(f, "Unknown error: {:?}", err), 188 + } 189 + } 190 + } 191 + 192 + /// Response type for 193 + ///sh.weaver.notebook.getEntry 194 + pub struct GetEntryResponse; 195 + impl jacquard_common::xrpc::XrpcResp for GetEntryResponse { 196 + const NSID: &'static str = "sh.weaver.notebook.getEntry"; 197 + const ENCODING: &'static str = "application/json"; 198 + type Output<'de> = GetEntryOutput<'de>; 199 + type Err<'de> = GetEntryError<'de>; 200 + } 201 + 202 + impl<'a> jacquard_common::xrpc::XrpcRequest for GetEntry<'a> { 203 + const NSID: &'static str = "sh.weaver.notebook.getEntry"; 204 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 205 + type Response = GetEntryResponse; 206 + } 207 + 208 + /// Endpoint type for 209 + ///sh.weaver.notebook.getEntry 210 + pub struct GetEntryRequest; 211 + impl jacquard_common::xrpc::XrpcEndpoint for GetEntryRequest { 212 + const PATH: &'static str = "/xrpc/sh.weaver.notebook.getEntry"; 213 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 214 + type Request<'de> = GetEntry<'de>; 215 + type Response = GetEntryResponse; 216 + }
+236
crates/weaver-api/src/sh_weaver/notebook/get_entry_by_title.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.notebook.getEntryByTitle 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[derive( 9 + serde::Serialize, 10 + serde::Deserialize, 11 + Debug, 12 + Clone, 13 + PartialEq, 14 + Eq, 15 + jacquard_derive::IntoStatic 16 + )] 17 + #[serde(rename_all = "camelCase")] 18 + pub struct GetEntryByTitle<'a> { 19 + #[serde(borrow)] 20 + pub notebook: jacquard_common::types::string::AtUri<'a>, 21 + #[serde(borrow)] 22 + pub title: jacquard_common::CowStr<'a>, 23 + } 24 + 25 + pub mod get_entry_by_title_state { 26 + 27 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 28 + #[allow(unused)] 29 + use ::core::marker::PhantomData; 30 + mod sealed { 31 + pub trait Sealed {} 32 + } 33 + /// State trait tracking which required fields have been set 34 + pub trait State: sealed::Sealed { 35 + type Notebook; 36 + type Title; 37 + } 38 + /// Empty state - all required fields are unset 39 + pub struct Empty(()); 40 + impl sealed::Sealed for Empty {} 41 + impl State for Empty { 42 + type Notebook = Unset; 43 + type Title = Unset; 44 + } 45 + ///State transition - sets the `notebook` field to Set 46 + pub struct SetNotebook<S: State = Empty>(PhantomData<fn() -> S>); 47 + impl<S: State> sealed::Sealed for SetNotebook<S> {} 48 + impl<S: State> State for SetNotebook<S> { 49 + type Notebook = Set<members::notebook>; 50 + type Title = S::Title; 51 + } 52 + ///State transition - sets the `title` field to Set 53 + pub struct SetTitle<S: State = Empty>(PhantomData<fn() -> S>); 54 + impl<S: State> sealed::Sealed for SetTitle<S> {} 55 + impl<S: State> State for SetTitle<S> { 56 + type Notebook = S::Notebook; 57 + type Title = Set<members::title>; 58 + } 59 + /// Marker types for field names 60 + #[allow(non_camel_case_types)] 61 + pub mod members { 62 + ///Marker type for the `notebook` field 63 + pub struct notebook(()); 64 + ///Marker type for the `title` field 65 + pub struct title(()); 66 + } 67 + } 68 + 69 + /// Builder for constructing an instance of this type 70 + pub struct GetEntryByTitleBuilder<'a, S: get_entry_by_title_state::State> { 71 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 72 + __unsafe_private_named: ( 73 + ::core::option::Option<jacquard_common::types::string::AtUri<'a>>, 74 + ::core::option::Option<jacquard_common::CowStr<'a>>, 75 + ), 76 + _phantom: ::core::marker::PhantomData<&'a ()>, 77 + } 78 + 79 + impl<'a> GetEntryByTitle<'a> { 80 + /// Create a new builder for this type 81 + pub fn new() -> GetEntryByTitleBuilder<'a, get_entry_by_title_state::Empty> { 82 + GetEntryByTitleBuilder::new() 83 + } 84 + } 85 + 86 + impl<'a> GetEntryByTitleBuilder<'a, get_entry_by_title_state::Empty> { 87 + /// Create a new builder with all fields unset 88 + pub fn new() -> Self { 89 + GetEntryByTitleBuilder { 90 + _phantom_state: ::core::marker::PhantomData, 91 + __unsafe_private_named: (None, None), 92 + _phantom: ::core::marker::PhantomData, 93 + } 94 + } 95 + } 96 + 97 + impl<'a, S> GetEntryByTitleBuilder<'a, S> 98 + where 99 + S: get_entry_by_title_state::State, 100 + S::Notebook: get_entry_by_title_state::IsUnset, 101 + { 102 + /// Set the `notebook` field (required) 103 + pub fn notebook( 104 + mut self, 105 + value: impl Into<jacquard_common::types::string::AtUri<'a>>, 106 + ) -> GetEntryByTitleBuilder<'a, get_entry_by_title_state::SetNotebook<S>> { 107 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 108 + GetEntryByTitleBuilder { 109 + _phantom_state: ::core::marker::PhantomData, 110 + __unsafe_private_named: self.__unsafe_private_named, 111 + _phantom: ::core::marker::PhantomData, 112 + } 113 + } 114 + } 115 + 116 + impl<'a, S> GetEntryByTitleBuilder<'a, S> 117 + where 118 + S: get_entry_by_title_state::State, 119 + S::Title: get_entry_by_title_state::IsUnset, 120 + { 121 + /// Set the `title` field (required) 122 + pub fn title( 123 + mut self, 124 + value: impl Into<jacquard_common::CowStr<'a>>, 125 + ) -> GetEntryByTitleBuilder<'a, get_entry_by_title_state::SetTitle<S>> { 126 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 127 + GetEntryByTitleBuilder { 128 + _phantom_state: ::core::marker::PhantomData, 129 + __unsafe_private_named: self.__unsafe_private_named, 130 + _phantom: ::core::marker::PhantomData, 131 + } 132 + } 133 + } 134 + 135 + impl<'a, S> GetEntryByTitleBuilder<'a, S> 136 + where 137 + S: get_entry_by_title_state::State, 138 + S::Notebook: get_entry_by_title_state::IsSet, 139 + S::Title: get_entry_by_title_state::IsSet, 140 + { 141 + /// Build the final struct 142 + pub fn build(self) -> GetEntryByTitle<'a> { 143 + GetEntryByTitle { 144 + notebook: self.__unsafe_private_named.0.unwrap(), 145 + title: self.__unsafe_private_named.1.unwrap(), 146 + } 147 + } 148 + } 149 + 150 + #[jacquard_derive::lexicon] 151 + #[derive( 152 + serde::Serialize, 153 + serde::Deserialize, 154 + Debug, 155 + Clone, 156 + PartialEq, 157 + Eq, 158 + jacquard_derive::IntoStatic 159 + )] 160 + #[serde(rename_all = "camelCase")] 161 + pub struct GetEntryByTitleOutput<'a> { 162 + #[serde(borrow)] 163 + pub entry: crate::sh_weaver::notebook::BookEntryView<'a>, 164 + /// The raw entry record data. 165 + #[serde(borrow)] 166 + pub record: jacquard_common::types::value::Data<'a>, 167 + } 168 + 169 + #[jacquard_derive::open_union] 170 + #[derive( 171 + serde::Serialize, 172 + serde::Deserialize, 173 + Debug, 174 + Clone, 175 + PartialEq, 176 + Eq, 177 + thiserror::Error, 178 + miette::Diagnostic, 179 + jacquard_derive::IntoStatic 180 + )] 181 + #[serde(tag = "error", content = "message")] 182 + #[serde(bound(deserialize = "'de: 'a"))] 183 + pub enum GetEntryByTitleError<'a> { 184 + #[serde(rename = "NotebookNotFound")] 185 + NotebookNotFound(std::option::Option<String>), 186 + #[serde(rename = "EntryNotFound")] 187 + EntryNotFound(std::option::Option<String>), 188 + } 189 + 190 + impl std::fmt::Display for GetEntryByTitleError<'_> { 191 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 192 + match self { 193 + Self::NotebookNotFound(msg) => { 194 + write!(f, "NotebookNotFound")?; 195 + if let Some(msg) = msg { 196 + write!(f, ": {}", msg)?; 197 + } 198 + Ok(()) 199 + } 200 + Self::EntryNotFound(msg) => { 201 + write!(f, "EntryNotFound")?; 202 + if let Some(msg) = msg { 203 + write!(f, ": {}", msg)?; 204 + } 205 + Ok(()) 206 + } 207 + Self::Unknown(err) => write!(f, "Unknown error: {:?}", err), 208 + } 209 + } 210 + } 211 + 212 + /// Response type for 213 + ///sh.weaver.notebook.getEntryByTitle 214 + pub struct GetEntryByTitleResponse; 215 + impl jacquard_common::xrpc::XrpcResp for GetEntryByTitleResponse { 216 + const NSID: &'static str = "sh.weaver.notebook.getEntryByTitle"; 217 + const ENCODING: &'static str = "application/json"; 218 + type Output<'de> = GetEntryByTitleOutput<'de>; 219 + type Err<'de> = GetEntryByTitleError<'de>; 220 + } 221 + 222 + impl<'a> jacquard_common::xrpc::XrpcRequest for GetEntryByTitle<'a> { 223 + const NSID: &'static str = "sh.weaver.notebook.getEntryByTitle"; 224 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 225 + type Response = GetEntryByTitleResponse; 226 + } 227 + 228 + /// Endpoint type for 229 + ///sh.weaver.notebook.getEntryByTitle 230 + pub struct GetEntryByTitleRequest; 231 + impl jacquard_common::xrpc::XrpcEndpoint for GetEntryByTitleRequest { 232 + const PATH: &'static str = "/xrpc/sh.weaver.notebook.getEntryByTitle"; 233 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 234 + type Request<'de> = GetEntryByTitle<'de>; 235 + type Response = GetEntryByTitleResponse; 236 + }
+156
crates/weaver-api/src/sh_weaver/notebook/get_notebook.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.notebook.getNotebook 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[derive( 9 + serde::Serialize, 10 + serde::Deserialize, 11 + Debug, 12 + Clone, 13 + PartialEq, 14 + Eq, 15 + jacquard_derive::IntoStatic 16 + )] 17 + #[serde(rename_all = "camelCase")] 18 + pub struct GetNotebook<'a> { 19 + #[serde(borrow)] 20 + pub notebook: jacquard_common::types::string::AtUri<'a>, 21 + } 22 + 23 + pub mod get_notebook_state { 24 + 25 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 26 + #[allow(unused)] 27 + use ::core::marker::PhantomData; 28 + mod sealed { 29 + pub trait Sealed {} 30 + } 31 + /// State trait tracking which required fields have been set 32 + pub trait State: sealed::Sealed { 33 + type Notebook; 34 + } 35 + /// Empty state - all required fields are unset 36 + pub struct Empty(()); 37 + impl sealed::Sealed for Empty {} 38 + impl State for Empty { 39 + type Notebook = Unset; 40 + } 41 + ///State transition - sets the `notebook` field to Set 42 + pub struct SetNotebook<S: State = Empty>(PhantomData<fn() -> S>); 43 + impl<S: State> sealed::Sealed for SetNotebook<S> {} 44 + impl<S: State> State for SetNotebook<S> { 45 + type Notebook = Set<members::notebook>; 46 + } 47 + /// Marker types for field names 48 + #[allow(non_camel_case_types)] 49 + pub mod members { 50 + ///Marker type for the `notebook` field 51 + pub struct notebook(()); 52 + } 53 + } 54 + 55 + /// Builder for constructing an instance of this type 56 + pub struct GetNotebookBuilder<'a, S: get_notebook_state::State> { 57 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 58 + __unsafe_private_named: ( 59 + ::core::option::Option<jacquard_common::types::string::AtUri<'a>>, 60 + ), 61 + _phantom: ::core::marker::PhantomData<&'a ()>, 62 + } 63 + 64 + impl<'a> GetNotebook<'a> { 65 + /// Create a new builder for this type 66 + pub fn new() -> GetNotebookBuilder<'a, get_notebook_state::Empty> { 67 + GetNotebookBuilder::new() 68 + } 69 + } 70 + 71 + impl<'a> GetNotebookBuilder<'a, get_notebook_state::Empty> { 72 + /// Create a new builder with all fields unset 73 + pub fn new() -> Self { 74 + GetNotebookBuilder { 75 + _phantom_state: ::core::marker::PhantomData, 76 + __unsafe_private_named: (None,), 77 + _phantom: ::core::marker::PhantomData, 78 + } 79 + } 80 + } 81 + 82 + impl<'a, S> GetNotebookBuilder<'a, S> 83 + where 84 + S: get_notebook_state::State, 85 + S::Notebook: get_notebook_state::IsUnset, 86 + { 87 + /// Set the `notebook` field (required) 88 + pub fn notebook( 89 + mut self, 90 + value: impl Into<jacquard_common::types::string::AtUri<'a>>, 91 + ) -> GetNotebookBuilder<'a, get_notebook_state::SetNotebook<S>> { 92 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 93 + GetNotebookBuilder { 94 + _phantom_state: ::core::marker::PhantomData, 95 + __unsafe_private_named: self.__unsafe_private_named, 96 + _phantom: ::core::marker::PhantomData, 97 + } 98 + } 99 + } 100 + 101 + impl<'a, S> GetNotebookBuilder<'a, S> 102 + where 103 + S: get_notebook_state::State, 104 + S::Notebook: get_notebook_state::IsSet, 105 + { 106 + /// Build the final struct 107 + pub fn build(self) -> GetNotebook<'a> { 108 + GetNotebook { 109 + notebook: self.__unsafe_private_named.0.unwrap(), 110 + } 111 + } 112 + } 113 + 114 + #[jacquard_derive::lexicon] 115 + #[derive( 116 + serde::Serialize, 117 + serde::Deserialize, 118 + Debug, 119 + Clone, 120 + PartialEq, 121 + Eq, 122 + jacquard_derive::IntoStatic 123 + )] 124 + #[serde(rename_all = "camelCase")] 125 + pub struct GetNotebookOutput<'a> { 126 + #[serde(borrow)] 127 + pub entries: Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 128 + #[serde(borrow)] 129 + pub notebook: crate::sh_weaver::notebook::NotebookView<'a>, 130 + } 131 + 132 + /// Response type for 133 + ///sh.weaver.notebook.getNotebook 134 + pub struct GetNotebookResponse; 135 + impl jacquard_common::xrpc::XrpcResp for GetNotebookResponse { 136 + const NSID: &'static str = "sh.weaver.notebook.getNotebook"; 137 + const ENCODING: &'static str = "application/json"; 138 + type Output<'de> = GetNotebookOutput<'de>; 139 + type Err<'de> = jacquard_common::xrpc::GenericError<'de>; 140 + } 141 + 142 + impl<'a> jacquard_common::xrpc::XrpcRequest for GetNotebook<'a> { 143 + const NSID: &'static str = "sh.weaver.notebook.getNotebook"; 144 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 145 + type Response = GetNotebookResponse; 146 + } 147 + 148 + /// Endpoint type for 149 + ///sh.weaver.notebook.getNotebook 150 + pub struct GetNotebookRequest; 151 + impl jacquard_common::xrpc::XrpcEndpoint for GetNotebookRequest { 152 + const PATH: &'static str = "/xrpc/sh.weaver.notebook.getNotebook"; 153 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 154 + type Request<'de> = GetNotebook<'de>; 155 + type Response = GetNotebookResponse; 156 + }
+226
crates/weaver-api/src/sh_weaver/notebook/get_notebook_by_title.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.notebook.getNotebookByTitle 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[derive( 9 + serde::Serialize, 10 + serde::Deserialize, 11 + Debug, 12 + Clone, 13 + PartialEq, 14 + Eq, 15 + jacquard_derive::IntoStatic 16 + )] 17 + #[serde(rename_all = "camelCase")] 18 + pub struct GetNotebookByTitle<'a> { 19 + #[serde(borrow)] 20 + pub actor: jacquard_common::types::ident::AtIdentifier<'a>, 21 + #[serde(borrow)] 22 + pub title: jacquard_common::CowStr<'a>, 23 + } 24 + 25 + pub mod get_notebook_by_title_state { 26 + 27 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 28 + #[allow(unused)] 29 + use ::core::marker::PhantomData; 30 + mod sealed { 31 + pub trait Sealed {} 32 + } 33 + /// State trait tracking which required fields have been set 34 + pub trait State: sealed::Sealed { 35 + type Actor; 36 + type Title; 37 + } 38 + /// Empty state - all required fields are unset 39 + pub struct Empty(()); 40 + impl sealed::Sealed for Empty {} 41 + impl State for Empty { 42 + type Actor = Unset; 43 + type Title = Unset; 44 + } 45 + ///State transition - sets the `actor` field to Set 46 + pub struct SetActor<S: State = Empty>(PhantomData<fn() -> S>); 47 + impl<S: State> sealed::Sealed for SetActor<S> {} 48 + impl<S: State> State for SetActor<S> { 49 + type Actor = Set<members::actor>; 50 + type Title = S::Title; 51 + } 52 + ///State transition - sets the `title` field to Set 53 + pub struct SetTitle<S: State = Empty>(PhantomData<fn() -> S>); 54 + impl<S: State> sealed::Sealed for SetTitle<S> {} 55 + impl<S: State> State for SetTitle<S> { 56 + type Actor = S::Actor; 57 + type Title = Set<members::title>; 58 + } 59 + /// Marker types for field names 60 + #[allow(non_camel_case_types)] 61 + pub mod members { 62 + ///Marker type for the `actor` field 63 + pub struct actor(()); 64 + ///Marker type for the `title` field 65 + pub struct title(()); 66 + } 67 + } 68 + 69 + /// Builder for constructing an instance of this type 70 + pub struct GetNotebookByTitleBuilder<'a, S: get_notebook_by_title_state::State> { 71 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 72 + __unsafe_private_named: ( 73 + ::core::option::Option<jacquard_common::types::ident::AtIdentifier<'a>>, 74 + ::core::option::Option<jacquard_common::CowStr<'a>>, 75 + ), 76 + _phantom: ::core::marker::PhantomData<&'a ()>, 77 + } 78 + 79 + impl<'a> GetNotebookByTitle<'a> { 80 + /// Create a new builder for this type 81 + pub fn new() -> GetNotebookByTitleBuilder<'a, get_notebook_by_title_state::Empty> { 82 + GetNotebookByTitleBuilder::new() 83 + } 84 + } 85 + 86 + impl<'a> GetNotebookByTitleBuilder<'a, get_notebook_by_title_state::Empty> { 87 + /// Create a new builder with all fields unset 88 + pub fn new() -> Self { 89 + GetNotebookByTitleBuilder { 90 + _phantom_state: ::core::marker::PhantomData, 91 + __unsafe_private_named: (None, None), 92 + _phantom: ::core::marker::PhantomData, 93 + } 94 + } 95 + } 96 + 97 + impl<'a, S> GetNotebookByTitleBuilder<'a, S> 98 + where 99 + S: get_notebook_by_title_state::State, 100 + S::Actor: get_notebook_by_title_state::IsUnset, 101 + { 102 + /// Set the `actor` field (required) 103 + pub fn actor( 104 + mut self, 105 + value: impl Into<jacquard_common::types::ident::AtIdentifier<'a>>, 106 + ) -> GetNotebookByTitleBuilder<'a, get_notebook_by_title_state::SetActor<S>> { 107 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 108 + GetNotebookByTitleBuilder { 109 + _phantom_state: ::core::marker::PhantomData, 110 + __unsafe_private_named: self.__unsafe_private_named, 111 + _phantom: ::core::marker::PhantomData, 112 + } 113 + } 114 + } 115 + 116 + impl<'a, S> GetNotebookByTitleBuilder<'a, S> 117 + where 118 + S: get_notebook_by_title_state::State, 119 + S::Title: get_notebook_by_title_state::IsUnset, 120 + { 121 + /// Set the `title` field (required) 122 + pub fn title( 123 + mut self, 124 + value: impl Into<jacquard_common::CowStr<'a>>, 125 + ) -> GetNotebookByTitleBuilder<'a, get_notebook_by_title_state::SetTitle<S>> { 126 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 127 + GetNotebookByTitleBuilder { 128 + _phantom_state: ::core::marker::PhantomData, 129 + __unsafe_private_named: self.__unsafe_private_named, 130 + _phantom: ::core::marker::PhantomData, 131 + } 132 + } 133 + } 134 + 135 + impl<'a, S> GetNotebookByTitleBuilder<'a, S> 136 + where 137 + S: get_notebook_by_title_state::State, 138 + S::Actor: get_notebook_by_title_state::IsSet, 139 + S::Title: get_notebook_by_title_state::IsSet, 140 + { 141 + /// Build the final struct 142 + pub fn build(self) -> GetNotebookByTitle<'a> { 143 + GetNotebookByTitle { 144 + actor: self.__unsafe_private_named.0.unwrap(), 145 + title: self.__unsafe_private_named.1.unwrap(), 146 + } 147 + } 148 + } 149 + 150 + #[jacquard_derive::lexicon] 151 + #[derive( 152 + serde::Serialize, 153 + serde::Deserialize, 154 + Debug, 155 + Clone, 156 + PartialEq, 157 + Eq, 158 + jacquard_derive::IntoStatic 159 + )] 160 + #[serde(rename_all = "camelCase")] 161 + pub struct GetNotebookByTitleOutput<'a> { 162 + #[serde(borrow)] 163 + pub entries: Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 164 + #[serde(borrow)] 165 + pub notebook: crate::sh_weaver::notebook::NotebookView<'a>, 166 + } 167 + 168 + #[jacquard_derive::open_union] 169 + #[derive( 170 + serde::Serialize, 171 + serde::Deserialize, 172 + Debug, 173 + Clone, 174 + PartialEq, 175 + Eq, 176 + thiserror::Error, 177 + miette::Diagnostic, 178 + jacquard_derive::IntoStatic 179 + )] 180 + #[serde(tag = "error", content = "message")] 181 + #[serde(bound(deserialize = "'de: 'a"))] 182 + pub enum GetNotebookByTitleError<'a> { 183 + #[serde(rename = "NotebookNotFound")] 184 + NotebookNotFound(std::option::Option<String>), 185 + } 186 + 187 + impl std::fmt::Display for GetNotebookByTitleError<'_> { 188 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 189 + match self { 190 + Self::NotebookNotFound(msg) => { 191 + write!(f, "NotebookNotFound")?; 192 + if let Some(msg) = msg { 193 + write!(f, ": {}", msg)?; 194 + } 195 + Ok(()) 196 + } 197 + Self::Unknown(err) => write!(f, "Unknown error: {:?}", err), 198 + } 199 + } 200 + } 201 + 202 + /// Response type for 203 + ///sh.weaver.notebook.getNotebookByTitle 204 + pub struct GetNotebookByTitleResponse; 205 + impl jacquard_common::xrpc::XrpcResp for GetNotebookByTitleResponse { 206 + const NSID: &'static str = "sh.weaver.notebook.getNotebookByTitle"; 207 + const ENCODING: &'static str = "application/json"; 208 + type Output<'de> = GetNotebookByTitleOutput<'de>; 209 + type Err<'de> = GetNotebookByTitleError<'de>; 210 + } 211 + 212 + impl<'a> jacquard_common::xrpc::XrpcRequest for GetNotebookByTitle<'a> { 213 + const NSID: &'static str = "sh.weaver.notebook.getNotebookByTitle"; 214 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 215 + type Response = GetNotebookByTitleResponse; 216 + } 217 + 218 + /// Endpoint type for 219 + ///sh.weaver.notebook.getNotebookByTitle 220 + pub struct GetNotebookByTitleRequest; 221 + impl jacquard_common::xrpc::XrpcEndpoint for GetNotebookByTitleRequest { 222 + const PATH: &'static str = "/xrpc/sh.weaver.notebook.getNotebookByTitle"; 223 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 224 + type Request<'de> = GetNotebookByTitle<'de>; 225 + type Response = GetNotebookByTitleResponse; 226 + }
crates/weaver-app/.env-example crates/weaver-app/.env-dev
+102
crates/weaver-app/assets/styling/drafts.css
··· 1 + .drafts-page { 2 + max-width: 800px; 3 + margin: 0 auto; 4 + padding: 2rem; 5 + } 6 + 7 + .drafts-header { 8 + display: flex; 9 + justify-content: space-between; 10 + align-items: center; 11 + margin-bottom: 2rem; 12 + } 13 + 14 + .drafts-header h1 { 15 + margin: 0; 16 + } 17 + 18 + .drafts-empty { 19 + text-align: center; 20 + padding: 4rem 2rem; 21 + color: var(--color-subtle); 22 + } 23 + 24 + .drafts-list { 25 + display: flex; 26 + flex-direction: column; 27 + gap: 0.5rem; 28 + } 29 + 30 + .draft-card { 31 + display: flex; 32 + align-items: center; 33 + gap: 1rem; 34 + padding: 1rem; 35 + background: var(--color-surface); 36 + border: 1px solid var(--color-border); 37 + border-radius: 0; 38 + transition: border-color 0.15s ease; 39 + } 40 + 41 + .draft-card:hover { 42 + border-color: var(--color-primary); 43 + } 44 + 45 + .draft-card-link { 46 + flex: 1; 47 + text-decoration: none; 48 + color: inherit; 49 + } 50 + 51 + .draft-card-content { 52 + display: flex; 53 + align-items: center; 54 + gap: 1rem; 55 + } 56 + 57 + .draft-title { 58 + margin: 0; 59 + font-size: 1rem; 60 + font-weight: 500; 61 + } 62 + 63 + .draft-badge { 64 + font-size: 0.75rem; 65 + padding: 0.125rem 0.5rem; 66 + border-radius: 4px; 67 + font-weight: 500; 68 + } 69 + 70 + .draft-badge-new { 71 + background: var(--color-secondary); 72 + color: var(--color-base); 73 + } 74 + 75 + .draft-badge-edit { 76 + background: var(--color-surface); 77 + color: var(--color-subtle); 78 + border: 1px solid var(--color-border); 79 + } 80 + 81 + /* Mobile adjustments */ 82 + @media (max-width: 600px) { 83 + .drafts-page { 84 + padding: 1rem; 85 + } 86 + 87 + .drafts-header { 88 + flex-direction: column; 89 + align-items: flex-start; 90 + gap: 1rem; 91 + } 92 + 93 + .draft-card { 94 + padding: 0.75rem; 95 + } 96 + 97 + .draft-card-content { 98 + flex-direction: column; 99 + align-items: flex-start; 100 + gap: 0.5rem; 101 + } 102 + }
+91
crates/weaver-app/assets/styling/entry-actions.css
··· 1 + .entry-actions { 2 + display: flex; 3 + align-items: center; 4 + gap: 0.25rem; 5 + } 6 + 7 + .entry-action-link { 8 + text-decoration: none; 9 + } 10 + 11 + /* Match notebook-add-entry styling for buttons */ 12 + .entry-actions .button { 13 + font-size: 0.85rem; 14 + color: var(--color-primary); 15 + background: transparent; 16 + padding: 0.25rem 0.5rem; 17 + border-radius: 0; 18 + border: 1px solid var(--color-border); 19 + transition: 20 + background-color 0.15s ease, 21 + color 0.15s ease; 22 + } 23 + 24 + .entry-actions .button:hover { 25 + background-color: var(--color-primary); 26 + color: var(--color-base); 27 + } 28 + 29 + .entry-actions-dropdown { 30 + position: relative; 31 + } 32 + 33 + .dropdown-menu { 34 + position: absolute; 35 + right: 0; 36 + top: 100%; 37 + background: var(--color-surface); 38 + border: 1px solid var(--color-border); 39 + padding: 0.25rem; 40 + z-index: 100; 41 + min-width: 160px; 42 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 43 + } 44 + 45 + @media (prefers-color-scheme: dark) { 46 + .dropdown-menu { 47 + box-shadow: none; 48 + border: 1px dashed var(--color-border); 49 + } 50 + } 51 + 52 + .dropdown-item { 53 + display: block; 54 + width: 100%; 55 + padding: 0.5rem 0.75rem; 56 + text-align: left; 57 + background: none; 58 + border: none; 59 + cursor: pointer; 60 + font-size: 0.875rem; 61 + color: var(--color-text); 62 + } 63 + 64 + .dropdown-item:hover { 65 + background: var(--color-highlight); 66 + } 67 + 68 + .dropdown-item-danger { 69 + color: var(--color-error); 70 + } 71 + 72 + .dropdown-item-danger:hover { 73 + background: var(--color-error); 74 + color: var(--color-base); 75 + } 76 + 77 + .dialog-error { 78 + color: var(--destructive); 79 + font-size: 0.875rem; 80 + padding: 0.5rem; 81 + background: var(--destructive-muted, rgba(220, 38, 38, 0.1)); 82 + border-radius: 0; 83 + margin-bottom: 1rem; 84 + } 85 + 86 + .dialog-actions { 87 + display: flex; 88 + gap: 0.5rem; 89 + justify-content: flex-end; 90 + margin-top: 1rem; 91 + }
+14 -3
crates/weaver-app/assets/styling/entry-card.css
··· 79 79 } 80 80 81 81 .entry-card-header { 82 + display: flex; 83 + align-items: baseline; 84 + gap: 0.75rem; 85 + margin-bottom: 0.5rem; 86 + } 87 + 88 + .entry-card-title-link { 89 + flex: 1; 90 + text-decoration: none; 91 + color: inherit; 92 + } 93 + 94 + .entry-card-title-link:hover .entry-card-title { 95 + color: var(--color-secondary); 82 96 } 83 97 84 98 .entry-card-title { 85 - margin-left: auto; 86 99 font-size: 1.25rem; 87 100 font-weight: 600; 88 101 color: var(--color-primary); ··· 92 105 } 93 106 94 107 .entry-card-meta { 95 - display: flex; 96 - align-items: center; 97 108 gap: 1rem; 98 109 margin-bottom: 0.5rem; 99 110 font-size: 0.875rem;
+8
crates/weaver-app/assets/styling/entry.css
··· 105 105 border-bottom: 2px solid var(--color-border); 106 106 } 107 107 108 + .entry-header-row { 109 + display: flex; 110 + align-items: baseline; 111 + justify-content: space-between; 112 + gap: 1rem; 113 + } 114 + 108 115 .entry-title { 109 116 margin-bottom: calc(1rem * var(--spacing-scale, 1.5)); 110 117 margin-top: calc(1rem * var(--spacing-scale, 1.5)); 111 118 color: var(--color-text); 119 + flex: 1; 112 120 } 113 121 114 122 .entry-meta-info {
+45 -1
crates/weaver-app/assets/styling/notebook-card.css
··· 87 87 border-bottom: 2px solid var(--color-border); 88 88 } 89 89 90 + .notebook-card-header-top { 91 + display: flex; 92 + justify-content: space-between; 93 + align-items: flex-start; 94 + gap: 1rem; 95 + } 96 + 97 + .notebook-add-entry { 98 + font-size: 0.85rem; 99 + color: var(--color-primary); 100 + text-decoration: none; 101 + padding: 0.25rem 0.5rem; 102 + border: 1px solid var(--color-border); 103 + border-radius: 0; 104 + transition: 105 + background-color 0.15s ease, 106 + color 0.15s ease; 107 + white-space: nowrap; 108 + } 109 + 110 + .notebook-add-entry:hover { 111 + background-color: var(--color-primary); 112 + color: var(--color-base); 113 + } 114 + 115 + .notebook-actions { 116 + margin-bottom: 1.5rem; 117 + } 118 + 90 119 .notebook-card-title { 91 120 font-size: 1.5rem; 92 121 font-weight: 600; ··· 152 181 153 182 .entry-preview-header { 154 183 display: flex; 155 - justify-content: space-between; 156 184 align-items: baseline; 157 185 gap: 1rem; 158 186 margin-bottom: 0.5rem; 187 + } 188 + 189 + .entry-preview-title-link { 190 + flex: 1; 191 + text-decoration: none; 192 + color: inherit; 193 + } 194 + 195 + .entry-preview-title-link:hover .entry-preview-title { 196 + color: var(--color-secondary); 197 + } 198 + 199 + .entry-preview-content-link { 200 + display: block; 201 + text-decoration: none; 202 + color: inherit; 159 203 } 160 204 161 205 .entry-preview-title {
+19
crates/weaver-app/assets/styling/notebook-cover.css
··· 108 108 color: var(--color-subtle); 109 109 border-bottom: 1px solid var(--color-border); 110 110 } 111 + 112 + /* Owner actions */ 113 + .notebook-cover-actions { 114 + margin-top: 1.5rem; 115 + padding-top: 1rem; 116 + border-top: 1px solid var(--color-border); 117 + border-radius: 0; 118 + } 119 + 120 + .notebook-cover-action-link { 121 + text-decoration: none; 122 + border-radius: 0; 123 + } 124 + 125 + .notebook-cover-actions .button { 126 + width: 100%; 127 + text-align: left; 128 + border-radius: 0; 129 + }
+82
crates/weaver-app/assets/styling/profile-actions.css
··· 1 + .profile-actions { 2 + grid-column: 3; 3 + position: sticky; 4 + top: 2rem; 5 + align-self: flex-start; 6 + } 7 + 8 + .profile-actions-container { 9 + padding-top: 2.25rem; 10 + padding-right: 5rem; 11 + } 12 + 13 + .profile-actions-title { 14 + font-size: 0.75rem; 15 + font-weight: 600; 16 + margin-bottom: 1rem; 17 + color: var(--color-subtle); 18 + text-transform: uppercase; 19 + letter-spacing: 0.05em; 20 + } 21 + 22 + .profile-actions-list { 23 + display: flex; 24 + flex-direction: column; 25 + gap: 0.5rem; 26 + } 27 + 28 + .profile-action-link { 29 + display: block; 30 + text-decoration: none; 31 + } 32 + 33 + /* Match notebook-add-entry styling */ 34 + .profile-actions-list .button { 35 + width: 100%; 36 + text-align: left; 37 + font-size: 0.85rem; 38 + color: var(--color-primary); 39 + background: transparent; 40 + padding: 0.25rem 0.5rem; 41 + border-radius: 0; 42 + border: 1px solid var(--color-border); 43 + transition: 44 + background-color 0.15s ease, 45 + color 0.15s ease; 46 + } 47 + 48 + .profile-actions-list .button:hover { 49 + background-color: var(--color-primary); 50 + color: var(--color-base); 51 + } 52 + 53 + .profile-actions-list .button:disabled { 54 + opacity: 0.5; 55 + cursor: not-allowed; 56 + border-radius: 0; 57 + } 58 + 59 + .profile-actions-list .button:disabled:hover { 60 + background: transparent; 61 + color: var(--color-primary); 62 + } 63 + 64 + /* Mobile menubar */ 65 + .profile-actions-menubar { 66 + display: none; 67 + gap: 0.5rem; 68 + padding: 1rem 0; 69 + border-bottom: 1px solid var(--color-border); 70 + margin-bottom: 1rem; 71 + } 72 + 73 + /* Hide sidebar on mobile, show menubar */ 74 + @media (max-width: 1400px) { 75 + .profile-actions { 76 + display: none; 77 + } 78 + 79 + .profile-actions-menubar { 80 + display: flex; 81 + } 82 + }
+41 -11
crates/weaver-app/src/components/dialog/component.rs
··· 3 3 self, DialogContentProps, DialogDescriptionProps, DialogRootProps, DialogTitleProps, 4 4 }; 5 5 6 + const DIALOG_CSS: Asset = asset!("./dialog.css"); 7 + 8 + /// Fixed-position overlay that works in both SSR and client-only modes. 9 + /// Wraps dioxus_primitives DialogRoot with inline positioning styles 10 + /// since the CSS file doesn't load reliably in client-only mode. 6 11 #[component] 7 12 pub fn DialogRoot(props: DialogRootProps) -> Element { 13 + let is_open = props.open.read().unwrap_or(false); 14 + 15 + let overlay_style = if is_open { 16 + "position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center;" 17 + } else { 18 + "display: none;" 19 + }; 20 + 8 21 rsx! { 9 - document::Link { rel: "stylesheet", href: asset!("./dialog.css") } 10 - dialog::DialogRoot { 11 - class: "dialog-backdrop", 12 - id: props.id, 13 - is_modal: props.is_modal, 14 - open: props.open, 15 - default_open: props.default_open, 16 - on_open_change: props.on_open_change, 17 - attributes: props.attributes, 18 - {props.children} 22 + document::Link { rel: "stylesheet", href: DIALOG_CSS } 23 + div { 24 + style: "{overlay_style}", 25 + onclick: { 26 + let on_change = props.on_open_change.clone(); 27 + move |_| { 28 + on_change.call(false); 29 + } 30 + }, 31 + dialog::DialogRoot { 32 + class: "dialog-backdrop-inner", 33 + id: props.id, 34 + is_modal: props.is_modal, 35 + open: props.open, 36 + default_open: props.default_open, 37 + on_open_change: props.on_open_change, 38 + attributes: props.attributes, 39 + {props.children} 40 + } 19 41 } 20 42 } 21 43 } ··· 23 45 #[component] 24 46 pub fn DialogContent(props: DialogContentProps) -> Element { 25 47 rsx! { 26 - dialog::DialogContent { class: "dialog", id: props.id, attributes: props.attributes, {props.children} } 48 + div { 49 + onclick: move |e| e.stop_propagation(), 50 + dialog::DialogContent { 51 + class: "dialog", 52 + id: props.id, 53 + attributes: props.attributes, 54 + {props.children} 55 + } 56 + } 27 57 } 28 58 } 29 59
+30 -15
crates/weaver-app/src/components/editor/component.rs
··· 4 4 use jacquard::IntoStatic; 5 5 use jacquard::cowstr::ToCowStr; 6 6 use jacquard::identity::resolver::IdentityResolver; 7 + use jacquard::smol_str::SmolStr; 7 8 use jacquard::types::blob::BlobRef; 8 9 use jacquard::types::ident::AtIdentifier; 9 10 use weaver_api::sh_weaver::embed::images::Image; ··· 14 15 use crate::fetch::Fetcher; 15 16 16 17 use super::actions::{ 17 - execute_action, handle_keydown_with_bindings, EditorAction, Key, KeyCombo, KeybindingConfig, 18 - KeydownResult, Range, 18 + EditorAction, Key, KeyCombo, KeybindingConfig, KeydownResult, Range, execute_action, 19 + handle_keydown_with_bindings, 19 20 }; 20 - use super::beforeinput::{handle_beforeinput, BeforeInputContext, BeforeInputResult, InputType}; 21 + use super::beforeinput::{BeforeInputContext, BeforeInputResult, InputType, handle_beforeinput}; 21 22 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 22 23 use super::beforeinput::{get_data_from_event, get_target_range_from_event}; 23 24 use super::document::{CompositionState, EditorDocument, LoadedDocState}; ··· 57 58 /// # Props 58 59 /// - `initial_content`: Optional initial markdown content (for new entries) 59 60 /// - `entry_uri`: Optional AT-URI of an existing entry to edit 61 + /// - `target_notebook`: Optional notebook title to add the entry to when publishing 60 62 #[component] 61 - pub fn MarkdownEditor(initial_content: Option<String>, entry_uri: Option<String>) -> Element { 63 + pub fn MarkdownEditor( 64 + initial_content: Option<String>, 65 + entry_uri: Option<String>, 66 + target_notebook: Option<SmolStr>, 67 + ) -> Element { 62 68 let fetcher = use_context::<Fetcher>(); 63 69 64 70 // Determine draft key - use entry URI if editing existing, otherwise generate TID 65 71 let draft_key = use_hook(|| { 66 72 entry_uri.clone().unwrap_or_else(|| { 67 - format!("new:{}", jacquard::types::tid::Ticker::new().next(None).as_str()) 73 + format!( 74 + "new:{}", 75 + jacquard::types::tid::Ticker::new().next(None).as_str() 76 + ) 68 77 }) 69 78 }); 70 79 71 80 // Parse entry URI once 72 81 let parsed_uri = entry_uri.as_ref().and_then(|s| { 73 - jacquard::types::string::AtUri::new(s).ok().map(|u| u.into_static()) 82 + jacquard::types::string::AtUri::new(s) 83 + .ok() 84 + .map(|u| u.into_static()) 74 85 }); 75 86 76 87 // Clone draft_key for render (resource closure moves it) ··· 112 123 entry_authority 113 124 ); 114 125 return LoadResult::Failed( 115 - "You can only edit your own entries".to_string() 126 + "You can only edit your own entries".to_string(), 116 127 ); 117 128 } 118 129 } ··· 215 226 /// - PDS sync with auto-save 216 227 /// - Keyboard shortcuts (Ctrl+B for bold, Ctrl+I for italic) 217 228 #[component] 218 - fn MarkdownEditorInner( 219 - draft_key: String, 220 - loaded_state: LoadedDocState, 221 - ) -> Element { 229 + fn MarkdownEditorInner(draft_key: String, loaded_state: LoadedDocState) -> Element { 222 230 // Context for authenticated API calls 223 231 let fetcher = use_context::<Fetcher>(); 224 232 let auth_state = use_context::<Signal<AuthState>>(); ··· 352 360 353 361 // Track last saved frontiers to detect changes (peek-only, no subscriptions) 354 362 let mut last_saved_frontiers: Signal<Option<loro::Frontiers>> = use_signal(|| None); 363 + 364 + // Store interval handle so it's dropped when component unmounts (prevents panic) 365 + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 366 + let mut interval_holder: Signal<Option<gloo_timers::callback::Interval>> = use_signal(|| None); 355 367 356 368 // Auto-save with periodic check (no reactive dependency to avoid loops) 357 369 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] ··· 383 395 last_saved_frontiers.set(Some(current_frontiers)); 384 396 } 385 397 }); 386 - interval.forget(); 398 + // Store in signal instead of forget - interval drops when component unmounts 399 + interval_holder.set(Some(interval)); 387 400 }); 388 401 389 402 // Set up beforeinput listener for all text input handling. ··· 466 479 if let Some(window) = web_sys::window() { 467 480 if let Some(doc) = window.document() { 468 481 if let Some(elem) = doc.get_element_by_id(editor_id) { 469 - if let Some(html_elem) = elem.dyn_ref::<web_sys::HtmlElement>() { 482 + if let Some(html_elem) = 483 + elem.dyn_ref::<web_sys::HtmlElement>() 484 + { 470 485 let _ = html_elem.blur(); 471 486 let _ = html_elem.focus(); 472 487 } ··· 817 832 if plat.android && evt.key() == Key::Enter { 818 833 tracing::debug!("Android: handling Enter in keypress"); 819 834 evt.prevent_default(); 820 - 835 + 821 836 // Get current range 822 837 let range = if let Some(sel) = *doc.selection.read() { 823 838 Range::new(sel.anchor.min(sel.head), sel.anchor.max(sel.head)) 824 839 } else { 825 840 Range::caret(doc.cursor.read().offset) 826 841 }; 827 - 842 + 828 843 let action = EditorAction::InsertParagraph { range }; 829 844 execute_action(&mut doc, &action); 830 845 }
+144 -86
crates/weaver-app/src/components/entry.rs
··· 1 1 #![allow(non_snake_case)] 2 2 3 + use crate::Route; 3 4 #[cfg(feature = "server")] 4 5 use crate::blobcache::BlobCache; 5 6 use crate::{ 7 + components::EntryActions, 6 8 components::avatar::{Avatar, AvatarImage}, 7 9 data::use_handle, 8 10 }; 9 - 10 - use crate::Route; 11 11 use dioxus::prelude::*; 12 + use jacquard::IntoStatic; 13 + use jacquard::types::aturi::AtUri; 12 14 13 15 const ENTRY_CSS: Asset = asset!("/assets/styling/entry.css"); 14 16 ··· 66 68 })); 67 69 68 70 // Handle blob caching when entry data is available 69 - match &*entry.read() { 71 + match &*entry.read_unchecked() { 70 72 Some((book_entry_view, entry_record)) => { 71 73 if let Some(embeds) = &entry_record.embeds { 72 74 if let Some(_images) = &embeds.images { 73 75 // Register blob mappings with service worker (client-side only) 74 - #[cfg(all( 75 - target_family = "wasm", 76 - target_os = "unknown", 77 - not(feature = "fullstack-server") 78 - ))] 79 - { 80 - let fetcher = fetcher.clone(); 81 - let images = images.clone(); 82 - spawn(async move { 83 - let _ = crate::service_worker::register_entry_blobs( 84 - &ident(), 85 - book_title().as_str(), 86 - &_images, 87 - &fetcher, 88 - ) 89 - .await; 90 - }); 91 - } 76 + // #[cfg(all( 77 + // target_family = "wasm", 78 + // target_os = "unknown", 79 + // not(feature = "fullstack-server") 80 + // ))] 81 + // { 82 + // let fetcher = fetcher.clone(); 83 + // let images = _images.clone().into_static(); 84 + // spawn(async move { 85 + // let images = images.clone(); 86 + // let fetcher = fetcher.clone(); 87 + // let _ = crate::service_worker::register_entry_blobs( 88 + // &ident(), 89 + // book_title().as_str(), 90 + // &_images, 91 + // &fetcher, 92 + // ) 93 + // .await; 94 + // }); 95 + // } 92 96 } 93 97 } 94 98 rsx! { EntryPageView { ··· 143 147 // Metadata header 144 148 EntryMetadata { 145 149 entry_view: entry_view.clone(), 146 - created_at: entry_record().created_at.clone() 150 + created_at: entry_record().created_at.clone(), 151 + entry_uri: entry_view.uri.clone().into_static(), 152 + book_title: Some(book_title()), 153 + ident: ident() 147 154 } 148 155 149 156 // Rendered markdown ··· 176 183 ident: AtIdentifier<'static>, 177 184 ) -> Element { 178 185 use crate::Route; 186 + use crate::auth::AuthState; 179 187 use jacquard::from_data; 180 188 use weaver_api::sh_weaver::notebook::entry::Entry; 181 189 190 + let mut hidden = use_signal(|| false); 191 + 192 + // If removed from notebook, hide this card 193 + if hidden() { 194 + return rsx! {}; 195 + } 196 + 197 + let auth_state = use_context::<Signal<AuthState>>(); 198 + 182 199 let entry_view = &entry.entry; 183 200 let title = entry_view 184 201 .title ··· 192 209 .format("%B %d, %Y") 193 210 .to_string(); 194 211 212 + // Check ownership 213 + let is_owner = { 214 + let current_did = auth_state.read().did.clone(); 215 + match (&current_did, &ident) { 216 + (Some(did), AtIdentifier::Did(ident_did)) => *did == *ident_did, 217 + _ => false, 218 + } 219 + }; 220 + 221 + let entry_uri = entry_view.uri.clone().into_static(); 222 + 195 223 // Only show author if notebook has multiple authors 196 224 let show_author = author_count > 1; 197 225 let first_author = if show_author { ··· 210 238 211 239 rsx! { 212 240 div { class: "entry-card", 213 - Link { 214 - to: Route::EntryPage { 215 - ident: ident, 216 - book_title: book_title.clone(), 217 - title: title.to_string().into() 218 - }, 219 - class: "entry-card-link", 220 - 221 - 222 - 223 - div { class: "entry-card-meta", 224 - div { class: "entry-card-header", 225 - 241 + div { class: "entry-card-meta", 242 + div { class: "entry-card-header", 243 + Link { 244 + to: Route::EntryPage { 245 + ident: ident.clone(), 246 + book_title: book_title.clone(), 247 + title: title.to_string().into() 248 + }, 249 + class: "entry-card-title-link", 226 250 h3 { class: "entry-card-title", "{title}" } 227 - div { class: "entry-card-date", 228 - time { datetime: "{entry_view.indexed_at.as_str()}", "{formatted_date}" } 251 + } 252 + div { class: "entry-card-date", 253 + time { datetime: "{entry_view.indexed_at.as_str()}", "{formatted_date}" } 254 + } 255 + if is_owner { 256 + EntryActions { 257 + entry_uri, 258 + entry_title: title.to_string(), 259 + in_notebook: true, 260 + notebook_title: Some(book_title.clone()), 261 + on_removed: Some(EventHandler::new(move |_| hidden.set(true))) 229 262 } 230 263 } 231 - if let Some(author) = first_author { 232 - div { class: "entry-card-author", 233 - { 234 - use weaver_api::sh_weaver::actor::ProfileDataViewInner; 264 + } 265 + if let Some(author) = first_author { 266 + div { class: "entry-card-author", 267 + { 268 + use weaver_api::sh_weaver::actor::ProfileDataViewInner; 235 269 236 - match &author.record.inner { 237 - ProfileDataViewInner::ProfileView(profile) => { 238 - let display_name = profile.display_name.as_ref().map(|n| n.as_ref()).unwrap_or("Unknown"); 239 - let handle = profile.handle.clone(); 240 - rsx! { 241 - if let Some(ref avatar_url) = profile.avatar { 242 - Avatar { 243 - AvatarImage { src: avatar_url.as_ref() } 244 - } 270 + match &author.record.inner { 271 + ProfileDataViewInner::ProfileView(profile) => { 272 + let display_name = profile.display_name.as_ref().map(|n| n.as_ref()).unwrap_or("Unknown"); 273 + let handle = profile.handle.clone(); 274 + rsx! { 275 + if let Some(ref avatar_url) = profile.avatar { 276 + Avatar { 277 + AvatarImage { src: avatar_url.as_ref() } 245 278 } 246 - span { class: "author-name", "{display_name}" } 247 - span { class: "meta-label", "@{handle}" } 248 279 } 280 + span { class: "author-name", "{display_name}" } 281 + span { class: "meta-label", "@{handle}" } 249 282 } 250 - ProfileDataViewInner::ProfileViewDetailed(profile) => { 251 - let display_name = profile.display_name.as_ref().map(|n| n.as_ref()).unwrap_or("Unknown"); 252 - let handle = profile.handle.clone(); 253 - rsx! { 254 - if let Some(ref avatar_url) = profile.avatar { 255 - Avatar { 256 - AvatarImage { src: avatar_url.as_ref() } 257 - } 283 + } 284 + ProfileDataViewInner::ProfileViewDetailed(profile) => { 285 + let display_name = profile.display_name.as_ref().map(|n| n.as_ref()).unwrap_or("Unknown"); 286 + let handle = profile.handle.clone(); 287 + rsx! { 288 + if let Some(ref avatar_url) = profile.avatar { 289 + Avatar { 290 + AvatarImage { src: avatar_url.as_ref() } 258 291 } 259 - span { class: "author-name", "{display_name}" } 260 - span { class: "meta-label", "@{handle}" } 261 292 } 293 + span { class: "author-name", "{display_name}" } 294 + span { class: "meta-label", "@{handle}" } 262 295 } 263 - ProfileDataViewInner::TangledProfileView(profile) => { 264 - rsx! { 265 - span { class: "author-name", "@{profile.handle.as_ref()}" } 266 - } 296 + } 297 + ProfileDataViewInner::TangledProfileView(profile) => { 298 + rsx! { 299 + span { class: "author-name", "@{profile.handle.as_ref()}" } 267 300 } 268 - _ => { 269 - rsx! { 270 - span { class: "author-name", "Unknown" } 271 - } 301 + } 302 + _ => { 303 + rsx! { 304 + span { class: "author-name", "Unknown" } 272 305 } 273 306 } 274 307 } 275 308 } 276 309 } 277 - 278 - 279 310 } 311 + } 280 312 281 - 282 - 283 - if let Some(ref html) = preview_html { 284 - div { class: "entry-card-preview", dangerous_inner_html: "{html}" } 285 - } 286 - if let Some(ref tags) = entry_view.tags { 287 - if !tags.is_empty() { 288 - div { class: "entry-card-tags", 289 - for tag in tags.iter() { 290 - span { class: "entry-card-tag", "{tag}" } 291 - } 313 + if let Some(ref html) = preview_html { 314 + div { class: "entry-card-preview", dangerous_inner_html: "{html}" } 315 + } 316 + if let Some(ref tags) = entry_view.tags { 317 + if !tags.is_empty() { 318 + div { class: "entry-card-tags", 319 + for tag in tags.iter() { 320 + span { class: "entry-card-tag", "{tag}" } 292 321 } 293 322 } 294 323 } ··· 299 328 300 329 /// Metadata header showing title, authors, date, tags 301 330 #[component] 302 - fn EntryMetadata(entry_view: EntryView<'static>, created_at: Datetime) -> Element { 331 + fn EntryMetadata( 332 + entry_view: EntryView<'static>, 333 + created_at: Datetime, 334 + entry_uri: AtUri<'static>, 335 + book_title: Option<SmolStr>, 336 + ident: AtIdentifier<'static>, 337 + ) -> Element { 338 + let navigator = use_navigator(); 339 + 303 340 let title = entry_view 304 341 .title 305 342 .as_ref() 306 343 .map(|t| t.as_ref()) 307 344 .unwrap_or("Untitled"); 308 345 309 - //let indexed_at_chrono = entry_view.indexed_at.as_ref(); 346 + let entry_title = title.to_string(); 347 + 348 + // Navigate back to notebook when entry is removed 349 + let nav_book_title = book_title.clone(); 350 + let nav_ident = ident.clone(); 351 + let on_removed = move |_| { 352 + if let Some(ref title) = nav_book_title { 353 + navigator.push(Route::NotebookIndex { 354 + ident: nav_ident.clone(), 355 + book_title: title.clone(), 356 + }); 357 + } 358 + }; 310 359 311 360 rsx! { 312 361 header { class: "entry-metadata", 313 - h1 { class: "entry-title", "{title}" } 362 + div { class: "entry-header-row", 363 + h1 { class: "entry-title", "{title}" } 364 + EntryActions { 365 + entry_uri: entry_uri.clone(), 366 + entry_title, 367 + in_notebook: book_title.is_some(), 368 + notebook_title: book_title.clone(), 369 + on_removed: Some(EventHandler::new(on_removed)) 370 + } 371 + } 314 372 315 373 div { class: "entry-meta-info", 316 374 // Authors
+371
crates/weaver-app/src/components/entry_actions.rs
··· 1 + //! Action buttons for entries (edit, delete, remove from notebook). 2 + 3 + use crate::Route; 4 + use crate::auth::AuthState; 5 + use crate::components::button::{Button, ButtonVariant}; 6 + use crate::components::dialog::{DialogContent, DialogDescription, DialogRoot, DialogTitle}; 7 + use crate::fetch::Fetcher; 8 + use dioxus::prelude::*; 9 + use jacquard::smol_str::SmolStr; 10 + use jacquard::types::aturi::AtUri; 11 + use jacquard::types::ident::AtIdentifier; 12 + use jacquard::IntoStatic; 13 + use weaver_api::com_atproto::repo::delete_record::DeleteRecord; 14 + use weaver_api::com_atproto::repo::put_record::PutRecord; 15 + 16 + const ENTRY_ACTIONS_CSS: Asset = asset!("/assets/styling/entry-actions.css"); 17 + 18 + #[derive(Props, Clone, PartialEq)] 19 + pub struct EntryActionsProps { 20 + /// The AT-URI of the entry 21 + pub entry_uri: AtUri<'static>, 22 + /// The entry title (for display in confirmation) 23 + pub entry_title: String, 24 + /// Whether this entry is in a notebook (enables "remove from notebook") 25 + #[props(default = false)] 26 + pub in_notebook: bool, 27 + /// Notebook title (if in_notebook is true, used for edit route) 28 + #[props(default)] 29 + pub notebook_title: Option<SmolStr>, 30 + /// Callback when entry is removed from notebook (for optimistic UI update) 31 + #[props(default)] 32 + pub on_removed: Option<EventHandler<()>>, 33 + } 34 + 35 + /// Action buttons for an entry: edit, delete, optionally remove from notebook. 36 + #[component] 37 + pub fn EntryActions(props: EntryActionsProps) -> Element { 38 + let auth_state = use_context::<Signal<AuthState>>(); 39 + let fetcher = use_context::<Fetcher>(); 40 + let navigator = use_navigator(); 41 + 42 + let mut show_delete_confirm = use_signal(|| false); 43 + let mut show_remove_confirm = use_signal(|| false); 44 + let mut show_dropdown = use_signal(|| false); 45 + let mut deleting = use_signal(|| false); 46 + let mut removing = use_signal(|| false); 47 + let mut error = use_signal(|| None::<String>); 48 + 49 + // Check ownership - compare auth DID with entry's authority 50 + let current_did = auth_state.read().did.clone(); 51 + let entry_authority = props.entry_uri.authority(); 52 + let is_owner = match (&current_did, entry_authority) { 53 + (Some(current), AtIdentifier::Did(entry_did)) => *current == *entry_did, 54 + _ => false, 55 + }; 56 + 57 + if !is_owner { 58 + return rsx! {}; 59 + } 60 + 61 + // Extract rkey from URI for edit route 62 + let rkey = match props.entry_uri.rkey() { 63 + Some(r) => r.0.to_string(), 64 + None => return rsx! {}, // Can't edit without rkey 65 + }; 66 + 67 + // Build edit route based on whether entry is in a notebook 68 + let ident = props.entry_uri.authority().clone(); 69 + let edit_route = if props.in_notebook { 70 + if let Some(ref notebook) = props.notebook_title { 71 + Route::NotebookEntryEdit { 72 + ident: ident.into_static(), 73 + book_title: notebook.clone(), 74 + rkey: rkey.clone().into(), 75 + } 76 + } else { 77 + Route::StandaloneEntryEdit { 78 + ident: ident.into_static(), 79 + rkey: rkey.clone().into(), 80 + } 81 + } 82 + } else { 83 + Route::StandaloneEntryEdit { 84 + ident: ident.into_static(), 85 + rkey: rkey.clone().into(), 86 + } 87 + }; 88 + 89 + let entry_uri_for_delete = props.entry_uri.clone(); 90 + let entry_title = props.entry_title.clone(); 91 + 92 + let delete_fetcher = fetcher.clone(); 93 + let handle_delete = move |_| { 94 + let fetcher = delete_fetcher.clone(); 95 + let uri = entry_uri_for_delete.clone(); 96 + let navigator = navigator.clone(); 97 + 98 + spawn(async move { 99 + use jacquard::prelude::*; 100 + 101 + deleting.set(true); 102 + error.set(None); 103 + 104 + let client = fetcher.get_client(); 105 + let collection = uri.collection(); 106 + let rkey = uri.rkey(); 107 + 108 + if let (Some(collection), Some(rkey)) = (collection, rkey) { 109 + let did = match fetcher.current_did().await { 110 + Some(d) => d, 111 + None => { 112 + error.set(Some("Not authenticated".to_string())); 113 + deleting.set(false); 114 + return; 115 + } 116 + }; 117 + 118 + let request = DeleteRecord::new() 119 + .repo(AtIdentifier::Did(did)) 120 + .collection(collection.clone()) 121 + .rkey(rkey.clone()) 122 + .build(); 123 + 124 + match client.send(request).await { 125 + Ok(_) => { 126 + show_delete_confirm.set(false); 127 + // Navigate back to home after delete 128 + navigator.push(Route::Home {}); 129 + } 130 + Err(e) => { 131 + error.set(Some(format!("Delete failed: {:?}", e))); 132 + } 133 + } 134 + } else { 135 + error.set(Some("Invalid entry URI".to_string())); 136 + } 137 + deleting.set(false); 138 + }); 139 + }; 140 + 141 + // Handler for removing entry from notebook (keeps entry, just removes from notebook's list) 142 + let entry_uri_for_remove = props.entry_uri.clone(); 143 + let notebook_title_for_remove = props.notebook_title.clone(); 144 + let on_removed = props.on_removed.clone(); 145 + let handle_remove_from_notebook = move |_| { 146 + let fetcher = fetcher.clone(); 147 + let entry_uri = entry_uri_for_remove.clone(); 148 + let notebook_title = notebook_title_for_remove.clone(); 149 + let on_removed = on_removed.clone(); 150 + 151 + spawn(async move { 152 + use jacquard::{from_data, to_data, prelude::*, types::string::Nsid}; 153 + use weaver_api::sh_weaver::notebook::book::Book; 154 + 155 + let client = fetcher.get_client(); 156 + 157 + removing.set(true); 158 + error.set(None); 159 + 160 + let notebook_title = match notebook_title { 161 + Some(t) => t, 162 + None => { 163 + error.set(Some("No notebook specified".to_string())); 164 + removing.set(false); 165 + return; 166 + } 167 + }; 168 + 169 + let did = match fetcher.current_did().await { 170 + Some(d) => d, 171 + None => { 172 + error.set(Some("Not authenticated".to_string())); 173 + removing.set(false); 174 + return; 175 + } 176 + }; 177 + 178 + // Get the notebook by title 179 + let ident = AtIdentifier::Did(did.clone()); 180 + let notebook_result = fetcher.get_notebook(ident.clone(), notebook_title.clone()).await; 181 + 182 + let (notebook_view, _) = match notebook_result { 183 + Ok(Some(data)) => data.as_ref().clone(), 184 + Ok(None) => { 185 + error.set(Some("Notebook not found".to_string())); 186 + removing.set(false); 187 + return; 188 + } 189 + Err(e) => { 190 + error.set(Some(format!("Failed to get notebook: {:?}", e))); 191 + removing.set(false); 192 + return; 193 + } 194 + }; 195 + 196 + // Parse the book record to get the entry_list 197 + let mut book: Book = match from_data(&notebook_view.record) { 198 + Ok(b) => b, 199 + Err(e) => { 200 + error.set(Some(format!("Failed to parse notebook: {:?}", e))); 201 + removing.set(false); 202 + return; 203 + } 204 + }; 205 + 206 + // Filter out the entry 207 + let entry_uri_str = entry_uri.as_str(); 208 + let original_len = book.entry_list.len(); 209 + book.entry_list.retain(|ref_| ref_.uri.as_str() != entry_uri_str); 210 + 211 + if book.entry_list.len() == original_len { 212 + error.set(Some("Entry not found in notebook".to_string())); 213 + removing.set(false); 214 + return; 215 + } 216 + 217 + // Get the notebook's rkey from its URI 218 + let notebook_rkey = match notebook_view.uri.rkey() { 219 + Some(r) => r, 220 + None => { 221 + error.set(Some("Invalid notebook URI".to_string())); 222 + removing.set(false); 223 + return; 224 + } 225 + }; 226 + 227 + // Convert book to Data for the request 228 + let book_data = match to_data(&book) { 229 + Ok(d) => d, 230 + Err(e) => { 231 + error.set(Some(format!("Failed to serialize notebook: {:?}", e))); 232 + removing.set(false); 233 + return; 234 + } 235 + }; 236 + 237 + // Update the notebook record 238 + let request = PutRecord::new() 239 + .repo(AtIdentifier::Did(did)) 240 + .collection(Nsid::new_static("sh.weaver.notebook.book").unwrap()) 241 + .rkey(notebook_rkey.clone()) 242 + .record(book_data) 243 + .build(); 244 + 245 + match client.send(request).await { 246 + Ok(_) => { 247 + show_remove_confirm.set(false); 248 + // Notify parent to remove from local state 249 + if let Some(handler) = &on_removed { 250 + handler.call(()); 251 + } 252 + } 253 + Err(e) => { 254 + error.set(Some(format!("Failed to update notebook: {:?}", e))); 255 + } 256 + } 257 + removing.set(false); 258 + }); 259 + }; 260 + 261 + rsx! { 262 + document::Link { rel: "stylesheet", href: ENTRY_ACTIONS_CSS } 263 + 264 + div { class: "entry-actions", 265 + // Edit button (always visible for owner) 266 + Link { 267 + to: edit_route, 268 + class: "entry-action-link", 269 + Button { 270 + variant: ButtonVariant::Ghost, 271 + "Edit" 272 + } 273 + } 274 + 275 + // Dropdown for destructive actions 276 + div { class: "entry-actions-dropdown", 277 + Button { 278 + variant: ButtonVariant::Ghost, 279 + onclick: move |_| show_dropdown.toggle(), 280 + "⋮" 281 + } 282 + 283 + if show_dropdown() { 284 + div { class: "dropdown-menu", 285 + if props.in_notebook { 286 + button { 287 + class: "dropdown-item", 288 + onclick: move |_| { 289 + show_dropdown.set(false); 290 + show_remove_confirm.set(true); 291 + }, 292 + "Remove from notebook" 293 + } 294 + } 295 + button { 296 + class: "dropdown-item dropdown-item-danger", 297 + onclick: move |_| { 298 + show_dropdown.set(false); 299 + show_delete_confirm.set(true); 300 + }, 301 + "Delete" 302 + } 303 + } 304 + } 305 + } 306 + 307 + // Delete confirmation dialog 308 + DialogRoot { 309 + open: show_delete_confirm(), 310 + on_open_change: move |open: bool| show_delete_confirm.set(open), 311 + DialogContent { 312 + DialogTitle { "Delete Entry?" } 313 + DialogDescription { 314 + "Delete \"{entry_title}\"? This removes the published entry. You can restore from drafts if needed." 315 + } 316 + if let Some(ref err) = error() { 317 + div { class: "dialog-error", "{err}" } 318 + } 319 + div { class: "dialog-actions", 320 + Button { 321 + variant: ButtonVariant::Destructive, 322 + onclick: handle_delete, 323 + disabled: deleting(), 324 + if deleting() { "Deleting..." } else { "Delete" } 325 + } 326 + Button { 327 + variant: ButtonVariant::Ghost, 328 + onclick: move |_| show_delete_confirm.set(false), 329 + "Cancel" 330 + } 331 + } 332 + } 333 + } 334 + 335 + // Remove from notebook confirmation dialog 336 + if props.in_notebook { 337 + { 338 + let entry_title_for_remove = entry_title.clone(); 339 + rsx! { 340 + DialogRoot { 341 + open: show_remove_confirm(), 342 + on_open_change: move |open: bool| show_remove_confirm.set(open), 343 + DialogContent { 344 + DialogTitle { "Remove from Notebook?" } 345 + DialogDescription { 346 + "Remove \"{entry_title_for_remove}\" from this notebook? The entry will still exist but won't be part of this notebook." 347 + } 348 + if let Some(ref err) = error() { 349 + div { class: "dialog-error", "{err}" } 350 + } 351 + div { class: "dialog-actions", 352 + Button { 353 + variant: ButtonVariant::Primary, 354 + onclick: handle_remove_from_notebook, 355 + disabled: removing(), 356 + if removing() { "Removing..." } else { "Remove" } 357 + } 358 + Button { 359 + variant: ButtonVariant::Ghost, 360 + onclick: move |_| show_remove_confirm.set(false), 361 + "Cancel" 362 + } 363 + } 364 + } 365 + } 366 + } 367 + } 368 + } 369 + } 370 + } 371 + }
+119 -40
crates/weaver-app/src/components/identity.rs
··· 1 + use crate::auth::AuthState; 2 + use crate::components::{ProfileActions, ProfileActionsMenubar}; 1 3 use crate::{Route, data, fetch}; 2 4 use dioxus::prelude::*; 3 5 use jacquard::{smol_str::SmolStr, types::ident::AtIdentifier}; ··· 47 49 48 50 // Main content area 49 51 main { class: "repository-main", 52 + // Mobile menubar (hidden on desktop) 53 + ProfileActionsMenubar { ident } 54 + 50 55 div { class: "notebooks-list", 51 56 match &*notebooks.read() { 52 57 Some(notebook_list) => rsx! { ··· 72 77 } 73 78 } 74 79 } 80 + 81 + // Actions sidebar (desktop only) 82 + ProfileActions { ident } 75 83 } 76 84 } 77 85 } ··· 84 92 use jacquard::IntoStatic; 85 93 86 94 let fetcher = use_context::<fetch::Fetcher>(); 95 + let auth_state = use_context::<Signal<AuthState>>(); 87 96 88 97 let title = notebook 89 98 .title ··· 91 100 .map(|t| t.as_ref()) 92 101 .unwrap_or("Untitled Notebook"); 93 102 103 + // Check ownership for "Add Entry" link 104 + let notebook_ident = notebook.uri.authority().clone().into_static(); 105 + let is_owner = { 106 + let current_did = auth_state.read().did.clone(); 107 + match (&current_did, &notebook_ident) { 108 + (Some(did), AtIdentifier::Did(nb_did)) => *did == *nb_did, 109 + _ => false, 110 + } 111 + }; 112 + 94 113 // Format date 95 114 let formatted_date = notebook.indexed_at.as_ref().format("%B %d, %Y").to_string(); 96 115 ··· 126 145 class: "notebook-card-header-link", 127 146 128 147 div { class: "notebook-card-header", 129 - h2 { class: "notebook-card-title", "{title}" } 148 + div { class: "notebook-card-header-top", 149 + h2 { class: "notebook-card-title", "{title}" } 150 + if is_owner { 151 + Link { 152 + to: Route::NewDraft { ident: notebook_ident.clone(), notebook: Some(book_title.clone()) }, 153 + class: "notebook-add-entry", 154 + "+ Add" 155 + } 156 + } 157 + } 130 158 131 159 div { class: "notebook-card-date", 132 160 time { datetime: "{notebook.indexed_at.as_str()}", "{formatted_date}" } ··· 199 227 let created_at = from_data::<Entry>(&entry_view.entry.record).ok() 200 228 .map(|entry| entry.created_at.as_ref().format("%B %d, %Y").to_string()); 201 229 202 - rsx! { 203 - Link { 204 - to: Route::EntryPage { 205 - ident: ident.clone(), 206 - book_title: book_title.clone(), 207 - title: entry_title.to_string().into() 208 - }, 209 - class: "notebook-entry-preview-link", 230 + let entry_uri = entry_view.entry.uri.clone().into_static(); 210 231 211 - div { class: "notebook-entry-preview", 212 - div { class: "entry-preview-header", 232 + rsx! { 233 + div { class: "notebook-entry-preview", 234 + div { class: "entry-preview-header", 235 + Link { 236 + to: Route::EntryPage { 237 + ident: ident.clone(), 238 + book_title: book_title.clone(), 239 + title: entry_title.to_string().into() 240 + }, 241 + class: "entry-preview-title-link", 213 242 div { class: "entry-preview-title", "{entry_title}" } 214 - if let Some(ref date) = created_at { 215 - div { class: "entry-preview-date", "{date}" } 243 + } 244 + if let Some(ref date) = created_at { 245 + div { class: "entry-preview-date", "{date}" } 246 + } 247 + if is_owner { 248 + crate::components::EntryActions { 249 + entry_uri, 250 + entry_title: entry_title.to_string(), 251 + in_notebook: true, 252 + notebook_title: Some(book_title.clone()) 216 253 } 217 254 } 218 - if let Some(ref html) = preview_html { 255 + } 256 + if let Some(ref html) = preview_html { 257 + Link { 258 + to: Route::EntryPage { 259 + ident: ident.clone(), 260 + book_title: book_title.clone(), 261 + title: entry_title.to_string().into() 262 + }, 263 + class: "entry-preview-content-link", 219 264 div { class: "entry-preview-content", dangerous_inner_html: "{html}" } 220 265 } 221 266 } ··· 243 288 let created_at = from_data::<Entry>(&first_entry.entry.record).ok() 244 289 .map(|entry| entry.created_at.as_ref().format("%B %d, %Y").to_string()); 245 290 291 + let entry_uri = first_entry.entry.uri.clone().into_static(); 292 + 246 293 rsx! { 247 - Link { 248 - to: Route::EntryPage { 249 - ident: ident.clone(), 250 - book_title: book_title.clone(), 251 - title: entry_title.to_string().into() 252 - }, 253 - class: "notebook-entry-preview-link", 254 - 255 - div { class: "notebook-entry-preview notebook-entry-preview-first", 256 - div { class: "entry-preview-header", 294 + div { class: "notebook-entry-preview notebook-entry-preview-first", 295 + div { class: "entry-preview-header", 296 + Link { 297 + to: Route::EntryPage { 298 + ident: ident.clone(), 299 + book_title: book_title.clone(), 300 + title: entry_title.to_string().into() 301 + }, 302 + class: "entry-preview-title-link", 257 303 div { class: "entry-preview-title", "{entry_title}" } 258 - if let Some(ref date) = created_at { 259 - div { class: "entry-preview-date", "{date}" } 304 + } 305 + if let Some(ref date) = created_at { 306 + div { class: "entry-preview-date", "{date}" } 307 + } 308 + if is_owner { 309 + crate::components::EntryActions { 310 + entry_uri, 311 + entry_title: entry_title.to_string(), 312 + in_notebook: true, 313 + notebook_title: Some(book_title.clone()) 260 314 } 261 315 } 262 - if let Some(ref html) = preview_html { 316 + } 317 + if let Some(ref html) = preview_html { 318 + Link { 319 + to: Route::EntryPage { 320 + ident: ident.clone(), 321 + book_title: book_title.clone(), 322 + title: entry_title.to_string().into() 323 + }, 324 + class: "entry-preview-content-link", 263 325 div { class: "entry-preview-content", dangerous_inner_html: "{html}" } 264 326 } 265 327 } ··· 296 358 let created_at = from_data::<Entry>(&last_entry.entry.record).ok() 297 359 .map(|entry| entry.created_at.as_ref().format("%B %d, %Y").to_string()); 298 360 361 + let entry_uri = last_entry.entry.uri.clone().into_static(); 362 + 299 363 rsx! { 300 - Link { 301 - to: Route::EntryPage { 302 - ident: ident.clone(), 303 - book_title: book_title.clone(), 304 - title: entry_title.to_string().into() 305 - }, 306 - class: "notebook-entry-preview-link", 307 - 308 - div { class: "notebook-entry-preview notebook-entry-preview-last", 309 - div { class: "entry-preview-header", 364 + div { class: "notebook-entry-preview notebook-entry-preview-last", 365 + div { class: "entry-preview-header", 366 + Link { 367 + to: Route::EntryPage { 368 + ident: ident.clone(), 369 + book_title: book_title.clone(), 370 + title: entry_title.to_string().into() 371 + }, 372 + class: "entry-preview-title-link", 310 373 div { class: "entry-preview-title", "{entry_title}" } 311 - if let Some(ref date) = created_at { 312 - div { class: "entry-preview-date", "{date}" } 374 + } 375 + if let Some(ref date) = created_at { 376 + div { class: "entry-preview-date", "{date}" } 377 + } 378 + if is_owner { 379 + crate::components::EntryActions { 380 + entry_uri, 381 + entry_title: entry_title.to_string(), 382 + in_notebook: true, 383 + notebook_title: Some(book_title.clone()) 313 384 } 314 385 } 315 - if let Some(ref html) = preview_html { 386 + } 387 + if let Some(ref html) = preview_html { 388 + Link { 389 + to: Route::EntryPage { 390 + ident: ident.clone(), 391 + book_title: book_title.clone(), 392 + title: entry_title.to_string().into() 393 + }, 394 + class: "entry-preview-content-link", 316 395 div { class: "entry-preview-content", dangerous_inner_html: "{html}" } 317 396 } 318 397 }
+5
crates/weaver-app/src/components/mod.rs
··· 128 128 pub mod button; 129 129 pub mod dialog; 130 130 pub mod editor; 131 + pub mod entry_actions; 131 132 pub mod input; 133 + pub mod profile_actions; 134 + 135 + pub use entry_actions::EntryActions; 136 + pub use profile_actions::{ProfileActions, ProfileActionsMenubar};
+29 -1
crates/weaver-app/src/components/notebook_cover.rs
··· 1 1 #![allow(non_snake_case)] 2 2 3 + use crate::Route; 3 4 use crate::components::avatar::{Avatar, AvatarImage}; 5 + use crate::components::button::{Button, ButtonVariant}; 4 6 use dioxus::prelude::*; 7 + use jacquard::smol_str::SmolStr; 8 + use jacquard::types::ident::AtIdentifier; 5 9 use weaver_api::sh_weaver::notebook::NotebookView; 6 10 7 11 const NOTEBOOK_COVER_CSS: Asset = asset!("/assets/styling/notebook-cover.css"); 8 12 9 13 #[component] 10 - pub fn NotebookCover(notebook: NotebookView<'static>, title: String) -> Element { 14 + pub fn NotebookCover( 15 + notebook: NotebookView<'static>, 16 + title: String, 17 + #[props(default = false)] is_owner: bool, 18 + #[props(default)] ident: Option<AtIdentifier<'static>>, 19 + ) -> Element { 11 20 use jacquard::from_data; 12 21 use weaver_api::sh_weaver::notebook::book::Book; 13 22 ··· 65 74 div { class: "notebook-cover-tags", 66 75 for tag in tags.iter() { 67 76 span { class: "notebook-cover-tag", "{tag}" } 77 + } 78 + } 79 + } 80 + } 81 + 82 + // Owner actions 83 + if is_owner { 84 + if let Some(ref owner_ident) = ident { 85 + div { class: "notebook-cover-actions", 86 + Link { 87 + to: Route::NewDraft { 88 + ident: owner_ident.clone(), 89 + notebook: Some(SmolStr::from(title.as_str())) 90 + }, 91 + class: "notebook-cover-action-link", 92 + Button { 93 + variant: ButtonVariant::Outline, 94 + "+ Add Entry" 95 + } 68 96 } 69 97 } 70 98 }
+101
crates/weaver-app/src/components/profile_actions.rs
··· 1 + //! Actions sidebar/menubar for profile page. 2 + 3 + use crate::Route; 4 + use crate::auth::AuthState; 5 + use crate::components::button::{Button, ButtonVariant}; 6 + use dioxus::prelude::*; 7 + use jacquard::types::ident::AtIdentifier; 8 + 9 + const PROFILE_ACTIONS_CSS: Asset = asset!("/assets/styling/profile-actions.css"); 10 + 11 + /// Actions available on the profile page for the owner. 12 + #[component] 13 + pub fn ProfileActions(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 14 + let auth_state = use_context::<Signal<AuthState>>(); 15 + 16 + // Check if viewing own profile 17 + let is_owner = { 18 + let current_did = auth_state.read().did.clone(); 19 + match (&current_did, ident()) { 20 + (Some(did), AtIdentifier::Did(ref ident_did)) => *did == *ident_did, 21 + _ => false, 22 + } 23 + }; 24 + 25 + if !is_owner { 26 + return rsx! {}; 27 + } 28 + 29 + rsx! { 30 + document::Link { rel: "stylesheet", href: PROFILE_ACTIONS_CSS } 31 + 32 + aside { class: "profile-actions", 33 + div { class: "profile-actions-container", 34 + div { class: "profile-actions-list", 35 + Link { 36 + to: Route::NewDraft { ident: ident(), notebook: None }, 37 + class: "profile-action-link", 38 + Button { 39 + variant: ButtonVariant::Outline, 40 + "New Entry" 41 + } 42 + } 43 + 44 + // TODO: New Notebook button (disabled for now) 45 + Button { 46 + variant: ButtonVariant::Outline, 47 + disabled: true, 48 + "New Notebook" 49 + } 50 + 51 + Link { 52 + to: Route::DraftsList { ident: ident() }, 53 + class: "profile-action-link", 54 + Button { 55 + variant: ButtonVariant::Ghost, 56 + "Drafts" 57 + } 58 + } 59 + } 60 + } 61 + } 62 + } 63 + } 64 + 65 + /// Mobile-friendly menubar version of profile actions. 66 + #[component] 67 + pub fn ProfileActionsMenubar(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 68 + let auth_state = use_context::<Signal<AuthState>>(); 69 + 70 + let is_owner = { 71 + let current_did = auth_state.read().did.clone(); 72 + match (&current_did, ident()) { 73 + (Some(did), AtIdentifier::Did(ref ident_did)) => *did == *ident_did, 74 + _ => false, 75 + } 76 + }; 77 + 78 + if !is_owner { 79 + return rsx! {}; 80 + } 81 + 82 + rsx! { 83 + div { class: "profile-actions-menubar", 84 + Link { 85 + to: Route::NewDraft { ident: ident(), notebook: None }, 86 + Button { 87 + variant: ButtonVariant::Primary, 88 + "New Entry" 89 + } 90 + } 91 + 92 + Link { 93 + to: Route::DraftsList { ident: ident() }, 94 + Button { 95 + variant: ButtonVariant::Ghost, 96 + "Drafts" 97 + } 98 + } 99 + } 100 + } 101 + }
+84 -2
crates/weaver-app/src/data.rs
··· 518 518 None 519 519 } 520 520 }); 521 - (res, memo); 521 + (res, memo) 522 522 } 523 523 524 524 /// Fetches notebooks for a specific DID ··· 644 644 /// Fetches notebooks from UFOS client-side only (no SSR) 645 645 #[cfg(not(feature = "fullstack-server"))] 646 646 pub fn use_notebooks_from_ufos() -> ( 647 - Resource<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>, 647 + Resource<Option<Vec<serde_json::Value>>>, 648 648 Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>, 649 649 ) { 650 650 let fetcher = use_context::<crate::fetch::Fetcher>(); ··· 808 808 }); 809 809 let memo = use_memo(move || r.read().as_ref().and_then(|v| v.clone())); 810 810 (r, memo) 811 + } 812 + 813 + // ============================================================================ 814 + // Ownership Checking 815 + // ============================================================================ 816 + 817 + /// Check if the current authenticated user owns a resource identified by an AtIdentifier. 818 + /// 819 + /// Returns a memo that is: 820 + /// - `Some(true)` if the user is authenticated and their DID matches the resource owner 821 + /// - `Some(false)` if the user is authenticated but doesn't match, or resource is a handle 822 + /// - `None` if the user is not authenticated 823 + /// 824 + /// For handles, this does a synchronous check that returns `false` since we can't resolve 825 + /// handles synchronously. Use `use_is_owner_async` for handle resolution. 826 + pub fn use_is_owner(resource_owner: ReadSignal<AtIdentifier<'static>>) -> Memo<Option<bool>> { 827 + let auth_state = use_context::<Signal<AuthState>>(); 828 + 829 + use_memo(move || { 830 + let current_did = auth_state.read().did.clone()?; 831 + let owner = resource_owner(); 832 + 833 + match owner { 834 + AtIdentifier::Did(did) => Some(did == current_did), 835 + AtIdentifier::Handle(_) => Some(false), // Can't resolve synchronously 836 + } 837 + }) 838 + } 839 + 840 + /// Check ownership with async handle resolution. 841 + /// 842 + /// Returns a resource that resolves to: 843 + /// - `Some(true)` if the user owns the resource 844 + /// - `Some(false)` if the user doesn't own the resource 845 + /// - `None` if the user is not authenticated 846 + #[cfg(feature = "fullstack-server")] 847 + pub fn use_is_owner_async( 848 + resource_owner: ReadSignal<AtIdentifier<'static>>, 849 + ) -> Resource<Option<bool>> { 850 + let auth_state = use_context::<Signal<AuthState>>(); 851 + let fetcher = use_context::<crate::fetch::Fetcher>(); 852 + 853 + use_resource(move || { 854 + let fetcher = fetcher.clone(); 855 + let owner = resource_owner(); 856 + async move { 857 + let current_did = auth_state.read().did.clone()?; 858 + 859 + match owner { 860 + AtIdentifier::Did(did) => Some(did == current_did), 861 + AtIdentifier::Handle(handle) => match fetcher.resolve_handle(&handle).await { 862 + Ok(resolved_did) => Some(resolved_did == current_did), 863 + Err(_) => Some(false), 864 + }, 865 + } 866 + } 867 + }) 868 + } 869 + 870 + /// Check ownership with async handle resolution (client-only mode). 871 + #[cfg(not(feature = "fullstack-server"))] 872 + pub fn use_is_owner_async( 873 + resource_owner: ReadSignal<AtIdentifier<'static>>, 874 + ) -> Resource<Option<bool>> { 875 + let auth_state = use_context::<Signal<AuthState>>(); 876 + let fetcher = use_context::<crate::fetch::Fetcher>(); 877 + 878 + use_resource(move || { 879 + let fetcher = fetcher.clone(); 880 + let owner = resource_owner(); 881 + async move { 882 + let current_did = auth_state.read().did.clone()?; 883 + 884 + match owner { 885 + AtIdentifier::Did(did) => Some(did == current_did), 886 + AtIdentifier::Handle(handle) => match fetcher.resolve_handle(&handle).await { 887 + Ok(resolved_did) => Some(resolved_did == current_did), 888 + Err(_) => Some(false), 889 + }, 890 + } 891 + } 892 + }) 811 893 } 812 894 813 895 #[cfg(feature = "fullstack-server")]
+22 -2
crates/weaver-app/src/main.rs
··· 17 17 use std::sync::{Arc, LazyLock}; 18 18 #[allow(unused)] 19 19 use views::{ 20 - Callback, Editor, Home, Navbar, Notebook, NotebookIndex, NotebookPage, RecordIndex, RecordPage, 20 + Callback, DraftEdit, DraftsList, Editor, Home, Navbar, NewDraft, Notebook, NotebookEntryByRkey, 21 + NotebookEntryEdit, NotebookIndex, NotebookPage, RecordIndex, RecordPage, StandaloneEntry, 22 + StandaloneEntryEdit, 21 23 }; 22 24 23 25 use crate::{ ··· 69 71 #[layout(Repository)] 70 72 #[route("/")] 71 73 RepositoryIndex { ident: AtIdentifier<'static> }, 74 + // Drafts routes (before /:book_title to avoid capture) 75 + #[route("/drafts")] 76 + DraftsList { ident: AtIdentifier<'static> }, 77 + #[route("/drafts/:tid")] 78 + DraftEdit { ident: AtIdentifier<'static>, tid: SmolStr }, 79 + #[route("/new?:notebook")] 80 + NewDraft { ident: AtIdentifier<'static>, notebook: Option<SmolStr> }, 81 + // Standalone entry routes 82 + #[route("/e/:rkey")] 83 + StandaloneEntry { ident: AtIdentifier<'static>, rkey: SmolStr }, 84 + #[route("/e/:rkey/edit")] 85 + StandaloneEntryEdit { ident: AtIdentifier<'static>, rkey: SmolStr }, 86 + // Notebook routes 72 87 #[nest("/:book_title")] 73 88 #[layout(Notebook)] 74 89 #[route("/")] 75 90 NotebookIndex { ident: AtIdentifier<'static>, book_title: SmolStr }, 76 91 #[route("/:title")] 77 - EntryPage { ident: AtIdentifier<'static>, book_title: SmolStr, title: SmolStr } 92 + EntryPage { ident: AtIdentifier<'static>, book_title: SmolStr, title: SmolStr }, 93 + // Entry by rkey (canonical path) 94 + #[route("/e/:rkey")] 95 + NotebookEntryByRkey { ident: AtIdentifier<'static>, book_title: SmolStr, rkey: SmolStr }, 96 + #[route("/e/:rkey/edit")] 97 + NotebookEntryEdit { ident: AtIdentifier<'static>, book_title: SmolStr, rkey: SmolStr } 78 98 79 99 } 80 100 const FAVICON: Asset = asset!("/assets/weaver_photo_sm.jpg");
+263
crates/weaver-app/src/views/drafts.rs
··· 1 + //! Drafts and standalone entry views. 2 + 3 + use crate::Route; 4 + use crate::auth::AuthState; 5 + use crate::components::button::{Button, ButtonVariant}; 6 + use crate::components::dialog::{DialogContent, DialogDescription, DialogRoot, DialogTitle}; 7 + use crate::components::editor::{delete_draft, list_drafts}; 8 + use dioxus::prelude::*; 9 + use jacquard::smol_str::SmolStr; 10 + use jacquard::types::ident::AtIdentifier; 11 + 12 + const DRAFTS_CSS: Asset = asset!("/assets/styling/drafts.css"); 13 + 14 + /// Drafts list page - shows all drafts for the authenticated user. 15 + #[component] 16 + pub fn DraftsList(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 17 + // ALL hooks must be called unconditionally at the top 18 + let auth_state = use_context::<Signal<AuthState>>(); 19 + let navigator = use_navigator(); 20 + let mut drafts = use_signal(list_drafts); 21 + let mut show_delete_confirm = use_signal(|| None::<String>); 22 + 23 + // Check ownership - redirect if not viewing own drafts 24 + let current_did = auth_state.read().did.clone(); 25 + let is_owner = match (&current_did, ident()) { 26 + (Some(did), AtIdentifier::Did(ref ident_did)) => *did == *ident_did, 27 + _ => false, 28 + }; 29 + 30 + // Redirect non-owners 31 + let ident_for_redirect = ident(); 32 + use_effect(move || { 33 + if !is_owner { 34 + navigator.replace(Route::RepositoryIndex { 35 + ident: ident_for_redirect.clone(), 36 + }); 37 + } 38 + }); 39 + 40 + if !is_owner { 41 + return rsx! { div { "Redirecting..." } }; 42 + } 43 + 44 + let mut handle_delete = move |key: String| { 45 + delete_draft(&key); 46 + drafts.set(list_drafts()); 47 + show_delete_confirm.set(None); 48 + }; 49 + 50 + rsx! { 51 + document::Link { rel: "stylesheet", href: DRAFTS_CSS } 52 + document::Title { "Drafts" } 53 + 54 + div { class: "drafts-page", 55 + div { class: "drafts-header", 56 + h1 { "Drafts" } 57 + Link { 58 + to: Route::NewDraft { ident: ident(), notebook: None }, 59 + Button { 60 + variant: ButtonVariant::Primary, 61 + "New Draft" 62 + } 63 + } 64 + } 65 + 66 + if drafts().is_empty() { 67 + div { class: "drafts-empty", 68 + p { "No drafts yet." } 69 + p { "Start writing something new!" } 70 + } 71 + } else { 72 + div { class: "drafts-list", 73 + for (key, title, editing_uri) in drafts() { 74 + { 75 + let key_for_delete = key.clone(); 76 + let is_edit_draft = editing_uri.is_some(); 77 + let display_title = if title.is_empty() { "Untitled".to_string() } else { title }; 78 + let tid = key.strip_prefix("new:").unwrap_or(&key); 79 + 80 + rsx! { 81 + div { 82 + class: "draft-card", 83 + key: "{key}", 84 + 85 + Link { 86 + to: Route::DraftEdit { 87 + ident: ident(), 88 + tid: tid.to_string().into(), 89 + }, 90 + class: "draft-card-link", 91 + 92 + div { class: "draft-card-content", 93 + h3 { class: "draft-title", "{display_title}" } 94 + if is_edit_draft { 95 + span { class: "draft-badge draft-badge-edit", "Editing" } 96 + } else { 97 + span { class: "draft-badge draft-badge-new", "New" } 98 + } 99 + } 100 + } 101 + 102 + Button { 103 + variant: ButtonVariant::Ghost, 104 + onclick: move |_| show_delete_confirm.set(Some(key_for_delete.clone())), 105 + "×" 106 + } 107 + } 108 + } 109 + } 110 + } 111 + } 112 + } 113 + } 114 + 115 + // Delete confirmation 116 + DialogRoot { 117 + open: show_delete_confirm().is_some(), 118 + on_open_change: move |_: bool| show_delete_confirm.set(None), 119 + DialogContent { 120 + DialogTitle { "Delete Draft?" } 121 + DialogDescription { 122 + "This will permanently delete this draft." 123 + } 124 + div { class: "dialog-actions", 125 + Button { 126 + variant: ButtonVariant::Destructive, 127 + onclick: move |_| { 128 + if let Some(key) = show_delete_confirm() { 129 + handle_delete(key); 130 + } 131 + }, 132 + "Delete" 133 + } 134 + Button { 135 + variant: ButtonVariant::Ghost, 136 + onclick: move |_| show_delete_confirm.set(None), 137 + "Cancel" 138 + } 139 + } 140 + } 141 + } 142 + } 143 + } 144 + 145 + /// Edit an existing draft by TID. 146 + #[component] 147 + pub fn DraftEdit( 148 + ident: ReadSignal<AtIdentifier<'static>>, 149 + tid: ReadSignal<SmolStr>, 150 + ) -> Element { 151 + use crate::components::editor::MarkdownEditor; 152 + use crate::views::editor::EditorCss; 153 + 154 + // Draft key for "new" drafts is "new:{tid}" 155 + let draft_key = format!("new:{}", tid()); 156 + 157 + rsx! { 158 + EditorCss {} 159 + div { class: "editor-page", 160 + MarkdownEditor { entry_uri: Some(draft_key), target_notebook: None } 161 + } 162 + } 163 + } 164 + 165 + /// Create a new draft. 166 + #[component] 167 + pub fn NewDraft( 168 + ident: ReadSignal<AtIdentifier<'static>>, 169 + notebook: ReadSignal<Option<SmolStr>>, 170 + ) -> Element { 171 + use crate::components::editor::MarkdownEditor; 172 + use crate::views::editor::EditorCss; 173 + 174 + rsx! { 175 + EditorCss {} 176 + div { class: "editor-page", 177 + MarkdownEditor { 178 + entry_uri: None, 179 + target_notebook: notebook() 180 + } 181 + } 182 + } 183 + } 184 + 185 + /// View a standalone entry by rkey. 186 + #[component] 187 + pub fn StandaloneEntry( 188 + ident: ReadSignal<AtIdentifier<'static>>, 189 + rkey: ReadSignal<SmolStr>, 190 + ) -> Element { 191 + // Construct AT-URI for the entry 192 + let entry_uri = use_memo(move || { 193 + format!("at://{}/sh.weaver.notebook.entry/{}", ident(), rkey()) 194 + }); 195 + 196 + rsx! { 197 + div { class: "standalone-entry-page", 198 + p { "Standalone entry view - implementation pending" } 199 + p { "URI: {entry_uri}" } 200 + } 201 + } 202 + } 203 + 204 + /// Edit a standalone entry. 205 + #[component] 206 + pub fn StandaloneEntryEdit( 207 + ident: ReadSignal<AtIdentifier<'static>>, 208 + rkey: ReadSignal<SmolStr>, 209 + ) -> Element { 210 + use crate::components::editor::MarkdownEditor; 211 + use crate::views::editor::EditorCss; 212 + 213 + // Construct AT-URI for the entry 214 + let entry_uri = use_memo(move || { 215 + format!("at://{}/sh.weaver.notebook.entry/{}", ident(), rkey()) 216 + }); 217 + 218 + rsx! { 219 + EditorCss {} 220 + div { class: "editor-page", 221 + MarkdownEditor { entry_uri: Some(entry_uri()), target_notebook: None } 222 + } 223 + } 224 + } 225 + 226 + /// View a notebook entry by rkey. 227 + #[component] 228 + pub fn NotebookEntryByRkey( 229 + ident: ReadSignal<AtIdentifier<'static>>, 230 + book_title: ReadSignal<SmolStr>, 231 + rkey: ReadSignal<SmolStr>, 232 + ) -> Element { 233 + rsx! { 234 + div { class: "notebook-entry-rkey-page", 235 + p { "Notebook entry by rkey - implementation pending" } 236 + p { "Notebook: {book_title}" } 237 + p { "Entry rkey: {rkey}" } 238 + } 239 + } 240 + } 241 + 242 + /// Edit a notebook entry by rkey. 243 + #[component] 244 + pub fn NotebookEntryEdit( 245 + ident: ReadSignal<AtIdentifier<'static>>, 246 + book_title: ReadSignal<SmolStr>, 247 + rkey: ReadSignal<SmolStr>, 248 + ) -> Element { 249 + use crate::components::editor::MarkdownEditor; 250 + use crate::views::editor::EditorCss; 251 + 252 + // Construct AT-URI for the entry 253 + let entry_uri = use_memo(move || { 254 + format!("at://{}/sh.weaver.notebook.entry/{}", ident(), rkey()) 255 + }); 256 + 257 + rsx! { 258 + EditorCss {} 259 + div { class: "editor-page", 260 + MarkdownEditor { entry_uri: Some(entry_uri()), target_notebook: Some(book_title()) } 261 + } 262 + } 263 + }
+1 -1
crates/weaver-app/src/views/editor.rs
··· 12 12 rsx! { 13 13 EditorCss {} 14 14 div { class: "editor-page", 15 - MarkdownEditor { entry_uri: entry } 15 + MarkdownEditor { entry_uri: entry, target_notebook: None } 16 16 } 17 17 } 18 18 }
+6
crates/weaver-app/src/views/mod.rs
··· 28 28 29 29 mod editor; 30 30 pub use editor::Editor; 31 + 32 + mod drafts; 33 + pub use drafts::{ 34 + DraftEdit, DraftsList, NewDraft, NotebookEntryByRkey, NotebookEntryEdit, StandaloneEntry, 35 + StandaloneEntryEdit, 36 + };
+15 -1
crates/weaver-app/src/views/notebook.rs
··· 1 1 use crate::{ 2 2 Route, 3 + auth::AuthState, 3 4 components::{EntryCard, NotebookCover, NotebookCss}, 5 + components::button::{Button, ButtonVariant}, 4 6 data, 5 7 }; 6 8 use dioxus::prelude::*; ··· 50 52 #[cfg(feature = "fullstack-server")] 51 53 entries_result?; 52 54 55 + // Check ownership for "Add Entry" button 56 + let auth_state = use_context::<Signal<AuthState>>(); 57 + let is_owner = { 58 + let current_did = auth_state.read().did.clone(); 59 + match (&current_did, ident()) { 60 + (Some(did), AtIdentifier::Did(ref ident_did)) => *did == *ident_did, 61 + _ => false, 62 + } 63 + }; 64 + 53 65 rsx! { 54 66 document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS } 55 67 ··· 63 75 aside { class: "notebook-sidebar", 64 76 NotebookCover { 65 77 notebook: notebook_view.clone(), 66 - title: book_title().to_string() 78 + title: book_title().to_string(), 79 + is_owner, 80 + ident: Some(ident()) 67 81 } 68 82 } 69 83
+150 -76
crates/weaver-common/src/agent.rs
··· 5 5 }; 6 6 7 7 // Re-export jacquard for convenience 8 + use crate::constellation::{GetBacklinksQuery, RecordId}; 8 9 use crate::error::WeaverError; 9 10 pub use jacquard; 10 11 use jacquard::bytes::Bytes; ··· 14 15 use jacquard::types::blob::{BlobRef, MimeType}; 15 16 use jacquard::types::string::{AtUri, Did, RecordKey}; 16 17 use jacquard::types::tid::Tid; 18 + use jacquard::types::uri::Uri; 19 + use jacquard::url::Url; 17 20 use jacquard::xrpc::Response; 18 - use jacquard::{IntoStatic, xrpc}; 21 + use jacquard::{CowStr, IntoStatic, xrpc}; 19 22 use mime_sniffer::MimeTypeSniffer; 20 23 use std::path::Path; 21 24 use weaver_api::com_atproto::repo::get_record::GetRecordResponse; ··· 24 27 use weaver_api::sh_weaver::publish::blob::Blob as PublishedBlob; 25 28 26 29 use crate::{PublishResult, W_TICKER, normalize_title_path}; 30 + 31 + const CONSTELLATION_URL: &str = "https://constellation.microcosm.blue"; 27 32 28 33 /// Extension trait providing weaver-specific multi-step operations on Agent 29 34 /// ··· 157 162 AgentError::from(ClientError::from(e).with_context("Failed to resolve PDS for DID")) 158 163 })?; 159 164 160 - // Search for existing notebook with this title 161 - let resp = self 162 - .xrpc(pds_url) 163 - .send( 164 - &ListRecords::new() 165 - .repo(author_did.clone()) 166 - .collection(Nsid::raw(Book::NSID)) 167 - .limit(100) 168 - .build(), 169 - ) 170 - .await 171 - .map_err(|e| AgentError::from(ClientError::from(e)))?; 165 + // Search for existing notebook with this title (paginated) 166 + let mut cursor: Option<CowStr<'static>> = None; 167 + loop { 168 + let resp = self 169 + .xrpc(pds_url.clone()) 170 + .send( 171 + &ListRecords::new() 172 + .repo(author_did.clone()) 173 + .collection(Nsid::raw(Book::NSID)) 174 + .limit(100) 175 + .maybe_cursor(cursor.clone()) 176 + .build(), 177 + ) 178 + .await 179 + .map_err(|e| AgentError::from(ClientError::from(e)))?; 172 180 173 - if let Ok(list) = resp.parse() { 181 + let list = match resp.parse() { 182 + Ok(l) => l, 183 + Err(_) => break, // Parse error, stop searching 184 + }; 185 + 174 186 for record in list.records { 175 187 let notebook: Book = jacquard::from_data(&record.value).map_err(|_| { 176 188 AgentError::from(ClientError::invalid_request( ··· 188 200 .collect(); 189 201 return Ok((record.uri.into_static(), entries)); 190 202 } 203 + } 204 + 205 + match list.cursor { 206 + Some(c) => cursor = Some(c.into_static()), 207 + None => break, // No more pages 191 208 } 192 209 } 193 210 ··· 235 252 // Find or create notebook 236 253 let (notebook_uri, entry_refs) = self.upsert_notebook(notebook_title, &did).await?; 237 254 255 + // Fast path: if notebook is empty, skip search and create directly 256 + if entry_refs.is_empty() { 257 + let response = self.create_record(entry, None).await?; 258 + let new_ref = StrongRef::new() 259 + .uri(response.uri.clone().into_static()) 260 + .cid(response.cid.clone().into_static()) 261 + .build(); 262 + 263 + use weaver_api::sh_weaver::notebook::book::Book; 264 + let notebook_entry_ref = StrongRef::new() 265 + .uri(response.uri.into_static()) 266 + .cid(response.cid.into_static()) 267 + .build(); 268 + 269 + self.update_record::<Book>(&notebook_uri, |book| { 270 + book.entry_list.push(notebook_entry_ref); 271 + }) 272 + .await?; 273 + 274 + return Ok((new_ref, true)); 275 + } 276 + 238 277 // Check if entry with this title exists in the notebook 278 + // O(n) network calls - unavoidable without title indexing 239 279 for entry_ref in &entry_refs { 240 280 let existing = self 241 281 .get_record::<entry::Entry>(&entry_ref.uri) ··· 390 430 } 391 431 392 432 /// Search for an entry by title within a notebook's entry list 433 + /// 434 + /// O(n) network calls - unavoidable without title indexing. 435 + /// Breaks early on match to minimize unnecessary fetches. 393 436 fn entry_by_title<'a>( 394 437 &self, 395 438 notebook: &NotebookView<'a>, ··· 478 521 } 479 522 }; 480 523 481 - // TODO: use the cursor to search through all records with this NSID for the repo 482 - let resp = self 483 - .xrpc(pds_url) 484 - .send( 485 - &ListRecords::new() 486 - .repo(repo_did) 487 - .collection(Nsid::raw(Book::NSID)) 488 - .limit(100) 489 - .build(), 490 - ) 491 - .await 492 - .map_err(|e| AgentError::from(ClientError::from(e)))?; 524 + // Search with pagination 525 + let mut cursor: Option<CowStr<'static>> = None; 526 + loop { 527 + let resp = self 528 + .xrpc(pds_url.clone()) 529 + .send( 530 + &ListRecords::new() 531 + .repo(repo_did.clone()) 532 + .collection(Nsid::raw(Book::NSID)) 533 + .limit(100) 534 + .maybe_cursor(cursor.clone()) 535 + .build(), 536 + ) 537 + .await 538 + .map_err(|e| AgentError::from(ClientError::from(e)))?; 539 + 540 + let list = match resp.parse() { 541 + Ok(l) => l, 542 + Err(_) => break, 543 + }; 493 544 494 - if let Ok(list) = resp.parse() { 495 545 for record in list.records { 496 546 let notebook: Book = jacquard::from_data(&record.value).map_err(|_| { 497 547 AgentError::from(ClientError::invalid_request( 498 548 "Failed to parse notebook record", 499 549 )) 500 550 })?; 501 - if let Some(book_title) = notebook.path 502 - && book_title == title 503 - { 504 - let tags = notebook.tags.clone(); 505 551 506 - let mut authors = Vec::new(); 507 - 508 - for (index, author) in notebook.authors.iter().enumerate() { 509 - let (profile_uri, profile_view) = 510 - self.hydrate_profile_view(&author.did).await?; 511 - authors.push( 512 - AuthorListView::new() 513 - .maybe_uri(profile_uri) 514 - .record(profile_view) 515 - .index(index as i64) 516 - .build(), 517 - ); 518 - } 519 - let entries = notebook 520 - .entry_list 521 - .iter() 522 - .cloned() 523 - .map(IntoStatic::into_static) 524 - .collect(); 525 - 526 - return Ok(Some(( 527 - NotebookView::new() 528 - .cid(record.cid) 529 - .uri(record.uri) 530 - .indexed_at(jacquard::types::string::Datetime::now()) 531 - .title(book_title) 532 - .maybe_tags(tags) 533 - .authors(authors) 534 - .record(record.value.clone()) 535 - .build() 536 - .into_static(), 537 - entries, 538 - ))); 539 - } else if let Some(book_title) = notebook.title 540 - && book_title == title 552 + // Match on path first, then title 553 + let matched_title = if let Some(ref path) = notebook.path 554 + && path.as_ref() == title 541 555 { 556 + Some(path.clone()) 557 + } else if let Some(ref book_title) = notebook.title 558 + && book_title.as_ref() == title 559 + { 560 + Some(book_title.clone()) 561 + } else { 562 + None 563 + }; 564 + 565 + if let Some(matched) = matched_title { 542 566 let tags = notebook.tags.clone(); 543 567 544 568 let mut authors = Vec::new(); 545 - use weaver_api::app_bsky::actor::{ 546 - ProfileViewDetailed, get_profile::GetProfile, 547 - profile::Profile as BskyProfile, 548 - }; 549 - use weaver_api::sh_weaver::actor::{ 550 - ProfileDataView, ProfileDataViewInner, ProfileView, 551 - profile::Profile as WeaverProfile, 552 - }; 553 - 554 569 for (index, author) in notebook.authors.iter().enumerate() { 555 570 let (profile_uri, profile_view) = 556 571 self.hydrate_profile_view(&author.did).await?; ··· 562 577 .build(), 563 578 ); 564 579 } 580 + 565 581 let entries = notebook 566 582 .entry_list 567 583 .iter() ··· 574 590 .cid(record.cid) 575 591 .uri(record.uri) 576 592 .indexed_at(jacquard::types::string::Datetime::now()) 577 - .title(book_title) 593 + .title(matched) 578 594 .maybe_tags(tags) 579 595 .authors(authors) 580 596 .record(record.value.clone()) ··· 583 599 entries, 584 600 ))); 585 601 } 602 + } 603 + 604 + match list.cursor { 605 + Some(c) => cursor = Some(c.into_static()), 606 + None => break, 586 607 } 587 608 } 588 609 ··· 903 924 .title(title) 904 925 .authors(notebook.authors.clone()) 905 926 .build()) 927 + } 928 + } 929 + 930 + /// Find the notebook that contains a given entry using constellation backlinks. 931 + /// 932 + /// Queries constellation for `sh.weaver.notebook.book` records that reference 933 + /// the given entry URI via the `.entryList[].uri` path. 934 + fn find_notebook_for_entry( 935 + &self, 936 + entry_uri: &AtUri<'_>, 937 + ) -> impl Future<Output = Result<Option<RecordId<'static>>, WeaverError>> 938 + where 939 + Self: Sized, 940 + { 941 + async move { 942 + let constellation_url = Url::parse(CONSTELLATION_URL).map_err(|e| { 943 + AgentError::from(ClientError::invalid_request(format!( 944 + "Invalid constellation URL: {}", 945 + e 946 + ))) 947 + })?; 948 + 949 + let query = GetBacklinksQuery { 950 + subject: Uri::At(entry_uri.clone().into_static()), 951 + source: "sh.weaver.notebook.book:.entryList[].uri".into(), 952 + cursor: None, 953 + did: vec![], 954 + limit: 1, 955 + }; 956 + 957 + let response = self 958 + .xrpc(constellation_url) 959 + .send(&query) 960 + .await 961 + .map_err(|e| { 962 + AgentError::from(ClientError::invalid_request(format!( 963 + "Constellation query failed: {}", 964 + e 965 + ))) 966 + })?; 967 + 968 + let output = response.into_output().map_err(|e| { 969 + AgentError::from(ClientError::invalid_request(format!( 970 + "Failed to parse constellation response: {}", 971 + e 972 + ))) 973 + })?; 974 + 975 + Ok(output 976 + .records 977 + .into_iter() 978 + .next() 979 + .map(|r| r.into_static())) 906 980 } 907 981 } 908 982 }
+31
lexicons/collab/accept.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.collab.accept", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Acceptance of a collaboration invite. Completes the two-way agreement.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["invite", "resource", "createdAt"], 12 + "properties": { 13 + "invite": { 14 + "type": "ref", 15 + "ref": "com.atproto.repo.strongRef", 16 + "description": "Reference to the invite record being accepted." 17 + }, 18 + "resource": { 19 + "type": "string", 20 + "format": "at-uri", 21 + "description": "URI of the resource (denormalized for easier querying)." 22 + }, 23 + "createdAt": { 24 + "type": "string", 25 + "format": "datetime" 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+18
lexicons/collab/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.collab.defs", 4 + "defs": { 5 + "notebook": { 6 + "type": "token", 7 + "description": "Collaboration scoped to an entire notebook." 8 + }, 9 + "entry": { 10 + "type": "token", 11 + "description": "Collaboration scoped to a single entry." 12 + }, 13 + "chapter": { 14 + "type": "token", 15 + "description": "Collaboration scoped to a chapter." 16 + } 17 + } 18 + }
+56
lexicons/collab/invite.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.collab.invite", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Invitation to collaborate on a resource (notebook, entry, chapter, etc.). Creates half of a two-way agreement.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["resource", "invitee", "createdAt"], 12 + "properties": { 13 + "resource": { 14 + "type": "ref", 15 + "ref": "com.atproto.repo.strongRef", 16 + "description": "The resource to collaborate on (notebook, entry, chapter, etc.)." 17 + }, 18 + "invitee": { 19 + "type": "string", 20 + "format": "did", 21 + "description": "DID of the user being invited." 22 + }, 23 + "scope": { 24 + "type": "ref", 25 + "ref": "#collabScope", 26 + "description": "Optional explicit scope type. If omitted, inferred from resource lexicon." 27 + }, 28 + "message": { 29 + "type": "string", 30 + "maxGraphemes": 300, 31 + "maxLength": 3000, 32 + "description": "Optional message to the invitee." 33 + }, 34 + "createdAt": { 35 + "type": "string", 36 + "format": "datetime" 37 + }, 38 + "expiresAt": { 39 + "type": "string", 40 + "format": "datetime", 41 + "description": "Optional expiration for the invite." 42 + } 43 + } 44 + } 45 + }, 46 + "collabScope": { 47 + "type": "string", 48 + "description": "The scope/type of collaboration.", 49 + "knownValues": [ 50 + "sh.weaver.collab.defs#notebook", 51 + "sh.weaver.collab.defs#entry", 52 + "sh.weaver.collab.defs#chapter" 53 + ] 54 + } 55 + } 56 + }
+4
lexicons/edit/diff.json
··· 32 32 "doc": { 33 33 "type": "ref", 34 34 "ref": "sh.weaver.edit.defs#docRef" 35 + }, 36 + "createdAt": { 37 + "type": "string", 38 + "format": "datetime" 35 39 } 36 40 } 37 41 }
+32
lexicons/graph/bookmark.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.bookmark", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Bookmark a notebook or entry for later reading.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "ref", 15 + "ref": "com.atproto.repo.strongRef", 16 + "description": "The notebook or entry being bookmarked." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + }, 22 + "note": { 23 + "type": "string", 24 + "maxGraphemes": 300, 25 + "maxLength": 3000, 26 + "description": "Optional private note about why you saved this." 27 + } 28 + } 29 + } 30 + } 31 + } 32 + }
+14
lexicons/graph/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.defs", 4 + "defs": { 5 + "curatelist": { 6 + "type": "token", 7 + "description": "A curated collection of notebooks/entries for sharing." 8 + }, 9 + "readinglist": { 10 + "type": "token", 11 + "description": "A personal reading list." 12 + } 13 + } 14 + }
+26
lexicons/graph/follow.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.follow", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Request to follow an author. Requires acceptance to be active.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "string", 15 + "format": "did", 16 + "description": "DID of the author to follow." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+26
lexicons/graph/followAccept.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.followAccept", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Acceptance of a follow request. Completes the two-way agreement.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["follow", "createdAt"], 12 + "properties": { 13 + "follow": { 14 + "type": "ref", 15 + "ref": "com.atproto.repo.strongRef", 16 + "description": "Reference to the follow record being accepted." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+31
lexicons/graph/followGate.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.followGate", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Settings controlling follow approval behavior. Absence means auto-accept.", 8 + "key": "self", 9 + "record": { 10 + "type": "object", 11 + "required": ["createdAt"], 12 + "properties": { 13 + "requireApproval": { 14 + "type": "boolean", 15 + "default": false, 16 + "description": "If true, follows require manual acceptance." 17 + }, 18 + "invalidatePrior": { 19 + "type": "boolean", 20 + "default": false, 21 + "description": "If true, previously auto-accepted follows are invalidated when requireApproval is enabled. Appview should treat followAccept records created before this gate's createdAt as invalid." 22 + }, 23 + "createdAt": { 24 + "type": "string", 25 + "format": "datetime" 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+26
lexicons/graph/like.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.like", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record declaring a 'like' of a notebook or entry.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "ref", 15 + "ref": "com.atproto.repo.strongRef", 16 + "description": "The notebook or entry being liked." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+49
lexicons/graph/list.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.list", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A curated list of notebooks and/or entries.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["name", "purpose", "createdAt"], 12 + "properties": { 13 + "name": { 14 + "type": "string", 15 + "minLength": 1, 16 + "maxLength": 64, 17 + "description": "Display name for the list." 18 + }, 19 + "purpose": { 20 + "type": "ref", 21 + "ref": "#listPurpose", 22 + "description": "The purpose/type of list." 23 + }, 24 + "description": { 25 + "type": "string", 26 + "maxGraphemes": 300, 27 + "maxLength": 3000 28 + }, 29 + "avatar": { 30 + "type": "blob", 31 + "accept": ["image/png", "image/jpeg"], 32 + "maxSize": 1000000 33 + }, 34 + "createdAt": { 35 + "type": "string", 36 + "format": "datetime" 37 + } 38 + } 39 + } 40 + }, 41 + "listPurpose": { 42 + "type": "string", 43 + "knownValues": [ 44 + "sh.weaver.graph.defs#curatelist", 45 + "sh.weaver.graph.defs#readinglist" 46 + ] 47 + } 48 + } 49 + }
+31
lexicons/graph/listitem.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.listitem", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "An item in a list.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "list", "createdAt"], 12 + "properties": { 13 + "subject": { 14 + "type": "ref", 15 + "ref": "com.atproto.repo.strongRef", 16 + "description": "The notebook or entry being added to the list." 17 + }, 18 + "list": { 19 + "type": "string", 20 + "format": "at-uri", 21 + "description": "Reference to the list record." 22 + }, 23 + "createdAt": { 24 + "type": "string", 25 + "format": "datetime" 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+26
lexicons/graph/subscribe.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.subscribe", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Request to subscribe to a notebook. Requires acceptance to be active.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["notebook", "createdAt"], 12 + "properties": { 13 + "notebook": { 14 + "type": "string", 15 + "format": "at-uri", 16 + "description": "URI of the notebook to subscribe to." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+26
lexicons/graph/subscribeAccept.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.graph.subscribeAccept", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Acceptance of a subscription request.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subscribe", "createdAt"], 12 + "properties": { 13 + "subscribe": { 14 + "type": "ref", 15 + "ref": "com.atproto.repo.strongRef", 16 + "description": "Reference to the subscribe record being accepted." 17 + }, 18 + "createdAt": { 19 + "type": "string", 20 + "format": "datetime" 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+5
lexicons/notebook/book.json
··· 35 35 "type": "string", 36 36 "format": "datetime", 37 37 "description": "Client-declared timestamp when this was originally created." 38 + }, 39 + "updatedAt": { 40 + "type": "string", 41 + "format": "datetime", 42 + "description": "Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios." 38 43 } 39 44 } 40 45 }
+5
lexicons/notebook/entry.json
··· 25 25 "format": "datetime", 26 26 "description": "Client-declared timestamp when this was originally created." 27 27 }, 28 + "updatedAt": { 29 + "type": "string", 30 + "format": "datetime", 31 + "description": "Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios." 32 + }, 28 33 "embeds": { 29 34 "type": "object", 30 35 "description": "The set of images and records, if any, embedded in the notebook entry.",
+35
lexicons/notebook/getEntry.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.getEntry", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get an entry view by notebook URI and index, including prev/next navigation.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["notebook"], 11 + "properties": { 12 + "notebook": { 13 + "type": "string", 14 + "format": "at-uri", 15 + "description": "AT-URI of the notebook containing the entry." 16 + }, 17 + "index": { 18 + "type": "integer", 19 + "minimum": 0, 20 + "default": 0, 21 + "description": "Zero-based index of the entry in the notebook's entry list." 22 + } 23 + } 24 + }, 25 + "output": { 26 + "encoding": "application/json", 27 + "schema": { 28 + "type": "ref", 29 + "ref": "sh.weaver.notebook.defs#bookEntryView" 30 + } 31 + }, 32 + "errors": [{ "name": "NotebookNotFound" }, { "name": "EntryNotFound" }] 33 + } 34 + } 35 + }
+47
lexicons/notebook/getEntryByTitle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.getEntryByTitle", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get an entry view by notebook URI and title. Matches on either the entry's path or title field.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["notebook", "title"], 11 + "properties": { 12 + "notebook": { 13 + "type": "string", 14 + "format": "at-uri", 15 + "description": "AT-URI of the notebook containing the entry." 16 + }, 17 + "title": { 18 + "type": "string", 19 + "maxLength": 300, 20 + "description": "Title or path of the entry to fetch." 21 + } 22 + } 23 + }, 24 + "output": { 25 + "encoding": "application/json", 26 + "schema": { 27 + "type": "object", 28 + "required": ["entry", "record"], 29 + "properties": { 30 + "entry": { 31 + "type": "ref", 32 + "ref": "sh.weaver.notebook.defs#bookEntryView" 33 + }, 34 + "record": { 35 + "type": "unknown", 36 + "description": "The raw entry record data." 37 + } 38 + } 39 + } 40 + }, 41 + "errors": [ 42 + { "name": "NotebookNotFound" }, 43 + { "name": "EntryNotFound" } 44 + ] 45 + } 46 + } 47 + }
+41
lexicons/notebook/getNotebook.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.getNotebook", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a notebook view by its AT-URI, including hydrated author profiles and entry list.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["notebook"], 11 + "properties": { 12 + "notebook": { 13 + "type": "string", 14 + "format": "at-uri", 15 + "description": "AT-URI of the notebook to fetch." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["notebook", "entries"], 24 + "properties": { 25 + "notebook": { 26 + "type": "ref", 27 + "ref": "sh.weaver.notebook.defs#notebookView" 28 + }, 29 + "entries": { 30 + "type": "array", 31 + "items": { 32 + "type": "ref", 33 + "ref": "com.atproto.repo.strongRef" 34 + } 35 + } 36 + } 37 + } 38 + } 39 + } 40 + } 41 + }
+49
lexicons/notebook/getNotebookByTitle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.getNotebookByTitle", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a notebook view by actor and title. Matches on either the notebook's path or title field.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor", "title"], 11 + "properties": { 12 + "actor": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "Handle or DID of the notebook owner." 16 + }, 17 + "title": { 18 + "type": "string", 19 + "maxLength": 300, 20 + "description": "Title or path of the notebook to fetch." 21 + } 22 + } 23 + }, 24 + "output": { 25 + "encoding": "application/json", 26 + "schema": { 27 + "type": "object", 28 + "required": ["notebook", "entries"], 29 + "properties": { 30 + "notebook": { 31 + "type": "ref", 32 + "ref": "sh.weaver.notebook.defs#notebookView" 33 + }, 34 + "entries": { 35 + "type": "array", 36 + "items": { 37 + "type": "ref", 38 + "ref": "com.atproto.repo.strongRef" 39 + } 40 + } 41 + } 42 + } 43 + }, 44 + "errors": [ 45 + { "name": "NotebookNotFound" } 46 + ] 47 + } 48 + } 49 + }