#[cfg(feature = "graphql")]
use async_graphql::scalar;
use n5i::PodSecurityStandard;
pub use n5i::utils::MultiLanguageItem;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::hash::Hash;
pub use n5i::settings::Setting;
use crate::utils::true_default;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum Dependency {
OneDependency(String),
AlternativeDependency(HashSet<String>),
}
#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)]
pub struct SvcPorts {
pub udp: Vec<u16>,
pub tcp: Vec<u16>,
}
#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Copy, Hash)]
#[cfg_attr(feature = "graphql", derive(async_graphql::Enum))]
pub enum DangerLevel {
#[default]
None,
Low,
High,
}
#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
pub struct Permission {
pub id: String,
pub name: MultiLanguageItem,
pub description: MultiLanguageItem,
pub includes: Vec<String>,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub secrets: BTreeMap<String, Vec<String>>,
pub hidden: Vec<String>,
pub services: BTreeMap<String, SvcPorts>,
pub open_to_all_users: bool,
#[serde(default)]
pub volumes: Vec<VolumePermission>,
#[serde(default)]
pub danger_level: DangerLevel,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
pub struct VolumePermission {
pub volume: String,
pub sub_paths: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "graphql", derive(async_graphql::Enum))]
pub enum VolumeAccessPolicy {
ReadWriteOnce,
#[default]
ReadWriteMany,
ReadWriteOncePod,
}
#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
#[cfg_attr(feature = "graphql", graphql(name = "VolumeDefinition"))]
pub struct Volume {
pub minimum_size: u64,
pub recommended_size: u64,
pub name: MultiLanguageItem,
pub description: MultiLanguageItem,
#[serde(default)]
pub access_policy: VolumeAccessPolicy,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum Runtime {
#[serde(rename = "AppYml")]
Kubernetes,
Plugin(String),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
pub struct InputDeclaration {
pub label: MultiLanguageItem,
pub description: MultiLanguageItem,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
pub struct UiMenuEntry {
pub name: MultiLanguageItem,
pub icon: String,
pub path: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
pub struct UiModule {
pub menu_entries: Vec<UiMenuEntry>,
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct Settings(pub BTreeMap<String, Setting>);
#[cfg(feature = "graphql")]
scalar!(Settings);
impl Settings {
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "graphql", derive(async_graphql::Enum))]
pub enum AppScope {
#[default]
User,
System,
}
impl AppScope {
#[must_use]
pub fn get_ns(&self, user: &str, app_id: &str) -> String {
match self {
Self::User => format!("{user}-{app_id}"),
Self::System => app_id.to_string(),
}
}
}
impl TryFrom<i32> for AppScope {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::User),
1 => Ok(Self::System),
_ => Err(format!("Invalid AppScope value: {value}")),
}
}
}
impl From<AppScope> for i32 {
fn from(value: AppScope) -> Self {
match value {
AppScope::User => 0,
AppScope::System => 1,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AppComponent {
pub id: String,
pub name: MultiLanguageItem,
pub description: MultiLanguageItem,
pub icon: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
pub struct IngressOption {
pub plugin_id: String,
pub id: String,
pub name: MultiLanguageItem,
pub can_choose_domain: bool,
pub domain_placeholder: MultiLanguageItem,
pub component: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
pub struct PackagerDetails {
pub name: String,
pub website: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum AppPermission {
Simple(String),
Custom {
app_id: String,
id: String,
danger_level: DangerLevel,
name: MultiLanguageItem,
description: MultiLanguageItem,
},
}
impl PartialEq for AppPermission {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(AppPermission::Simple(id1), AppPermission::Simple(id2)) => id1 == id2,
(
AppPermission::Custom {
app_id: app_id1,
id: id1,
..
},
AppPermission::Custom {
app_id: app_id2,
id: id2,
..
},
) => app_id1 == app_id2 && id1 == id2,
_ => false,
}
}
}
impl Eq for AppPermission {}
impl PartialOrd for AppPermission {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AppPermission {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(AppPermission::Simple(id1), AppPermission::Simple(id2)) => id1.cmp(id2),
(
AppPermission::Custom {
app_id: app_id1,
id: id1,
..
},
AppPermission::Custom {
app_id: app_id2,
id: id2,
..
},
) => match app_id1.cmp(app_id2) {
Ordering::Equal => id1.cmp(id2),
other => other,
},
(AppPermission::Simple(_), AppPermission::Custom { .. }) => Ordering::Less,
(AppPermission::Custom { .. }, AppPermission::Simple(_)) => Ordering::Greater,
}
}
}
impl AppPermission {
pub fn app_id(&self) -> Result<&str, &str> {
match self {
AppPermission::Simple(id) => id.split_once('/').map(|(app_id, _)| app_id).ok_or(id),
AppPermission::Custom { app_id, .. } => Ok(app_id),
}
}
pub fn id(&self) -> String {
match self {
AppPermission::Simple(id) => id.clone(),
AppPermission::Custom { app_id, id, .. } => format!("{app_id}/{id}"),
}
}
pub fn app_and_id(&self) -> Result<(&str, &str), &str> {
match self {
AppPermission::Simple(id) => id.split_once('/').ok_or(id),
AppPermission::Custom { app_id, id, .. } => Ok((app_id, id)),
}
}
pub fn is_builtin(&self) -> bool {
match self {
AppPermission::Simple(s) => s.starts_with("builtin/"),
AppPermission::Custom { .. } => false,
}
}
}
impl Hash for AppPermission {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
AppPermission::Simple(id) => id.hash(state),
AppPermission::Custom { id, app_id, .. } => {
let combined = format!("{app_id}/{id}");
combined.hash(state);
}
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppUpdateInfo {
pub id: String,
pub current_version: semver::Version,
pub new_version: semver::Version,
pub release_notes: BTreeMap<String, MultiLanguageItem>,
pub current_permissions: HashSet<AppPermission>,
pub new_permissions: HashSet<AppPermission>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
#[allow(clippy::struct_excessive_bools)]
pub struct Metadata {
pub id: String,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub base_app: Option<String>,
pub name: MultiLanguageItem,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub version: semver::Version,
#[cfg_attr(feature = "graphql", graphql(name = "version"))]
pub display_version: String,
pub category: MultiLanguageItem,
pub tagline: MultiLanguageItem,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub screenshots: Vec<String>,
pub developers: BTreeMap<String, String>,
pub packager: Option<PackagerDetails>,
pub description: MultiLanguageItem,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "graphql", graphql(skip))]
pub dependencies: Vec<Dependency>,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub base_permissions: HashSet<AppPermission>,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub has_permissions: HashSet<AppPermission>,
pub exposes_permissions: Vec<Permission>,
pub repos: BTreeMap<String, String>,
pub support: String,
pub icon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_username: Option<String>,
pub default_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub implements: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub release_notes: BTreeMap<String, MultiLanguageItem>,
pub license: String,
#[serde(default, skip_serializing_if = "Settings::is_empty")]
#[cfg_attr(feature = "graphql", graphql(skip))]
pub settings: Settings,
pub volumes: BTreeMap<String, Volume>,
pub ports: Vec<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "graphql", graphql(skip))]
pub exported_data: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub runtime: Runtime,
pub allowed_scopes: Vec<AppScope>,
pub default_scope: AppScope,
#[serde(default)]
pub ui_module: Option<UiModule>,
#[serde(default = "true_default")]
pub supports_ingress: bool,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub can_be_protected: bool,
pub raw_ingress: bool,
pub post_install_redirect: Option<String>,
#[serde(default)]
#[cfg_attr(feature = "graphql", graphql(skip))]
pub components: Vec<AppComponent>,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub preload_type: Option<PreloadType>,
pub requires_cluster_config: Option<bool>,
pub deprecated: bool,
#[serde(default)]
pub ingress_options: Vec<IngressOption>,
#[serde(default)]
pub supports_multi_instance: bool,
#[serde(default)]
pub pod_security_standard: PodSecurityStandard,
pub is_snapshot_compatible: Option<bool>,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub downgrade_compatibility: Option<semver::Version>,
#[cfg_attr(feature = "graphql", graphql(skip))]
pub upgrade_compatibility: Option<semver::Version>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum PreloadType {
Nirvati,
MainVolumeFromSubdir(String),
MainVolumeFromRoot,
}