1use std::collections::HashMap;
15
16use chrono::{DateTime, Utc};
17use serde::{Deserialize, Serialize};
18use uuid::Uuid;
19
20use crate::spec::DeploymentSpec;
21
22#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
28pub struct StoredDeployment {
29 pub name: String,
31
32 #[schema(value_type = Object)]
34 pub spec: DeploymentSpec,
35
36 pub status: DeploymentStatus,
38
39 #[schema(value_type = String, example = "2025-01-27T12:00:00Z")]
41 pub created_at: DateTime<Utc>,
42
43 #[schema(value_type = String, example = "2025-01-27T12:00:00Z")]
45 pub updated_at: DateTime<Utc>,
46}
47
48impl StoredDeployment {
49 #[must_use]
51 pub fn new(spec: DeploymentSpec) -> Self {
52 let now = Utc::now();
53 Self {
54 name: spec.deployment.clone(),
55 spec,
56 status: DeploymentStatus::Pending,
57 created_at: now,
58 updated_at: now,
59 }
60 }
61
62 pub fn update_spec(&mut self, spec: DeploymentSpec) {
64 self.spec = spec;
65 self.updated_at = Utc::now();
66 }
67
68 pub fn update_status(&mut self, status: DeploymentStatus) {
70 self.status = status;
71 self.updated_at = Utc::now();
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
77#[serde(tag = "state", rename_all = "snake_case")]
78pub enum DeploymentStatus {
79 Pending,
81
82 Deploying,
84
85 Running,
87
88 Failed {
90 message: String,
92 },
93
94 Stopped,
96}
97
98impl std::fmt::Display for DeploymentStatus {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 match self {
101 DeploymentStatus::Pending => write!(f, "pending"),
102 DeploymentStatus::Deploying => write!(f, "deploying"),
103 DeploymentStatus::Running => write!(f, "running"),
104 DeploymentStatus::Failed { message } => write!(f, "failed: {message}"),
105 DeploymentStatus::Stopped => write!(f, "stopped"),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
119pub struct StoredUser {
120 pub id: String,
122
123 pub email: String,
125
126 pub display_name: String,
128
129 pub role: UserRole,
131
132 pub is_active: bool,
134
135 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
137 pub created_at: DateTime<Utc>,
138
139 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
141 pub updated_at: DateTime<Utc>,
142
143 #[schema(value_type = Option<String>, example = "2026-04-15T12:00:00Z")]
145 pub last_login_at: Option<DateTime<Utc>>,
146}
147
148impl StoredUser {
149 #[must_use]
151 pub fn new(email: impl Into<String>, display_name: impl Into<String>, role: UserRole) -> Self {
152 let now = Utc::now();
153 Self {
154 id: Uuid::new_v4().to_string(),
155 email: email.into().to_lowercase(),
156 display_name: display_name.into(),
157 role,
158 is_active: true,
159 created_at: now,
160 updated_at: now,
161 last_login_at: None,
162 }
163 }
164
165 pub fn touch_login(&mut self) {
167 let now = Utc::now();
168 self.last_login_at = Some(now);
169 self.updated_at = now;
170 }
171}
172
173#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
176#[serde(rename_all = "snake_case")]
177pub enum UserRole {
178 Admin,
180 User,
182}
183
184impl UserRole {
185 #[must_use]
187 pub fn as_str(self) -> &'static str {
188 match self {
189 UserRole::Admin => "admin",
190 UserRole::User => "user",
191 }
192 }
193}
194
195impl std::fmt::Display for UserRole {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 f.write_str(self.as_str())
198 }
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
211pub struct StoredEnvironment {
212 pub id: String,
214
215 pub name: String,
217
218 pub project_id: Option<String>,
220
221 pub description: Option<String>,
223
224 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
226 pub created_at: DateTime<Utc>,
227
228 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
230 pub updated_at: DateTime<Utc>,
231}
232
233impl StoredEnvironment {
234 #[must_use]
236 pub fn new(name: impl Into<String>, project_id: Option<String>) -> Self {
237 let now = Utc::now();
238 Self {
239 id: Uuid::new_v4().to_string(),
240 name: name.into(),
241 project_id,
242 description: None,
243 created_at: now,
244 updated_at: now,
245 }
246 }
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
256pub struct StoredProject {
257 pub id: String,
259
260 pub name: String,
262
263 pub description: Option<String>,
265
266 pub git_url: Option<String>,
268
269 pub git_branch: Option<String>,
271
272 pub git_credential_id: Option<String>,
274
275 pub build_kind: Option<BuildKind>,
277
278 pub build_path: Option<String>,
280
281 #[serde(default)]
288 pub deploy_spec_path: Option<String>,
289
290 pub registry_credential_id: Option<String>,
292
293 pub default_environment_id: Option<String>,
295
296 pub owner_id: Option<String>,
298
299 #[serde(default)]
302 pub auto_deploy: bool,
303
304 #[serde(default)]
308 pub poll_interval_secs: Option<u64>,
309
310 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
312 pub created_at: DateTime<Utc>,
313
314 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
316 pub updated_at: DateTime<Utc>,
317}
318
319impl StoredProject {
320 #[must_use]
322 pub fn new(name: impl Into<String>) -> Self {
323 let now = Utc::now();
324 Self {
325 id: Uuid::new_v4().to_string(),
326 name: name.into(),
327 description: None,
328 git_url: None,
329 git_branch: Some("main".to_string()),
330 git_credential_id: None,
331 build_kind: None,
332 build_path: None,
333 deploy_spec_path: None,
334 registry_credential_id: None,
335 default_environment_id: None,
336 owner_id: None,
337 auto_deploy: false,
338 poll_interval_secs: None,
339 created_at: now,
340 updated_at: now,
341 }
342 }
343}
344
345#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
347#[serde(rename_all = "snake_case")]
348pub enum BuildKind {
349 Dockerfile,
351 Compose,
353 ZImagefile,
355 Spec,
357}
358
359impl std::fmt::Display for BuildKind {
360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 match self {
362 BuildKind::Dockerfile => f.write_str("dockerfile"),
363 BuildKind::Compose => f.write_str("compose"),
364 BuildKind::ZImagefile => f.write_str("zimagefile"),
365 BuildKind::Spec => f.write_str("spec"),
366 }
367 }
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
381pub struct StoredVariable {
382 pub id: String,
384
385 pub name: String,
388
389 pub value: String,
391
392 pub scope: Option<String>,
394
395 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
397 pub created_at: DateTime<Utc>,
398
399 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
401 pub updated_at: DateTime<Utc>,
402}
403
404impl StoredVariable {
405 #[must_use]
407 pub fn new(name: impl Into<String>, value: impl Into<String>, scope: Option<String>) -> Self {
408 let now = Utc::now();
409 Self {
410 id: Uuid::new_v4().to_string(),
411 name: name.into(),
412 value: value.into(),
413 scope,
414 created_at: now,
415 updated_at: now,
416 }
417 }
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
430pub struct StoredSync {
431 pub id: String,
433
434 pub name: String,
436
437 pub project_id: Option<String>,
439
440 pub git_path: String,
442
443 #[serde(default)]
445 pub auto_apply: bool,
446
447 #[serde(default)]
451 pub delete_missing: bool,
452
453 pub last_applied_sha: Option<String>,
455
456 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
458 pub created_at: DateTime<Utc>,
459
460 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
462 pub updated_at: DateTime<Utc>,
463}
464
465impl StoredSync {
466 #[must_use]
468 pub fn new(name: impl Into<String>, git_path: impl Into<String>) -> Self {
469 let now = Utc::now();
470 Self {
471 id: Uuid::new_v4().to_string(),
472 name: name.into(),
473 project_id: None,
474 git_path: git_path.into(),
475 auto_apply: false,
476 delete_missing: false,
477 last_applied_sha: None,
478 created_at: now,
479 updated_at: now,
480 }
481 }
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
493pub struct StoredTask {
494 pub id: String,
496
497 pub name: String,
499
500 pub kind: TaskKind,
502
503 pub body: String,
505
506 pub project_id: Option<String>,
508
509 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
511 pub created_at: DateTime<Utc>,
512
513 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
515 pub updated_at: DateTime<Utc>,
516}
517
518impl StoredTask {
519 #[must_use]
521 pub fn new(
522 name: impl Into<String>,
523 kind: TaskKind,
524 body: impl Into<String>,
525 project_id: Option<String>,
526 ) -> Self {
527 let now = Utc::now();
528 Self {
529 id: Uuid::new_v4().to_string(),
530 name: name.into(),
531 kind,
532 body: body.into(),
533 project_id,
534 created_at: now,
535 updated_at: now,
536 }
537 }
538}
539
540#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
542#[serde(rename_all = "snake_case")]
543pub enum TaskKind {
544 Bash,
546}
547
548impl std::fmt::Display for TaskKind {
549 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
550 match self {
551 TaskKind::Bash => f.write_str("bash"),
552 }
553 }
554}
555
556#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
558pub struct TaskRun {
559 pub id: String,
561
562 pub task_id: String,
564
565 pub exit_code: Option<i32>,
567
568 pub stdout: String,
570
571 pub stderr: String,
573
574 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
576 pub started_at: DateTime<Utc>,
577
578 #[schema(value_type = Option<String>, example = "2026-04-15T12:00:01Z")]
580 pub finished_at: Option<DateTime<Utc>>,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
590pub struct StoredWorkflow {
591 pub id: String,
593
594 pub name: String,
596
597 pub steps: Vec<WorkflowStep>,
599
600 pub project_id: Option<String>,
602
603 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
605 pub created_at: DateTime<Utc>,
606
607 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
609 pub updated_at: DateTime<Utc>,
610}
611
612impl StoredWorkflow {
613 #[must_use]
615 pub fn new(name: impl Into<String>, steps: Vec<WorkflowStep>) -> Self {
616 let now = Utc::now();
617 Self {
618 id: Uuid::new_v4().to_string(),
619 name: name.into(),
620 steps,
621 project_id: None,
622 created_at: now,
623 updated_at: now,
624 }
625 }
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
630pub struct WorkflowStep {
631 pub name: String,
633
634 pub action: WorkflowAction,
636
637 #[serde(default)]
639 pub on_failure: Option<String>,
640}
641
642#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
644#[serde(tag = "type", rename_all = "snake_case")]
645pub enum WorkflowAction {
646 RunTask {
648 task_id: String,
650 },
651 BuildProject {
653 project_id: String,
655 },
656 DeployProject {
658 project_id: String,
660 },
661 ApplySync {
663 sync_id: String,
665 },
666}
667
668#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
670pub struct WorkflowRun {
671 pub id: String,
673
674 pub workflow_id: String,
676
677 pub status: WorkflowRunStatus,
679
680 pub step_results: Vec<StepResult>,
682
683 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
685 pub started_at: DateTime<Utc>,
686
687 #[schema(value_type = Option<String>, example = "2026-04-15T12:00:01Z")]
689 pub finished_at: Option<DateTime<Utc>>,
690}
691
692#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
694#[serde(rename_all = "snake_case")]
695pub enum WorkflowRunStatus {
696 Pending,
698 Running,
700 Completed,
702 Failed,
704}
705
706impl std::fmt::Display for WorkflowRunStatus {
707 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708 match self {
709 WorkflowRunStatus::Pending => f.write_str("pending"),
710 WorkflowRunStatus::Running => f.write_str("running"),
711 WorkflowRunStatus::Completed => f.write_str("completed"),
712 WorkflowRunStatus::Failed => f.write_str("failed"),
713 }
714 }
715}
716
717#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
719pub struct StepResult {
720 pub step_name: String,
722
723 pub status: String,
725
726 pub output: Option<String>,
728}
729
730#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
737pub struct StoredNotifier {
738 pub id: String,
740
741 pub name: String,
743
744 pub kind: NotifierKind,
746
747 pub config: NotifierConfig,
749
750 pub enabled: bool,
752
753 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
755 pub created_at: DateTime<Utc>,
756
757 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
759 pub updated_at: DateTime<Utc>,
760}
761
762impl StoredNotifier {
763 #[must_use]
765 pub fn new(name: impl Into<String>, kind: NotifierKind, config: NotifierConfig) -> Self {
766 let now = Utc::now();
767 Self {
768 id: Uuid::new_v4().to_string(),
769 name: name.into(),
770 kind,
771 config,
772 enabled: true,
773 created_at: now,
774 updated_at: now,
775 }
776 }
777}
778
779#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
781#[serde(rename_all = "snake_case")]
782pub enum NotifierKind {
783 Slack,
785 Discord,
787 Webhook,
789 Smtp,
791}
792
793impl std::fmt::Display for NotifierKind {
794 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
795 match self {
796 NotifierKind::Slack => f.write_str("slack"),
797 NotifierKind::Discord => f.write_str("discord"),
798 NotifierKind::Webhook => f.write_str("webhook"),
799 NotifierKind::Smtp => f.write_str("smtp"),
800 }
801 }
802}
803
804#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
806#[serde(tag = "type", rename_all = "snake_case")]
807pub enum NotifierConfig {
808 Slack {
810 webhook_url: String,
812 },
813 Discord {
815 webhook_url: String,
817 },
818 Webhook {
820 url: String,
822 #[serde(default)]
824 method: Option<String>,
825 #[serde(default)]
827 headers: Option<HashMap<String, String>>,
828 },
829 Smtp {
831 host: String,
833 port: u16,
835 username: String,
837 password: String,
839 from: String,
841 to: Vec<String>,
843 },
844}
845
846#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
852pub struct StoredUserGroup {
853 pub id: String,
855
856 pub name: String,
858
859 pub description: Option<String>,
861
862 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
864 pub created_at: DateTime<Utc>,
865
866 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
868 pub updated_at: DateTime<Utc>,
869}
870
871impl StoredUserGroup {
872 #[must_use]
874 pub fn new(name: impl Into<String>) -> Self {
875 let now = Utc::now();
876 Self {
877 id: Uuid::new_v4().to_string(),
878 name: name.into(),
879 description: None,
880 created_at: now,
881 updated_at: now,
882 }
883 }
884}
885
886#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
892#[serde(rename_all = "snake_case")]
893pub enum SubjectKind {
894 User,
896 Group,
898}
899
900impl std::fmt::Display for SubjectKind {
901 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
902 match self {
903 SubjectKind::User => f.write_str("user"),
904 SubjectKind::Group => f.write_str("group"),
905 }
906 }
907}
908
909#[derive(
912 Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, utoipa::ToSchema,
913)]
914#[serde(rename_all = "snake_case")]
915pub enum PermissionLevel {
916 None,
918 Read,
920 Execute,
922 Write,
924}
925
926impl std::fmt::Display for PermissionLevel {
927 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
928 match self {
929 PermissionLevel::None => f.write_str("none"),
930 PermissionLevel::Read => f.write_str("read"),
931 PermissionLevel::Execute => f.write_str("execute"),
932 PermissionLevel::Write => f.write_str("write"),
933 }
934 }
935}
936
937#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
940pub struct StoredPermission {
941 pub id: String,
943
944 pub subject_kind: SubjectKind,
946
947 pub subject_id: String,
949
950 pub resource_kind: String,
952
953 pub resource_id: Option<String>,
956
957 pub level: PermissionLevel,
959
960 #[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
962 pub created_at: DateTime<Utc>,
963}
964
965impl StoredPermission {
966 #[must_use]
968 pub fn new(
969 subject_kind: SubjectKind,
970 subject_id: impl Into<String>,
971 resource_kind: impl Into<String>,
972 resource_id: Option<String>,
973 level: PermissionLevel,
974 ) -> Self {
975 Self {
976 id: Uuid::new_v4().to_string(),
977 subject_kind,
978 subject_id: subject_id.into(),
979 resource_kind: resource_kind.into(),
980 resource_id,
981 level,
982 created_at: Utc::now(),
983 }
984 }
985}
986
987#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
998pub struct OidcIdentity {
999 pub id: String,
1001 pub user_id: String,
1003 pub provider: String,
1005 pub subject: String,
1007 pub email_at_link: Option<String>,
1009 #[schema(value_type = String, format = DateTime)]
1010 pub created_at: DateTime<Utc>,
1011 #[schema(value_type = String, format = DateTime)]
1012 pub updated_at: DateTime<Utc>,
1013}
1014
1015impl OidcIdentity {
1016 #[must_use]
1018 pub fn new(
1019 user_id: impl Into<String>,
1020 provider: impl Into<String>,
1021 subject: impl Into<String>,
1022 email_at_link: Option<String>,
1023 ) -> Self {
1024 let now = Utc::now();
1025 Self {
1026 id: Uuid::new_v4().to_string(),
1027 user_id: user_id.into(),
1028 provider: provider.into(),
1029 subject: subject.into(),
1030 email_at_link,
1031 created_at: now,
1032 updated_at: now,
1033 }
1034 }
1035}
1036
1037