at main 224 lines 8.1 kB view raw
1//! Host mode context for subdomain and custom domain routing. 2 3use crate::env::WEAVER_APP_HOST; 4use jacquard::smol_str::{SmolStr, format_smolstr}; 5use jacquard::types::string::AtIdentifier; 6use serde::{Deserialize, Serialize}; 7 8/// Unified host context resolved by middleware. 9/// 10/// This is inserted into request extensions by the host resolution middleware 11/// and read by the Dioxus app to determine which router to use. 12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 13pub enum HostContext { 14 /// Main weaver.sh domain - use full Route enum. 15 MainDomain, 16 /// Subdomain hosting (notebook.weaver.sh) - use SubdomainRoute. 17 Subdomain(SubdomainContext), 18 /// Custom domain (myblog.com) - use CustomDomainRoute. 19 CustomDomain(CustomDomainContext), 20} 21 22impl HostContext { 23 /// Get the link mode for this host context. 24 pub fn link_mode(&self) -> LinkMode { 25 match self { 26 HostContext::MainDomain => LinkMode::MainDomain, 27 HostContext::Subdomain(_) => LinkMode::Subdomain, 28 HostContext::CustomDomain(_) => LinkMode::CustomDomain, 29 } 30 } 31} 32 33/// Context for subdomain routing - identifies the notebook being served. 34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 35#[serde(bound(deserialize = ""))] 36pub struct SubdomainContext { 37 /// DID of the notebook owner. 38 #[serde(deserialize_with = "deserialize_static_ident")] 39 pub owner: AtIdentifier<'static>, 40 /// Notebook path (same as subdomain). 41 pub notebook_path: SmolStr, 42 43 /// Notebook title. 44 pub notebook_title: SmolStr, 45 /// Notebook rkey for direct lookups. 46 pub notebook_rkey: SmolStr, 47} 48 49fn deserialize_static_ident<'de, D>(deserializer: D) -> Result<AtIdentifier<'static>, D::Error> 50where 51 D: serde::Deserializer<'de>, 52{ 53 use jacquard::IntoStatic; 54 let did: AtIdentifier<'de> = Deserialize::deserialize(deserializer)?; 55 Ok(did.into_static()) 56} 57 58impl SubdomainContext { 59 /// Get the owner as an AtIdentifier for route parameters. 60 pub fn owner_ident(&self) -> AtIdentifier<'static> { 61 self.owner.clone() 62 } 63} 64 65/// Context for custom domain routing - identifies the publication being served. 66#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 67#[serde(bound(deserialize = ""))] 68pub struct CustomDomainContext { 69 /// Custom domain (e.g., "myblog.com"). 70 pub domain: SmolStr, 71 /// DID of the publication owner. 72 #[serde(deserialize_with = "deserialize_static_ident")] 73 pub owner: AtIdentifier<'static>, 74 /// Publication rkey. 75 pub publication_rkey: SmolStr, 76 /// Publication name. 77 pub publication_name: SmolStr, 78 /// For weaver-backed publications, the backing notebook URI. 79 pub notebook_uri: Option<SmolStr>, 80} 81 82/// Link mode for generating appropriate URLs based on host context. 83/// 84/// Components use this context to generate links that work on both 85/// the main domain and subdomain hosting. 86#[derive(Clone, Debug, Default, PartialEq, Eq)] 87pub enum LinkMode { 88 /// Main domain - full paths with /:ident/:notebook/:entry 89 #[default] 90 MainDomain, 91 /// Subdomain - simplified paths like /:entry 92 Subdomain, 93 /// Custom domain - paths like /:path for publications 94 CustomDomain, 95} 96 97impl LinkMode { 98 /// Check if we're in subdomain mode. 99 pub fn is_subdomain(&self) -> bool { 100 matches!(self, LinkMode::Subdomain) 101 } 102 103 /// Check if we're in a hosted mode (subdomain or custom domain). 104 pub fn is_hosted(&self) -> bool { 105 matches!(self, LinkMode::Subdomain | LinkMode::CustomDomain) 106 } 107 108 /// Generate link to a notebook entry by title. 109 pub fn entry_link( 110 &self, 111 ident: &AtIdentifier<'_>, 112 book_title: &str, 113 entry_title: &str, 114 ) -> SmolStr { 115 match self { 116 LinkMode::MainDomain => format_smolstr!("/{}/{}/{}", ident, book_title, entry_title), 117 LinkMode::Subdomain | LinkMode::CustomDomain => format_smolstr!("/{}", entry_title), 118 } 119 } 120 121 /// Generate link to a notebook entry by rkey. 122 pub fn entry_rkey_link( 123 &self, 124 ident: &AtIdentifier<'_>, 125 book_title: &str, 126 rkey: &str, 127 ) -> SmolStr { 128 match self { 129 LinkMode::MainDomain => format_smolstr!("/{}/{}/e/{}", ident, book_title, rkey), 130 LinkMode::Subdomain | LinkMode::CustomDomain => format_smolstr!("/e/{}", rkey), 131 } 132 } 133 134 /// Generate link to edit a notebook entry by rkey. 135 pub fn entry_edit_link( 136 &self, 137 ident: &AtIdentifier<'_>, 138 book_title: &str, 139 rkey: &str, 140 ) -> SmolStr { 141 match self { 142 LinkMode::MainDomain => format_smolstr!("/{}/{}/e/{}/edit", ident, book_title, rkey), 143 LinkMode::Subdomain | LinkMode::CustomDomain => format_smolstr!("/e/{}/edit", rkey), 144 } 145 } 146 147 /// Generate link to a notebook index. 148 pub fn notebook_link(&self, ident: &AtIdentifier<'_>, book_title: &str) -> SmolStr { 149 match self { 150 LinkMode::MainDomain => format_smolstr!("/{}/{}", ident, book_title), 151 LinkMode::Subdomain | LinkMode::CustomDomain => SmolStr::new_static("/"), 152 } 153 } 154 155 /// Generate link to a profile/repository. 156 pub fn profile_link(&self, ident: &AtIdentifier<'_>) -> SmolStr { 157 match self { 158 LinkMode::MainDomain => format_smolstr!("/{}", ident), 159 LinkMode::Subdomain | LinkMode::CustomDomain => format_smolstr!("/u/{}", ident), 160 } 161 } 162 163 /// Generate link to a standalone entry. 164 pub fn standalone_entry_link(&self, ident: &AtIdentifier<'_>, rkey: &str) -> SmolStr { 165 match self { 166 LinkMode::MainDomain => format_smolstr!("/{}/e/{}", ident, rkey), 167 // Standalone entries don't exist in hosted mode - link to main domain 168 LinkMode::Subdomain | LinkMode::CustomDomain => { 169 format_smolstr!("{}/{}/e/{}", WEAVER_APP_HOST, ident, rkey) 170 } 171 } 172 } 173 174 /// Generate link to edit a standalone entry. 175 pub fn standalone_entry_edit_link(&self, ident: &AtIdentifier<'_>, rkey: &str) -> SmolStr { 176 match self { 177 LinkMode::MainDomain => format_smolstr!("/{}/e/{}/edit", ident, rkey), 178 // Edit on main domain 179 LinkMode::Subdomain | LinkMode::CustomDomain => { 180 format_smolstr!("{}/{}/e/{}/edit", WEAVER_APP_HOST, ident, rkey) 181 } 182 } 183 } 184 185 /// Generate link to create a new draft. 186 pub fn new_draft_link(&self, ident: &AtIdentifier<'_>, notebook: Option<&str>) -> SmolStr { 187 match (self, notebook) { 188 (LinkMode::MainDomain, Some(nb)) => format_smolstr!("/{}/new?notebook={}", ident, nb), 189 (LinkMode::MainDomain, None) => format_smolstr!("/{}/new", ident), 190 // Drafts are on main domain 191 (LinkMode::Subdomain | LinkMode::CustomDomain, Some(nb)) => { 192 format_smolstr!("{}/{}/new?notebook={}", WEAVER_APP_HOST, ident, nb) 193 } 194 (LinkMode::Subdomain | LinkMode::CustomDomain, None) => { 195 format_smolstr!("{}/{}/new", WEAVER_APP_HOST, ident) 196 } 197 } 198 } 199 200 /// Generate link to drafts list. 201 pub fn drafts_link(&self, ident: &AtIdentifier<'_>) -> SmolStr { 202 match self { 203 LinkMode::MainDomain => format_smolstr!("/{}/drafts", ident), 204 LinkMode::Subdomain | LinkMode::CustomDomain => { 205 format_smolstr!("{}/{}/drafts", WEAVER_APP_HOST, ident) 206 } 207 } 208 } 209 210 /// Generate link to invites page. 211 pub fn invites_link(&self, ident: &AtIdentifier<'_>) -> SmolStr { 212 match self { 213 LinkMode::MainDomain => format_smolstr!("/{}/invites", ident), 214 LinkMode::Subdomain | LinkMode::CustomDomain => { 215 format_smolstr!("{}/{}/invites", WEAVER_APP_HOST, ident) 216 } 217 } 218 } 219 220 /// Generate link to a document by path (for custom domain publications). 221 pub fn document_link(&self, path: &str) -> SmolStr { 222 format_smolstr!("/{}", path.trim_start_matches('/')) 223 } 224}