Skip to main content

embystream/web/
contracts.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::config::{
5    backend::{Backend, BackendNode},
6    frontend::Frontend,
7    general::{Emby, Log, StreamMode, UserAgent},
8    http2::Http2,
9    types::FallbackConfig,
10};
11use crate::oauthutil::OAuthToken;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum UserRole {
16    Admin,
17    User,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "snake_case")]
22pub enum WizardStreamMode {
23    Frontend,
24    Backend,
25    Dual,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum DraftStatus {
31    Draft,
32    Generated,
33    Archived,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "snake_case")]
38pub enum ArtifactType {
39    ConfigToml,
40    NginxConf,
41    DockerCompose,
42    SystemdService,
43    Pm2Config,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum BackgroundProvider {
49    Tmdb,
50    Bing,
51    StaticFallback,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct SessionUser {
56    pub id: String,
57    pub username: String,
58    pub email: Option<String>,
59    pub role: UserRole,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct UserAdminSummary {
64    pub id: String,
65    pub username: String,
66    pub email: Option<String>,
67    pub role: UserRole,
68    pub disabled: bool,
69    pub created_at: DateTime<Utc>,
70    pub updated_at: DateTime<Utc>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct AuthResponse {
75    pub user: SessionUser,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub struct RegistrationSettingsResponse {
80    pub registration_enabled: bool,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct RegisterRequest {
85    pub username: String,
86    pub email: Option<String>,
87    pub password: String,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct LoginRequest {
92    pub login: String,
93    pub password: String,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct LogoutResponse {
98    pub ok: bool,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
102pub struct DraftSummary {
103    pub id: String,
104    pub name: String,
105    pub status: DraftStatus,
106    pub stream_mode: WizardStreamMode,
107    pub updated_at: DateTime<Utc>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct DraftDocument {
112    pub id: String,
113    pub name: String,
114    pub status: DraftStatus,
115    pub stream_mode: WizardStreamMode,
116    pub payload: WizardPayload,
117    pub updated_at: DateTime<Utc>,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct DraftEnvelope {
122    pub draft: DraftSummary,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct DraftDocumentEnvelope {
127    pub draft: DraftDocument,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct WizardTemplateResponse {
132    pub payload: WizardPayload,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct DraftListResponse {
137    pub items: Vec<DraftSummary>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141pub struct CreateDraftRequest {
142    pub name: String,
143    pub stream_mode: WizardStreamMode,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct SaveDraftRequest {
148    pub name: String,
149    pub payload: WizardPayload,
150    pub client_revision: u64,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct SaveDraftResponse {
155    pub draft: DraftRevision,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159pub struct DraftRevision {
160    pub id: String,
161    pub updated_at: DateTime<Utc>,
162    pub server_revision: u64,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
166pub struct ConfigSetSummary {
167    pub id: String,
168    pub name: String,
169    pub stream_mode: WizardStreamMode,
170    pub created_at: DateTime<Utc>,
171    pub updated_at: DateTime<Utc>,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175pub struct ConfigSetListResponse {
176    pub items: Vec<ConfigSetSummary>,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180pub struct ConfigSetEnvelope {
181    pub config_set: ConfigSetSummary,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub struct ArtifactSummary {
186    pub artifact_type: ArtifactType,
187    pub file_name: String,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
191pub struct ArtifactDocument {
192    pub artifact_type: ArtifactType,
193    pub file_name: String,
194    pub language: String,
195    pub content: String,
196}
197
198#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
199pub struct ArtifactListResponse {
200    pub items: Vec<ArtifactDocument>,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
204pub struct GenerateDraftResponse {
205    pub config_set: ConfigSetSummary,
206    pub artifacts: Vec<ArtifactSummary>,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
210pub struct BackgroundItem {
211    pub image_url: String,
212    pub title: String,
213    pub subtitle: Option<String>,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
217pub struct LoginBackgroundResponse {
218    pub provider: BackgroundProvider,
219    pub fetched_at: DateTime<Utc>,
220    pub expires_at: DateTime<Utc>,
221    pub items: Vec<BackgroundItem>,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
225pub struct LogEntry {
226    pub timestamp: DateTime<Utc>,
227    pub level: String,
228    pub source: String,
229    pub message: String,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233pub struct LogListResponse {
234    pub items: Vec<LogEntry>,
235    pub next_cursor: Option<String>,
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
239pub struct UserListResponse {
240    pub items: Vec<UserAdminSummary>,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
244pub struct UserEnvelope {
245    pub user: UserAdminSummary,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249pub struct UpdateUserRoleRequest {
250    pub role: UserRole,
251}
252
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
254pub struct UpdateUserDisabledRequest {
255    pub disabled: bool,
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
259pub struct UpdateUserPasswordRequest {
260    pub password: String,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
264pub struct UpdateRegistrationSettingsRequest {
265    pub registration_enabled: bool,
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
269pub struct ChangeOwnPasswordRequest {
270    pub current_password: String,
271    pub new_password: String,
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275pub struct SystemMetricsResponse {
276    pub cpu_usage_percent: f64,
277    pub cpu_core_count: u32,
278    pub memory_used_bytes: u64,
279    pub memory_total_bytes: u64,
280    pub memory_usage_percent: f64,
281    pub disk_used_bytes: u64,
282    pub disk_total_bytes: u64,
283    pub disk_usage_percent: f64,
284    pub uptime_seconds: u64,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288pub struct ApiErrorDetail {
289    pub code: String,
290    pub message: String,
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub field: Option<String>,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
296pub struct ApiErrorResponse {
297    pub error: ApiErrorDetail,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
301pub struct MetadataUpdateRequest {
302    pub name: String,
303}
304
305#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
306pub struct WizardSharedGeneral {
307    pub memory_mode: String,
308    pub encipher_key: String,
309    pub encipher_iv: String,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct WizardSharedPayload {
314    pub log: Log,
315    pub general: WizardSharedGeneral,
316    pub emby: Emby,
317    pub user_agent: UserAgent,
318    pub fallback: FallbackConfig,
319    pub http2: Http2,
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct WizardPayload {
324    pub stream_mode: WizardStreamMode,
325    pub shared: WizardSharedPayload,
326    pub frontend: Option<Frontend>,
327    pub backend: Option<Backend>,
328    #[serde(default)]
329    pub backend_nodes: Vec<BackendNode>,
330    #[serde(default)]
331    pub nginx: WizardNginxPayload,
332    #[serde(default)]
333    pub deployment: WizardDeploymentPayload,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize, Default)]
337pub struct WizardNginxPayload {
338    #[serde(default)]
339    pub frontend: WizardFrontendNginxPayload,
340    #[serde(default)]
341    pub backend: WizardBackendNginxPayload,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize, Default)]
345pub struct WizardDeploymentPayload {
346    #[serde(default)]
347    pub systemd: WizardSystemdPayload,
348    #[serde(default)]
349    pub pm2: WizardPm2Payload,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize, Default)]
353pub struct WizardSystemdPayload {
354    #[serde(default)]
355    pub binary_path: String,
356    #[serde(default)]
357    pub working_directory: String,
358    #[serde(default)]
359    pub config_path: String,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize, Default)]
363pub struct WizardPm2Payload {
364    #[serde(default)]
365    pub binary_path: String,
366    #[serde(default)]
367    pub working_directory: String,
368    #[serde(default)]
369    pub config_path: String,
370    #[serde(default)]
371    pub out_file: String,
372    #[serde(default)]
373    pub error_file: String,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct WizardFrontendNginxPayload {
378    #[serde(default)]
379    pub server_name: String,
380    #[serde(default)]
381    pub ssl_certificate: String,
382    #[serde(default)]
383    pub ssl_certificate_key: String,
384    #[serde(default = "default_frontend_client_max_body_size")]
385    pub client_max_body_size: String,
386    #[serde(default = "default_frontend_static_pattern")]
387    pub static_location_pattern: String,
388    #[serde(default = "default_frontend_websocket_pattern")]
389    pub websocket_location_pattern: String,
390}
391
392impl Default for WizardFrontendNginxPayload {
393    fn default() -> Self {
394        Self {
395            server_name: String::new(),
396            ssl_certificate: String::new(),
397            ssl_certificate_key: String::new(),
398            client_max_body_size: default_frontend_client_max_body_size(),
399            static_location_pattern: default_frontend_static_pattern(),
400            websocket_location_pattern: default_frontend_websocket_pattern(),
401        }
402    }
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct WizardBackendNginxPayload {
407    #[serde(default)]
408    pub server_name: String,
409    #[serde(default)]
410    pub ssl_certificate: String,
411    #[serde(default)]
412    pub ssl_certificate_key: String,
413    #[serde(default = "default_backend_client_max_body_size")]
414    pub client_max_body_size: String,
415    #[serde(default = "default_backend_resolver_provider")]
416    pub resolver_provider: String,
417    #[serde(default)]
418    pub custom_resolvers: String,
419    #[serde(default = "default_backend_access_log")]
420    pub access_log_path: String,
421    #[serde(default = "default_backend_error_log")]
422    pub error_log_path: String,
423    #[serde(default = "default_backend_google_access_log")]
424    pub google_drive_access_log_path: String,
425}
426
427impl Default for WizardBackendNginxPayload {
428    fn default() -> Self {
429        Self {
430            server_name: String::new(),
431            ssl_certificate: String::new(),
432            ssl_certificate_key: String::new(),
433            client_max_body_size: default_backend_client_max_body_size(),
434            resolver_provider: default_backend_resolver_provider(),
435            custom_resolvers: String::new(),
436            access_log_path: default_backend_access_log(),
437            error_log_path: default_backend_error_log(),
438            google_drive_access_log_path: default_backend_google_access_log(),
439        }
440    }
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct WizardGoogleDriveTokenPayload {
445    #[serde(default)]
446    pub access_token: String,
447    #[serde(default)]
448    pub refresh_token: String,
449    #[serde(default = "default_google_token_type")]
450    pub token_type: String,
451    #[serde(default)]
452    pub expiry: Option<chrono::DateTime<chrono::Utc>>,
453}
454
455impl Default for WizardGoogleDriveTokenPayload {
456    fn default() -> Self {
457        Self {
458            access_token: String::new(),
459            refresh_token: String::new(),
460            token_type: default_google_token_type(),
461            expiry: None,
462        }
463    }
464}
465
466fn default_google_token_type() -> String {
467    "Bearer".to_string()
468}
469
470fn default_frontend_client_max_body_size() -> String {
471    "100M".to_string()
472}
473
474fn default_frontend_static_pattern() -> String {
475    r"\.(webp|jpg|jpeg|png|gif|ico|css|js|html)$|Images|fonts".to_string()
476}
477
478fn default_frontend_websocket_pattern() -> String {
479    r"/(socket|embywebsocket)".to_string()
480}
481
482fn default_backend_client_max_body_size() -> String {
483    "1G".to_string()
484}
485
486fn default_backend_resolver_provider() -> String {
487    "none".to_string()
488}
489
490fn default_backend_access_log() -> String {
491    "/var/log/nginx/embystream_access.log".to_string()
492}
493
494fn default_backend_error_log() -> String {
495    "/var/log/nginx/embystream_error.log".to_string()
496}
497
498fn default_backend_google_access_log() -> String {
499    "/var/log/nginx/google_drive_access.log".to_string()
500}
501
502pub fn wizard_token_payload_from_oauth(
503    token: Option<&OAuthToken>,
504) -> Option<WizardGoogleDriveTokenPayload> {
505    token.map(|token| WizardGoogleDriveTokenPayload {
506        access_token: token.access_token.clone(),
507        refresh_token: token.refresh_token.clone(),
508        token_type: token.token_type.clone(),
509        expiry: token.expiry,
510    })
511}
512
513pub fn wizard_token_payload_into_oauth(
514    token: Option<&WizardGoogleDriveTokenPayload>,
515) -> Option<OAuthToken> {
516    token.and_then(|token| {
517        if token.access_token.trim().is_empty()
518            && token.refresh_token.trim().is_empty()
519        {
520            return None;
521        }
522
523        Some(OAuthToken {
524            access_token: token.access_token.clone(),
525            refresh_token: token.refresh_token.clone(),
526            token_type: if token.token_type.trim().is_empty() {
527                default_google_token_type()
528            } else {
529                token.token_type.clone()
530            },
531            expiry: token.expiry,
532        })
533    })
534}
535
536impl From<StreamMode> for WizardStreamMode {
537    fn from(value: StreamMode) -> Self {
538        match value {
539            StreamMode::Frontend => Self::Frontend,
540            StreamMode::Backend => Self::Backend,
541            StreamMode::Dual => Self::Dual,
542        }
543    }
544}
545
546impl From<WizardStreamMode> for StreamMode {
547    fn from(value: WizardStreamMode) -> Self {
548        match value {
549            WizardStreamMode::Frontend => Self::Frontend,
550            WizardStreamMode::Backend => Self::Backend,
551            WizardStreamMode::Dual => Self::Dual,
552        }
553    }
554}