Skip to main content

shared/extensions/
mod.rs

1#![allow(unused_variables)]
2
3use crate::{State, permissions::PermissionGroup};
4use indexmap::IndexMap;
5use serde::Serialize;
6use std::{ops::Deref, sync::Arc};
7use utoipa::ToSchema;
8use utoipa_axum::router::OpenApiRouter;
9
10pub mod background_tasks;
11pub mod commands;
12pub mod distr;
13pub mod email_templates;
14pub mod manager;
15pub mod settings;
16pub mod shutdown_handlers;
17
18pub struct ExtensionRouteBuilder {
19    state: State,
20    pub global: Option<Box<OpenApiRouter<State>>>,
21    pub api_admin: Option<Box<OpenApiRouter<State>>>,
22    pub api_auth: Option<Box<OpenApiRouter<State>>>,
23    pub api_client: Option<Box<OpenApiRouter<State>>>,
24    pub api_client_servers_server: Option<Box<OpenApiRouter<State>>>,
25    pub api_remote: Option<Box<OpenApiRouter<State>>>,
26    pub api_remote_servers_server: Option<Box<OpenApiRouter<State>>>,
27}
28
29impl ExtensionRouteBuilder {
30    pub fn new(state: State) -> Self {
31        Self {
32            state,
33            global: None,
34            api_admin: None,
35            api_auth: None,
36            api_client: None,
37            api_client_servers_server: None,
38            api_remote: None,
39            api_remote_servers_server: None,
40        }
41    }
42
43    /// Adds a router for handling requests to `/`.
44    pub fn add_global_router(
45        mut self,
46        router: impl FnOnce(OpenApiRouter<State>) -> OpenApiRouter<State>,
47    ) -> Self {
48        self.global = Some(Box::new(router(self.global.map_or_else(
49            || OpenApiRouter::new().with_state(self.state.clone()),
50            |b| *b,
51        ))));
52
53        self
54    }
55
56    /// Adds a router for handling requests to `/api/admin`.
57    /// Authentication middleware is already handled by the parent router.
58    pub fn add_admin_api_router(
59        mut self,
60        router: impl FnOnce(OpenApiRouter<State>) -> OpenApiRouter<State>,
61    ) -> Self {
62        self.api_admin = Some(Box::new(router(self.api_admin.map_or_else(
63            || OpenApiRouter::new().with_state(self.state.clone()),
64            |b| *b,
65        ))));
66
67        self
68    }
69
70    /// Adds a router for handling requests to `/api/auth`.
71    pub fn add_auth_api_router(
72        mut self,
73        router: impl FnOnce(OpenApiRouter<State>) -> OpenApiRouter<State>,
74    ) -> Self {
75        self.api_auth = Some(Box::new(router(self.api_auth.map_or_else(
76            || OpenApiRouter::new().with_state(self.state.clone()),
77            |b| *b,
78        ))));
79
80        self
81    }
82
83    /// Adds a router for handling requests to `/api/client`.
84    /// Authentication middleware is already handled by the parent router.
85    pub fn add_client_api_router(
86        mut self,
87        router: impl FnOnce(OpenApiRouter<State>) -> OpenApiRouter<State>,
88    ) -> Self {
89        self.api_client = Some(Box::new(router(self.api_client.map_or_else(
90            || OpenApiRouter::new().with_state(self.state.clone()),
91            |b| *b,
92        ))));
93
94        self
95    }
96
97    /// Adds a router for handling requests to `/api/client/servers/{server}`.
98    /// Authentication middleware is already handled by the parent router.
99    pub fn add_client_server_api_router(
100        mut self,
101        router: impl FnOnce(OpenApiRouter<State>) -> OpenApiRouter<State>,
102    ) -> Self {
103        self.api_client_servers_server = Some(Box::new(router(
104            self.api_client_servers_server.map_or_else(
105                || OpenApiRouter::new().with_state(self.state.clone()),
106                |b| *b,
107            ),
108        )));
109
110        self
111    }
112
113    /// Adds a router for handling requests to `/api/remote`.
114    /// Authentication middleware is already handled by the parent router.
115    pub fn add_remote_api_router(
116        mut self,
117        router: impl FnOnce(OpenApiRouter<State>) -> OpenApiRouter<State>,
118    ) -> Self {
119        self.api_remote = Some(Box::new(router(self.api_remote.map_or_else(
120            || OpenApiRouter::new().with_state(self.state.clone()),
121            |b| *b,
122        ))));
123
124        self
125    }
126
127    /// Adds a router for handling requests to `/api/admin`.
128    /// Authentication middleware is already handled by the parent router.
129    pub fn add_remote_server_api_router(
130        mut self,
131        router: impl FnOnce(OpenApiRouter<State>) -> OpenApiRouter<State>,
132    ) -> Self {
133        self.api_remote_servers_server = Some(Box::new(router(
134            self.api_remote_servers_server.map_or_else(
135                || OpenApiRouter::new().with_state(self.state.clone()),
136                |b| *b,
137            ),
138        )));
139
140        self
141    }
142}
143
144type RawPermissionMap = IndexMap<&'static str, PermissionGroup>;
145pub struct ExtensionPermissionsBuilder {
146    pub user_permissions: RawPermissionMap,
147    pub admin_permissions: RawPermissionMap,
148    pub server_permissions: RawPermissionMap,
149}
150
151impl ExtensionPermissionsBuilder {
152    pub fn new(
153        user_permissions: RawPermissionMap,
154        admin_permissions: RawPermissionMap,
155        server_permissions: RawPermissionMap,
156    ) -> Self {
157        Self {
158            user_permissions,
159            admin_permissions,
160            server_permissions,
161        }
162    }
163
164    /// Adds a permission group to the user permissions.
165    pub fn add_user_permission_group(
166        mut self,
167        group_name: &'static str,
168        group: PermissionGroup,
169    ) -> Self {
170        self.user_permissions.insert(group_name, group);
171
172        self
173    }
174
175    /// Mutates a permission group in the user permissions.
176    pub fn mutate_user_permission_group(
177        mut self,
178        group_name: &'static str,
179        mutation: impl FnOnce(&mut PermissionGroup),
180    ) -> Self {
181        if let Some(group) = self.user_permissions.get_mut(group_name) {
182            mutation(group);
183        }
184
185        self
186    }
187
188    /// Adds a permission group to the admin permissions.
189    pub fn add_admin_permission_group(
190        mut self,
191        group_name: &'static str,
192        group: PermissionGroup,
193    ) -> Self {
194        self.admin_permissions.insert(group_name, group);
195
196        self
197    }
198
199    /// Mutates a permission group in the admin permissions.
200    pub fn mutate_admin_permission_group(
201        mut self,
202        group_name: &'static str,
203        mutation: impl FnOnce(&mut PermissionGroup),
204    ) -> Self {
205        if let Some(group) = self.admin_permissions.get_mut(group_name) {
206            mutation(group);
207        }
208
209        self
210    }
211
212    /// Adds a permission group to the server permissions.
213    pub fn add_server_permission_group(
214        mut self,
215        group_name: &'static str,
216        group: PermissionGroup,
217    ) -> Self {
218        self.server_permissions.insert(group_name, group);
219
220        self
221    }
222
223    /// Mutates a permission group in the server permissions.
224    pub fn mutate_server_permission_group(
225        mut self,
226        group_name: &'static str,
227        mutation: impl FnOnce(&mut PermissionGroup),
228    ) -> Self {
229        if let Some(group) = self.server_permissions.get_mut(group_name) {
230            mutation(group);
231        }
232
233        self
234    }
235}
236
237pub struct ExtensionUpdateInfo {
238    pub version: semver::Version,
239    pub changes: Vec<compact_str::CompactString>,
240}
241
242pub type ExtensionCallValue = Box<dyn std::any::Any + Send + Sync>;
243
244#[async_trait::async_trait]
245pub trait Extension: Send + Sync {
246    /// Your extension entrypoint, this runs as soon as the database is migrated and before the webserver starts
247    async fn initialize(&mut self, state: State) {}
248
249    /// Your extension cli entrypoint, this runs after the env has been parsed
250    async fn initialize_cli(
251        &mut self,
252        env: Option<&Arc<crate::env::Env>>,
253        builder: commands::CliCommandGroupBuilder,
254    ) -> commands::CliCommandGroupBuilder {
255        builder
256    }
257
258    /// Your extension routes entrypoint, this runs as soon as the database is migrated and before the webserver starts
259    async fn initialize_router(
260        &mut self,
261        state: State,
262        builder: ExtensionRouteBuilder,
263    ) -> ExtensionRouteBuilder {
264        builder
265    }
266
267    /// Your extension email templates entrypoint, this runs as soon as the database is migrated and before the webserver starts
268    async fn initialize_email_templates(
269        &mut self,
270        state: State,
271        builder: email_templates::ExtensionEmailTemplateBuilder,
272    ) -> email_templates::ExtensionEmailTemplateBuilder {
273        builder
274    }
275
276    /// Your extension background tasks entrypoint, this runs as soon as the database is migrated and before the webserver starts
277    async fn initialize_background_tasks(
278        &mut self,
279        state: State,
280        builder: background_tasks::BackgroundTaskBuilder,
281    ) -> background_tasks::BackgroundTaskBuilder {
282        builder
283    }
284
285    /// Your extension shutdown handler entrypoint, this runs as soon as the database is migrated and before the webserver starts
286    async fn initialize_shutdown_handlers(
287        &mut self,
288        state: State,
289        builder: shutdown_handlers::ShutdownHandlerBuilder,
290    ) -> shutdown_handlers::ShutdownHandlerBuilder {
291        builder
292    }
293
294    /// Your extension permissions entrypoint, this runs as soon as the database is migrated and before the webserver starts
295    async fn initialize_permissions(
296        &mut self,
297        state: State,
298        builder: ExtensionPermissionsBuilder,
299    ) -> ExtensionPermissionsBuilder {
300        builder
301    }
302
303    /// Your extension settings deserializer, this is used to deserialize your extension settings from the database
304    /// Whatever value you return in the `deserialize_boxed` method must match the trait `ExtensionSettings`, which requires
305    /// `SettingsSerializeExt` to be implemented for it. If you have no clue what this means. copy code from the docs.
306    async fn settings_deserializer(&self, state: State) -> settings::ExtensionSettingsDeserializer {
307        Arc::new(settings::EmptySettings)
308    }
309
310    /// Your extension update checker, this is used to check for updates to your extension. It runs every 12 hours and on startup.
311    /// You can return an `ExtensionUpdateInfo` struct with the new version and a list of changes, this changes list *should* be all
312    /// changes from the current version to the new version. An empty changes list will simply not show any changelog, but will still show that an update is available.
313    async fn check_for_updates(
314        &self,
315        state: State,
316        current_version: &semver::Version,
317    ) -> Result<Option<ExtensionUpdateInfo>, anyhow::Error> {
318        Ok(None)
319    }
320
321    /// Your extension call processor, this can be called by other extensions to interact with yours,
322    /// if the call does not apply to your extension, simply return `None` to continue the matching process.
323    ///
324    /// Optimally (if applies) make sure your calls are globally unique, for example by prepending them with your package name
325    async fn process_call(
326        &self,
327        name: &str,
328        args: &[ExtensionCallValue],
329    ) -> Option<ExtensionCallValue> {
330        None
331    }
332
333    /// Your extension call processor, this can be called by other extensions to interact with yours,
334    /// if the call does not apply to your extension, simply return `None` to continue the matching process.
335    ///
336    /// The only difference to `process_call` is that this takes an owned vec, its automatically implemented in terms of `process_call`.
337    ///
338    /// Optimally (if applies) make sure your calls are globally unique, for example by prepending them with your package name
339    async fn process_call_owned(
340        &self,
341        name: &str,
342        args: Vec<ExtensionCallValue>,
343    ) -> Option<ExtensionCallValue> {
344        self.process_call(name, &args).await
345    }
346}
347
348#[derive(ToSchema, Serialize, Clone)]
349pub struct ConstructedExtension {
350    pub metadata_toml: distr::MetadataToml,
351    pub package_name: &'static str,
352    pub description: &'static str,
353    pub authors: &'static [&'static str],
354    #[schema(value_type = String)]
355    pub version: semver::Version,
356
357    #[serde(skip)]
358    #[schema(ignore)]
359    pub extension: Arc<dyn Extension>,
360}
361
362impl Deref for ConstructedExtension {
363    type Target = Arc<dyn Extension>;
364
365    fn deref(&self) -> &Self::Target {
366        &self.extension
367    }
368}
369
370#[derive(ToSchema, Serialize, Clone)]
371pub struct PendingExtension {
372    pub metadata_toml: distr::MetadataToml,
373    pub package_name: compact_str::CompactString,
374    pub description: compact_str::CompactString,
375    pub authors: Vec<compact_str::CompactString>,
376    #[schema(value_type = String)]
377    pub version: semver::Version,
378}