feat: ship public ops features and cache docker builds
Some checks failed
docker-images / build-and-push (admin, admin, termi-astro-admin, admin/Dockerfile) (push) Failing after 13s
docker-images / build-and-push (frontend, frontend, termi-astro-frontend, frontend/Dockerfile) (push) Has been cancelled
docker-images / build-and-push (backend, backend, termi-astro-backend, backend/Dockerfile) (push) Has been cancelled

This commit is contained in:
2026-04-01 13:22:19 +08:00
parent 669b79cc95
commit 497a9d713d
75 changed files with 6985 additions and 668 deletions

View File

@@ -93,6 +93,22 @@ pub struct SiteSettingsPayload {
pub ai_enabled: Option<bool>,
#[serde(default, alias = "paragraphCommentsEnabled")]
pub paragraph_comments_enabled: Option<bool>,
#[serde(default, alias = "commentTurnstileEnabled")]
pub comment_turnstile_enabled: Option<bool>,
#[serde(default, alias = "subscriptionTurnstileEnabled")]
pub subscription_turnstile_enabled: Option<bool>,
#[serde(default, alias = "webPushEnabled")]
pub web_push_enabled: Option<bool>,
#[serde(default, alias = "turnstileSiteKey")]
pub turnstile_site_key: Option<String>,
#[serde(default, alias = "turnstileSecretKey")]
pub turnstile_secret_key: Option<String>,
#[serde(default, alias = "webPushVapidPublicKey")]
pub web_push_vapid_public_key: Option<String>,
#[serde(default, alias = "webPushVapidPrivateKey")]
pub web_push_vapid_private_key: Option<String>,
#[serde(default, alias = "webPushVapidSubject")]
pub web_push_vapid_subject: Option<String>,
#[serde(default, alias = "aiProvider")]
pub ai_provider: Option<String>,
#[serde(default, alias = "aiApiBase")]
@@ -139,6 +155,8 @@ pub struct SiteSettingsPayload {
pub seo_default_twitter_handle: Option<String>,
#[serde(default, alias = "notificationWebhookUrl")]
pub notification_webhook_url: Option<String>,
#[serde(default, alias = "notificationChannelType")]
pub notification_channel_type: Option<String>,
#[serde(default, alias = "notificationCommentEnabled")]
pub notification_comment_enabled: Option<bool>,
#[serde(default, alias = "notificationFriendLinkEnabled")]
@@ -177,6 +195,11 @@ pub struct PublicSiteSettingsResponse {
pub music_playlist: Option<serde_json::Value>,
pub ai_enabled: bool,
pub paragraph_comments_enabled: bool,
pub comment_turnstile_enabled: bool,
pub subscription_turnstile_enabled: bool,
pub web_push_enabled: bool,
pub turnstile_site_key: Option<String>,
pub web_push_vapid_public_key: Option<String>,
pub subscription_popup_enabled: bool,
pub subscription_popup_title: String,
pub subscription_popup_description: String,
@@ -220,6 +243,17 @@ fn normalize_optional_int(value: Option<i32>, min: i32, max: i32) -> Option<i32>
value.map(|item| item.clamp(min, max))
}
fn normalize_notification_channel_type(value: Option<String>) -> Option<String> {
value.and_then(|item| {
let normalized = item.trim().to_ascii_lowercase();
match normalized.as_str() {
"ntfy" => Some("ntfy".to_string()),
"webhook" => Some("webhook".to_string()),
_ => None,
}
})
}
pub(crate) fn default_subscription_popup_enabled() -> bool {
true
}
@@ -515,6 +549,32 @@ impl SiteSettingsPayload {
if let Some(paragraph_comments_enabled) = self.paragraph_comments_enabled {
item.paragraph_comments_enabled = Some(paragraph_comments_enabled);
}
if let Some(comment_turnstile_enabled) = self.comment_turnstile_enabled {
item.comment_turnstile_enabled = Some(comment_turnstile_enabled);
}
if let Some(subscription_turnstile_enabled) = self.subscription_turnstile_enabled {
item.subscription_turnstile_enabled = Some(subscription_turnstile_enabled);
}
if let Some(web_push_enabled) = self.web_push_enabled {
item.web_push_enabled = Some(web_push_enabled);
}
if let Some(turnstile_site_key) = self.turnstile_site_key {
item.turnstile_site_key = normalize_optional_string(Some(turnstile_site_key));
}
if let Some(turnstile_secret_key) = self.turnstile_secret_key {
item.turnstile_secret_key = normalize_optional_string(Some(turnstile_secret_key));
}
if let Some(web_push_vapid_public_key) = self.web_push_vapid_public_key {
item.web_push_vapid_public_key =
normalize_optional_string(Some(web_push_vapid_public_key));
}
if let Some(web_push_vapid_private_key) = self.web_push_vapid_private_key {
item.web_push_vapid_private_key =
normalize_optional_string(Some(web_push_vapid_private_key));
}
if let Some(web_push_vapid_subject) = self.web_push_vapid_subject {
item.web_push_vapid_subject = normalize_optional_string(Some(web_push_vapid_subject));
}
let provider_list_supplied = self.ai_providers.is_some();
let provided_ai_providers = self.ai_providers.map(normalize_ai_provider_configs);
let requested_active_provider_id = self
@@ -591,6 +651,10 @@ impl SiteSettingsPayload {
item.notification_webhook_url =
normalize_optional_string(Some(notification_webhook_url));
}
if self.notification_channel_type.is_some() {
item.notification_channel_type =
normalize_notification_channel_type(self.notification_channel_type);
}
if let Some(notification_comment_enabled) = self.notification_comment_enabled {
item.notification_comment_enabled = Some(notification_comment_enabled);
}
@@ -699,6 +763,14 @@ fn default_payload() -> SiteSettingsPayload {
]),
ai_enabled: Some(false),
paragraph_comments_enabled: Some(true),
comment_turnstile_enabled: Some(false),
subscription_turnstile_enabled: Some(false),
web_push_enabled: Some(false),
turnstile_site_key: None,
turnstile_secret_key: None,
web_push_vapid_public_key: None,
web_push_vapid_private_key: None,
web_push_vapid_subject: None,
ai_provider: Some(ai::provider_name(None)),
ai_api_base: Some(ai::default_api_base().to_string()),
ai_api_key: Some(ai::default_api_key().to_string()),
@@ -725,6 +797,7 @@ fn default_payload() -> SiteSettingsPayload {
seo_default_og_image: None,
seo_default_twitter_handle: None,
notification_webhook_url: None,
notification_channel_type: Some("webhook".to_string()),
notification_comment_enabled: Some(false),
notification_friend_link_enabled: Some(false),
subscription_popup_enabled: Some(default_subscription_popup_enabled()),
@@ -760,6 +833,18 @@ pub(crate) async fn load_current(ctx: &AppContext) -> Result<Model> {
}
fn public_response(model: Model) -> PublicSiteSettingsResponse {
let turnstile_site_key = crate::services::turnstile::site_key(&model);
let web_push_vapid_public_key = crate::services::web_push::public_key(&model);
let comment_turnstile_enabled = crate::services::turnstile::is_enabled(
&model,
crate::services::turnstile::TurnstileScope::Comment,
);
let subscription_turnstile_enabled = crate::services::turnstile::is_enabled(
&model,
crate::services::turnstile::TurnstileScope::Subscription,
);
let web_push_enabled = crate::services::web_push::is_enabled(&model);
PublicSiteSettingsResponse {
id: model.id,
site_name: model.site_name,
@@ -781,6 +866,11 @@ fn public_response(model: Model) -> PublicSiteSettingsResponse {
music_playlist: model.music_playlist,
ai_enabled: model.ai_enabled.unwrap_or(false),
paragraph_comments_enabled: model.paragraph_comments_enabled.unwrap_or(true),
comment_turnstile_enabled,
subscription_turnstile_enabled,
web_push_enabled,
turnstile_site_key,
web_push_vapid_public_key,
subscription_popup_enabled: model
.subscription_popup_enabled
.unwrap_or_else(default_subscription_popup_enabled),