shared/settings/
metadata.rs1use crate::prelude::StringExt;
2
3use super::{
4 ExtensionSettings, SettingsDeserializeExt, SettingsDeserializer, SettingsSerializeExt,
5 SettingsSerializer, app::AppSettingsApp,
6};
7use compact_str::ToCompactString;
8use serde::{Deserialize, Serialize};
9use utoipa::ToSchema;
10
11#[derive(ToSchema, Serialize, Deserialize, Clone, Copy)]
12#[serde(rename_all = "snake_case")]
13pub enum TwitterCard {
14 Summary,
15 SummaryLargeImage,
16}
17
18#[derive(Clone, ToSchema, Serialize, Deserialize)]
19pub struct AppSettingsMetadata {
20 pub description: Option<compact_str::CompactString>,
21 pub theme_color: compact_str::CompactString,
22 pub og_image: Option<compact_str::CompactString>,
23 pub twitter_card: TwitterCard,
24 pub indexable: bool,
25}
26
27#[derive(Serialize)]
28pub struct ResolvedMetadata {
29 pub description: compact_str::CompactString,
30 pub theme_color: compact_str::CompactString,
31 pub og_image: compact_str::CompactString,
32 pub twitter_card: &'static str,
33 pub robots: &'static str,
34}
35
36impl AppSettingsMetadata {
37 pub fn resolve(&self, app: &AppSettingsApp) -> ResolvedMetadata {
38 let base_url = app.url.trim_end_matches('/');
39
40 ResolvedMetadata {
41 description: self.description.clone().unwrap_or_else(|| {
42 compact_str::format_compact!(
43 "Manage your game servers and services with {}.",
44 app.name
45 )
46 }),
47 theme_color: self.theme_color.clone(),
48 og_image: match self.og_image.as_deref() {
49 Some(image)
50 if image.starts_with("http://")
51 || image.starts_with("https://")
52 || image.starts_with("//") =>
53 {
54 image.into()
55 }
56 Some(image) => {
57 compact_str::format_compact!("{base_url}/{}", image.trim_start_matches('/'))
58 }
59 None => compact_str::format_compact!("{base_url}/android-chrome-512x512.png"),
60 },
61 twitter_card: match self.twitter_card {
62 TwitterCard::Summary => "summary",
63 TwitterCard::SummaryLargeImage => "summary_large_image",
64 },
65 robots: if self.indexable {
66 "index, follow"
67 } else {
68 "noindex, nofollow"
69 },
70 }
71 }
72}
73
74#[async_trait::async_trait]
75impl SettingsSerializeExt for AppSettingsMetadata {
76 async fn serialize(
77 &self,
78 serializer: SettingsSerializer,
79 ) -> Result<SettingsSerializer, anyhow::Error> {
80 Ok(serializer
81 .write_raw_setting("description", self.description.as_deref().unwrap_or(""))
82 .write_raw_setting("theme_color", &*self.theme_color)
83 .write_raw_setting("og_image", self.og_image.as_deref().unwrap_or(""))
84 .write_raw_setting(
85 "twitter_card",
86 match self.twitter_card {
87 TwitterCard::Summary => "summary",
88 TwitterCard::SummaryLargeImage => "summary_large_image",
89 },
90 )
91 .write_raw_setting("indexable", self.indexable.to_compact_string()))
92 }
93}
94
95pub struct AppSettingsMetadataDeserializer;
96
97#[async_trait::async_trait]
98impl SettingsDeserializeExt for AppSettingsMetadataDeserializer {
99 async fn deserialize_boxed(
100 &self,
101 mut deserializer: SettingsDeserializer<'_>,
102 ) -> Result<ExtensionSettings, anyhow::Error> {
103 Ok(Box::new(AppSettingsMetadata {
104 description: deserializer
105 .take_raw_setting("description")
106 .and_then(|s| s.into_optional()),
107 theme_color: deserializer
108 .take_raw_setting("theme_color")
109 .unwrap_or_else(|| "#6c5ce7".into()),
110 og_image: deserializer
111 .take_raw_setting("og_image")
112 .and_then(|s| s.into_optional()),
113 twitter_card: match deserializer.take_raw_setting("twitter_card").as_deref() {
114 Some("summary") => TwitterCard::Summary,
115 _ => TwitterCard::SummaryLargeImage,
116 },
117 indexable: deserializer
118 .take_raw_setting("indexable")
119 .map(|s| s == "true")
120 .unwrap_or(true),
121 }))
122 }
123}