Skip to main content

shared/settings/
metadata.rs

1use 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}