1use anyhow::Context;
9use colored::Colorize;
10use include_dir::{Dir, include_dir};
11use serde::{Deserialize, Serialize};
12use std::{
13 sync::{Arc, LazyLock},
14 time::Instant,
15};
16use utoipa::ToSchema;
17
18pub mod cache;
19pub mod cap;
20pub mod captcha;
21pub mod database;
22pub mod deserialize;
23pub mod env;
24pub mod events;
25pub mod extensions;
26pub mod extract;
27pub mod jwt;
28pub mod mail;
29pub mod models;
30pub mod ntp;
31pub mod payload;
32pub mod permissions;
33pub mod prelude;
34pub mod response;
35pub mod settings;
36pub mod storage;
37pub mod telemetry;
38pub mod utils;
39
40pub use payload::Payload;
41pub use schema_extension_core::Extendible;
42
43pub const VERSION: &str = env!("CARGO_PKG_VERSION");
44pub const GIT_COMMIT: &str = env!("CARGO_GIT_COMMIT");
45pub const GIT_BRANCH: &str = env!("CARGO_GIT_BRANCH");
46pub const TARGET: &str = env!("CARGO_TARGET");
47
48pub fn full_version() -> String {
49 if GIT_BRANCH == "unknown" {
50 VERSION.to_string()
51 } else {
52 format!("{VERSION}:{GIT_COMMIT}@{GIT_BRANCH}")
53 }
54}
55
56pub const BUFFER_SIZE: usize = 32 * 1024;
57
58pub type GetIp = axum::extract::Extension<std::net::IpAddr>;
59
60#[derive(ToSchema, Serialize)]
61pub struct ApiError {
62 pub errors: Vec<String>,
63}
64
65impl ApiError {
66 #[inline]
67 pub fn new_value(errors: &[&str]) -> serde_json::Value {
68 serde_json::json!({
69 "errors": errors,
70 })
71 }
72
73 #[inline]
74 pub fn new_strings_value(errors: Vec<String>) -> serde_json::Value {
75 serde_json::json!({
76 "errors": errors,
77 })
78 }
79
80 #[inline]
81 pub fn new_wings_value(error: wings_api::ApiError) -> serde_json::Value {
82 serde_json::json!({
83 "errors": [error.error],
84 })
85 }
86}
87
88#[derive(Debug, ToSchema, Deserialize, Serialize, Clone, Copy)]
89#[serde(rename_all = "snake_case")]
90pub enum AppContainerType {
91 Official,
92 OfficialHeavy,
93 Unknown,
94 None,
95}
96
97pub struct AppState {
98 pub start_time: Instant,
99 pub container_type: AppContainerType,
100 pub version: String,
101
102 pub client: reqwest::Client,
103
104 pub extensions: Arc<extensions::manager::ExtensionManager>,
105 pub background_tasks: Arc<extensions::background_tasks::BackgroundTaskManager>,
106 pub shutdown_handlers: Arc<extensions::shutdown_handlers::ShutdownHandlerManager>,
107 pub settings: Arc<settings::Settings>,
108 pub jwt: Arc<jwt::Jwt>,
109 pub ntp: Arc<ntp::Ntp>,
110 pub storage: Arc<storage::Storage>,
111 pub captcha: Arc<captcha::Captcha>,
112 pub mail: Arc<mail::Mail>,
113 pub database: Arc<database::Database>,
114 pub cache: Arc<cache::Cache>,
115 pub env: Arc<env::Env>,
116}
117
118impl AppState {
119 pub async fn new_cli(env: Option<Arc<env::Env>>) -> Result<State, anyhow::Error> {
120 let env = match env {
121 Some(env) => env,
122 None => {
123 eprintln!(
124 "{}",
125 "please setup the new panel environment before using this command.".red()
126 );
127 std::process::exit(1);
128 }
129 };
130
131 let jwt = Arc::new(jwt::Jwt::new(&env));
132 let ntp = ntp::Ntp::new();
133 let cache = cache::Cache::new(&env).await;
134 let database = Arc::new(database::Database::new(&env, cache.clone()).await);
135
136 let background_tasks =
137 Arc::new(extensions::background_tasks::BackgroundTaskManager::default());
138 let shutdown_handlers =
139 Arc::new(extensions::shutdown_handlers::ShutdownHandlerManager::default());
140 let settings = Arc::new(
141 settings::Settings::new(database.clone())
142 .await
143 .context("failed to load settings")?,
144 );
145 let storage = Arc::new(storage::Storage::new(settings.clone()));
146 let captcha = Arc::new(captcha::Captcha::new(settings.clone()));
147 let mail = Arc::new(mail::Mail::new(settings.clone()));
148
149 let state = Arc::new(AppState {
150 start_time: Instant::now(),
151 container_type: match std::env::var("OCI_CONTAINER").as_deref() {
152 Ok("official") => AppContainerType::Official,
153 Ok("official-heavy") => AppContainerType::OfficialHeavy,
154 Ok(_) => AppContainerType::Unknown,
155 Err(_) => AppContainerType::None,
156 },
157 version: full_version(),
158
159 client: reqwest::ClientBuilder::new()
160 .user_agent(format!("github.com/calagopus/panel {}", VERSION))
161 .build()
162 .unwrap(),
163
164 extensions: Arc::new(extensions::manager::ExtensionManager::new(vec![])),
165 background_tasks: background_tasks.clone(),
166 shutdown_handlers: shutdown_handlers.clone(),
167 settings: settings.clone(),
168 jwt,
169 ntp,
170 storage,
171 captcha,
172 mail,
173 database: database.clone(),
174 cache: cache.clone(),
175 env: env.clone(),
176 });
177
178 Ok(state)
179 }
180}
181
182pub type State = Arc<AppState>;
183pub type GetState = axum::extract::State<State>;
184
185#[inline(always)]
186#[cold]
187fn cold_path() {}
188
189#[inline(always)]
190pub fn likely(b: bool) -> bool {
191 if b {
192 true
193 } else {
194 cold_path();
195 false
196 }
197}
198
199#[inline(always)]
200pub fn unlikely(b: bool) -> bool {
201 if b {
202 cold_path();
203 true
204 } else {
205 false
206 }
207}
208
209pub const FRONTEND_ASSETS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist");
210
211pub static FRONTEND_LANGUAGES: LazyLock<Vec<compact_str::CompactString>> = LazyLock::new(|| {
212 let mut languages = Vec::new();
213
214 let Some(translations) = FRONTEND_ASSETS.get_dir("translations") else {
215 return languages;
216 };
217
218 for translation in translations.files() {
219 let Some(file_name) = translation.path().file_name() else {
220 continue;
221 };
222
223 languages.push(file_name.to_string_lossy().trim_end_matches(".json").into());
224 }
225
226 languages
227});