cloudillo_core/lib.rs
1// SPDX-FileCopyrightText: Szilárd Hajba
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4//! Core infrastructure for the Cloudillo platform.
5//!
6//! This crate contains shared infrastructure modules that are used by the server
7//! crate and potentially by future feature crates. Extracting these into a separate
8//! crate enables better build parallelism and clearer module boundaries.
9
10pub mod abac;
11pub mod acme;
12pub mod app;
13pub mod bootstrap_types;
14pub mod bundled_apps;
15pub mod core_settings;
16pub mod create_perm;
17pub mod dir_cache;
18pub mod dns;
19pub mod doc_format;
20pub mod extensions;
21pub mod extract;
22pub mod file_access;
23pub mod log;
24pub mod maintenance;
25pub mod middleware;
26pub mod prelude;
27pub mod profile_me_cache;
28pub mod profile_visibility;
29pub mod proxy_token_cache;
30pub mod rate_limit;
31pub mod request;
32pub mod roles;
33pub mod scheduler;
34pub mod scope;
35pub mod settings;
36pub mod share_access;
37pub mod ws_broadcast;
38pub mod ws_bus;
39
40use std::net::IpAddr;
41use std::pin::Pin;
42
43// Re-export commonly used types
44pub use app::{App, AppBuilderOpts, AppState, ServerMode};
45pub use dir_cache::{DirCache, DirEntry};
46pub use extract::{Auth, IdTag, OptionalAuth};
47pub use middleware::{PermissionCheckFactory, PermissionCheckInput, PermissionCheckOutput};
48pub use profile_me_cache::ProfileMeCache;
49pub use profile_visibility::{CommunityRole, RequesterTier, SectionVisibility};
50pub use proxy_token_cache::ProxyTokenCache;
51pub use ws_broadcast::BroadcastManager;
52
53/// Type-erased function for verifying action tokens.
54/// Registered as an extension by the server's action module.
55/// Used by auth module for the token exchange flow.
56pub type ActionVerifyFn = Box<
57 dyn for<'a> Fn(
58 &'a app::App,
59 cloudillo_types::types::TnId,
60 &'a str,
61 Option<&'a IpAddr>,
62 ) -> Pin<
63 Box<
64 dyn Future<
65 Output = cloudillo_types::error::ClResult<
66 cloudillo_types::auth_adapter::ActionToken,
67 >,
68 > + Send
69 + 'a,
70 >,
71 > + Send
72 + Sync,
73>;
74
75/// Type-erased function for creating a complete tenant (bootstrap).
76/// Registered as an extension by the server's bootstrap module.
77/// Used by profile crate for registration and community creation.
78pub type CreateCompleteTenantFn = Box<
79 dyn for<'a> Fn(
80 &'a app::App,
81 bootstrap_types::CreateCompleteTenantOptions<'a>,
82 ) -> Pin<
83 Box<
84 dyn Future<Output = cloudillo_types::error::ClResult<cloudillo_types::types::TnId>>
85 + Send
86 + 'a,
87 >,
88 > + Send
89 + Sync,
90>;
91
92/// Type-erased function for creating an action.
93/// Registered as an extension by the server's action module.
94/// Used by profile crate for community CONN creation.
95pub type CreateActionFn = Box<
96 dyn for<'a> Fn(
97 &'a app::App,
98 cloudillo_types::types::TnId,
99 &'a str,
100 cloudillo_types::action_types::CreateAction,
101 ) -> Pin<
102 Box<dyn Future<Output = cloudillo_types::error::ClResult<Box<str>>> + Send + 'a>,
103 > + Send
104 + Sync,
105>;
106
107/// Type-erased hook asking for a document to be (re)indexed for full-text search.
108/// Registered as an extension by the server's app module (delegates to
109/// `cloudillo_search::indexer::schedule`).
110///
111/// Exists so storage crates can notify the search subsystem without depending on it:
112/// `cloudillo-rtdb` calling `cloudillo-search` directly would be a dependency cycle,
113/// since search reads documents back through the adapters.
114///
115/// Synchronous and infallible on purpose — the hook only enqueues a debounced task,
116/// so a write path must never await or fail on it.
117pub type SearchIndexFn = Box<dyn Fn(&app::App, cloudillo_types::types::TnId, &str) + Send + Sync>;
118
119/// Type-erased hook asking for one **whole object** — a file, a profile, an action —
120/// to be re-indexed. The counterpart of [`SearchIndexFn`], which covers the deep parts
121/// of a document.
122///
123/// `obj_tp` is the `search_docs` object type: `'F'`, `'P'` or `'A'`. Same contract as
124/// [`SearchIndexFn`]. Call it through [`search_index_object`] rather than looking the
125/// extension up by hand.
126pub type SearchObjectFn =
127 Box<dyn Fn(&app::App, cloudillo_types::types::TnId, char, &str) + Send + Sync>;
128
129/// Ask for one object's whole-object index row to be rebuilt. Prefer the three typed
130/// wrappers below.
131///
132/// Call this right after a write that changes a column the index reads — a file's
133/// `file_name`, `tags`, `status`, `visibility`, `owner_tag`, `root_id` or
134/// `content_type`; a profile's `name` or `id_tag`; an action's `content`, `type`,
135/// `sub_type`, `status`, `visibility` or `root_id`. Writes that only bump timestamps
136/// or counters need nothing.
137///
138/// A no-op when the search subsystem is not wired in, so a feature crate can call it
139/// unconditionally.
140pub fn search_index_object(
141 app: &app::App,
142 tn_id: cloudillo_types::types::TnId,
143 obj_tp: char,
144 obj_id: &str,
145) {
146 if let Ok(f) = app.ext::<SearchObjectFn>() {
147 f(app, tn_id, obj_tp, obj_id);
148 }
149}
150
151/// [`search_index_object`] for a file.
152pub fn search_index_file(app: &app::App, tn_id: cloudillo_types::types::TnId, file_id: &str) {
153 search_index_object(app, tn_id, 'F', file_id);
154}
155
156/// Ask for one document's deep `'D'` index rows to be rebuilt; a no-op when the search
157/// subsystem is not wired in. The counterpart of [`search_index_file`], which covers
158/// the file's own row.
159pub fn search_index_document(app: &app::App, tn_id: cloudillo_types::types::TnId, file_id: &str) {
160 if let Ok(f) = app.ext::<SearchIndexFn>() {
161 f(app, tn_id, file_id);
162 }
163}
164
165/// [`search_index_object`] for a profile.
166pub fn search_index_profile(app: &app::App, tn_id: cloudillo_types::types::TnId, id_tag: &str) {
167 search_index_object(app, tn_id, 'P', id_tag);
168}
169
170/// [`search_index_object`] for an action.
171pub fn search_index_action(app: &app::App, tn_id: cloudillo_types::types::TnId, action_id: &str) {
172 search_index_object(app, tn_id, 'A', action_id);
173}
174
175/// Type-erased lookup of an action type's search manifest, resolved through the DSL
176/// engine (`TYPE:SUB` first, then `TYPE`). Registered by the server's app module.
177///
178/// Exists for the same reason as [`SearchIndexFn`]: `cloudillo-search` must not depend
179/// on `cloudillo-action`. The manifest crosses as opaque JSON, so only
180/// `cloudillo-search` ever parses it.
181///
182/// Returns `None` when the type has no definition at all; otherwise the **resolved**
183/// definition key and that definition's manifest, itself `None` for a type that is not
184/// indexed. The key comes back separately so the caller can cache parsed rules under
185/// it — a resolved key is one of the process's fixed definition names, whereas the
186/// `(type, subType)` pair a federated action carries is not bounded by anything.
187pub type ActionSearchRulesFn =
188 Box<dyn Fn(&str, Option<&str>) -> Option<(Box<str>, Option<serde_json::Value>)> + Send + Sync>;
189
190/// Parameters passed to a `ScheduleEmailFn` invocation. Mirrors
191/// `cloudillo_email::EmailTaskParams` but lives in core so the ACME renewal
192/// task (and other core-side tasks) can schedule emails without a cyclic
193/// dependency on the email crate.
194pub struct ScheduleEmailParams {
195 pub to: String,
196 pub template_name: String,
197 pub template_vars: serde_json::Value,
198 pub lang: Option<String>,
199 pub custom_key: Option<String>,
200 pub from_name_override: Option<String>,
201}
202
203/// Type-erased function for scheduling a templated email via the scheduler.
204/// Registered as an extension by the server's app module (delegates to
205/// `cloudillo_email::EmailModule::schedule_email_task`).
206pub type ScheduleEmailFn = Box<
207 dyn for<'a> Fn(
208 &'a app::App,
209 cloudillo_types::types::TnId,
210 ScheduleEmailParams,
211 ) -> Pin<
212 Box<dyn Future<Output = cloudillo_types::error::ClResult<()>> + Send + 'a>,
213 > + Send
214 + Sync,
215>;
216
217/// Type-erased function invoked once the very first ACME certificate for a
218/// tenant has been successfully issued. Registered by the profile crate so
219/// it can flush deferred work (e.g. queueing a welcome email that requires
220/// HTTPS to be usable). Called from `acme::handle_renewal_success` only when
221/// the renewal row's pre-renewal `expires_at` was `None`.
222///
223/// **Implementations MUST be idempotent.** The hook may fire multiple times
224/// for the same `tn_id`: the bootstrap path (`bootstrap.rs`) and the
225/// early-retry task (`acme.rs::AcmeEarlyRetryTask`) can both observe the
226/// first successful issuance after a process restart, both with
227/// `is_first_issuance: true`. Implementations must dedupe — e.g. by using a
228/// scheduler dedup key or a marker setting cleared after first run.
229pub type OnFirstCertIssuedFn = Box<
230 dyn for<'a> Fn(
231 &'a app::App,
232 cloudillo_types::types::TnId,
233 &'a str,
234 ) -> Pin<
235 Box<dyn Future<Output = cloudillo_types::error::ClResult<()>> + Send + 'a>,
236 > + Send
237 + Sync,
238>;
239
240/// Type-erased function for ensuring a remote profile exists locally.
241/// Registered as an extension by the server's app module.
242/// Used by action hooks for profile sync.
243pub type EnsureProfileFn = Box<
244 dyn for<'a> Fn(
245 &'a app::App,
246 cloudillo_types::types::TnId,
247 &'a str,
248 ) -> Pin<
249 Box<dyn Future<Output = cloudillo_types::error::ClResult<bool>> + Send + 'a>,
250 > + Send
251 + Sync,
252>;
253
254pub fn register_settings(
255 registry: &mut settings::SettingsRegistry,
256) -> cloudillo_types::error::ClResult<()> {
257 core_settings::register_settings(registry)
258}
259
260// vim: ts=4