use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::spec::DeploymentSpec;
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredDeployment {
pub name: String,
#[schema(value_type = Object)]
pub spec: DeploymentSpec,
pub status: DeploymentStatus,
#[schema(value_type = String, example = "2025-01-27T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2025-01-27T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredDeployment {
#[must_use]
pub fn new(spec: DeploymentSpec) -> Self {
let now = Utc::now();
Self {
name: spec.deployment.clone(),
spec,
status: DeploymentStatus::Pending,
created_at: now,
updated_at: now,
}
}
pub fn update_spec(&mut self, spec: DeploymentSpec) {
self.spec = spec;
self.updated_at = Utc::now();
}
pub fn update_status(&mut self, status: DeploymentStatus) {
self.status = status;
self.updated_at = Utc::now();
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
#[serde(tag = "state", rename_all = "snake_case")]
pub enum DeploymentStatus {
Pending,
Deploying,
Running,
Failed {
message: String,
},
Stopped,
}
impl std::fmt::Display for DeploymentStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeploymentStatus::Pending => write!(f, "pending"),
DeploymentStatus::Deploying => write!(f, "deploying"),
DeploymentStatus::Running => write!(f, "running"),
DeploymentStatus::Failed { message } => write!(f, "failed: {message}"),
DeploymentStatus::Stopped => write!(f, "stopped"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredUser {
pub id: String,
pub email: String,
pub display_name: String,
pub role: UserRole,
pub is_active: bool,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
#[schema(value_type = Option<String>, example = "2026-04-15T12:00:00Z")]
pub last_login_at: Option<DateTime<Utc>>,
}
impl StoredUser {
#[must_use]
pub fn new(email: impl Into<String>, display_name: impl Into<String>, role: UserRole) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
email: email.into().to_lowercase(),
display_name: display_name.into(),
role,
is_active: true,
created_at: now,
updated_at: now,
last_login_at: None,
}
}
pub fn touch_login(&mut self) {
let now = Utc::now();
self.last_login_at = Some(now);
self.updated_at = now;
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum UserRole {
Admin,
User,
}
impl UserRole {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
UserRole::Admin => "admin",
UserRole::User => "user",
}
}
}
impl std::fmt::Display for UserRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredEnvironment {
pub id: String,
pub name: String,
pub project_id: Option<String>,
pub description: Option<String>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredEnvironment {
#[must_use]
pub fn new(name: impl Into<String>, project_id: Option<String>) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
name: name.into(),
project_id,
description: None,
created_at: now,
updated_at: now,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredProject {
pub id: String,
pub name: String,
pub description: Option<String>,
pub git_url: Option<String>,
pub git_branch: Option<String>,
pub git_credential_id: Option<String>,
pub build_kind: Option<BuildKind>,
pub build_path: Option<String>,
#[serde(default)]
pub deploy_spec_path: Option<String>,
pub registry_credential_id: Option<String>,
pub default_environment_id: Option<String>,
pub owner_id: Option<String>,
#[serde(default)]
pub auto_deploy: bool,
#[serde(default)]
pub poll_interval_secs: Option<u64>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredProject {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
name: name.into(),
description: None,
git_url: None,
git_branch: Some("main".to_string()),
git_credential_id: None,
build_kind: None,
build_path: None,
deploy_spec_path: None,
registry_credential_id: None,
default_environment_id: None,
owner_id: None,
auto_deploy: false,
poll_interval_secs: None,
created_at: now,
updated_at: now,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum BuildKind {
Dockerfile,
Compose,
ZImagefile,
Spec,
}
impl std::fmt::Display for BuildKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuildKind::Dockerfile => f.write_str("dockerfile"),
BuildKind::Compose => f.write_str("compose"),
BuildKind::ZImagefile => f.write_str("zimagefile"),
BuildKind::Spec => f.write_str("spec"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredVariable {
pub id: String,
pub name: String,
pub value: String,
pub scope: Option<String>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredVariable {
#[must_use]
pub fn new(name: impl Into<String>, value: impl Into<String>, scope: Option<String>) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
name: name.into(),
value: value.into(),
scope,
created_at: now,
updated_at: now,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredSync {
pub id: String,
pub name: String,
pub project_id: Option<String>,
pub git_path: String,
#[serde(default)]
pub auto_apply: bool,
#[serde(default)]
pub delete_missing: bool,
pub last_applied_sha: Option<String>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredSync {
#[must_use]
pub fn new(name: impl Into<String>, git_path: impl Into<String>) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
name: name.into(),
project_id: None,
git_path: git_path.into(),
auto_apply: false,
delete_missing: false,
last_applied_sha: None,
created_at: now,
updated_at: now,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredTask {
pub id: String,
pub name: String,
pub kind: TaskKind,
pub body: String,
pub project_id: Option<String>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredTask {
#[must_use]
pub fn new(
name: impl Into<String>,
kind: TaskKind,
body: impl Into<String>,
project_id: Option<String>,
) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
name: name.into(),
kind,
body: body.into(),
project_id,
created_at: now,
updated_at: now,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum TaskKind {
Bash,
}
impl std::fmt::Display for TaskKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TaskKind::Bash => f.write_str("bash"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct TaskRun {
pub id: String,
pub task_id: String,
pub exit_code: Option<i32>,
pub stdout: String,
pub stderr: String,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub started_at: DateTime<Utc>,
#[schema(value_type = Option<String>, example = "2026-04-15T12:00:01Z")]
pub finished_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredWorkflow {
pub id: String,
pub name: String,
pub steps: Vec<WorkflowStep>,
pub project_id: Option<String>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredWorkflow {
#[must_use]
pub fn new(name: impl Into<String>, steps: Vec<WorkflowStep>) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
name: name.into(),
steps,
project_id: None,
created_at: now,
updated_at: now,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WorkflowStep {
pub name: String,
pub action: WorkflowAction,
#[serde(default)]
pub on_failure: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum WorkflowAction {
RunTask {
task_id: String,
},
BuildProject {
project_id: String,
},
DeployProject {
project_id: String,
},
ApplySync {
sync_id: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WorkflowRun {
pub id: String,
pub workflow_id: String,
pub status: WorkflowRunStatus,
pub step_results: Vec<StepResult>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub started_at: DateTime<Utc>,
#[schema(value_type = Option<String>, example = "2026-04-15T12:00:01Z")]
pub finished_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum WorkflowRunStatus {
Pending,
Running,
Completed,
Failed,
}
impl std::fmt::Display for WorkflowRunStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WorkflowRunStatus::Pending => f.write_str("pending"),
WorkflowRunStatus::Running => f.write_str("running"),
WorkflowRunStatus::Completed => f.write_str("completed"),
WorkflowRunStatus::Failed => f.write_str("failed"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StepResult {
pub step_name: String,
pub status: String,
pub output: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredNotifier {
pub id: String,
pub name: String,
pub kind: NotifierKind,
pub config: NotifierConfig,
pub enabled: bool,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredNotifier {
#[must_use]
pub fn new(name: impl Into<String>, kind: NotifierKind, config: NotifierConfig) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
name: name.into(),
kind,
config,
enabled: true,
created_at: now,
updated_at: now,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum NotifierKind {
Slack,
Discord,
Webhook,
Smtp,
}
impl std::fmt::Display for NotifierKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NotifierKind::Slack => f.write_str("slack"),
NotifierKind::Discord => f.write_str("discord"),
NotifierKind::Webhook => f.write_str("webhook"),
NotifierKind::Smtp => f.write_str("smtp"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum NotifierConfig {
Slack {
webhook_url: String,
},
Discord {
webhook_url: String,
},
Webhook {
url: String,
#[serde(default)]
method: Option<String>,
#[serde(default)]
headers: Option<HashMap<String, String>>,
},
Smtp {
host: String,
port: u16,
username: String,
password: String,
from: String,
to: Vec<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredUserGroup {
pub id: String,
pub name: String,
pub description: Option<String>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub updated_at: DateTime<Utc>,
}
impl StoredUserGroup {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
name: name.into(),
description: None,
created_at: now,
updated_at: now,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum SubjectKind {
User,
Group,
}
impl std::fmt::Display for SubjectKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SubjectKind::User => f.write_str("user"),
SubjectKind::Group => f.write_str("group"),
}
}
}
#[derive(
Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, utoipa::ToSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum PermissionLevel {
None,
Read,
Execute,
Write,
}
impl std::fmt::Display for PermissionLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PermissionLevel::None => f.write_str("none"),
PermissionLevel::Read => f.write_str("read"),
PermissionLevel::Execute => f.write_str("execute"),
PermissionLevel::Write => f.write_str("write"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StoredPermission {
pub id: String,
pub subject_kind: SubjectKind,
pub subject_id: String,
pub resource_kind: String,
pub resource_id: Option<String>,
pub level: PermissionLevel,
#[schema(value_type = String, example = "2026-04-15T12:00:00Z")]
pub created_at: DateTime<Utc>,
}
impl StoredPermission {
#[must_use]
pub fn new(
subject_kind: SubjectKind,
subject_id: impl Into<String>,
resource_kind: impl Into<String>,
resource_id: Option<String>,
level: PermissionLevel,
) -> Self {
Self {
id: Uuid::new_v4().to_string(),
subject_kind,
subject_id: subject_id.into(),
resource_kind: resource_kind.into(),
resource_id,
level,
created_at: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct OidcIdentity {
pub id: String,
pub user_id: String,
pub provider: String,
pub subject: String,
pub email_at_link: Option<String>,
#[schema(value_type = String, format = DateTime)]
pub created_at: DateTime<Utc>,
#[schema(value_type = String, format = DateTime)]
pub updated_at: DateTime<Utc>,
}
impl OidcIdentity {
#[must_use]
pub fn new(
user_id: impl Into<String>,
provider: impl Into<String>,
subject: impl Into<String>,
email_at_link: Option<String>,
) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
user_id: user_id.into(),
provider: provider.into(),
subject: subject.into(),
email_at_link,
created_at: now,
updated_at: now,
}
}
}