1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use async_trait::async_trait;
use http::{header, HeaderValue};
use serde::{Deserialize, Serialize};
use tardis::{
    basic::{error::TardisError, result::TardisResult},
    TardisFuns,
};

use crate::{functions::http_route::SgHttpRouteMatchInst, plugins::context::SgRouteFilterRequestAction};

use super::{BoxSgPluginFilter, SgPluginFilter, SgPluginFilterDef, SgPluginFilterInitDto, SgRoutePluginContext};

pub const CODE: &str = "maintenance";
pub struct SgFilterMaintenanceDef;

impl SgPluginFilterDef for SgFilterMaintenanceDef {
    fn inst(&self, spec: serde_json::Value) -> TardisResult<BoxSgPluginFilter> {
        let filter = TardisFuns::json.json_to_obj::<SgFilterMaintenance>(spec)?;
        Ok(filter.boxed())
    }
}

#[derive(Default, Debug, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct SgFilterMaintenance {
    is_enabled: bool,
    title: Option<String>,
    msg: Option<String>,
}

#[async_trait]
impl SgPluginFilter for SgFilterMaintenance {
    fn accept(&self) -> super::SgPluginFilterAccept {
        super::SgPluginFilterAccept {
            kind: vec![super::SgPluginFilterKind::Http],
            ..Default::default()
        }
    }
    async fn init(&mut self, _: &SgPluginFilterInitDto) -> TardisResult<()> {
        Ok(())
    }

    async fn destroy(&self) -> TardisResult<()> {
        Ok(())
    }

    async fn req_filter(&self, _: &str, mut ctx: SgRoutePluginContext, _matched_match_inst: Option<&SgHttpRouteMatchInst>) -> TardisResult<(bool, SgRoutePluginContext)> {
        if self.is_enabled {
            ctx.set_action(SgRouteFilterRequestAction::Response);
            let default_content_type = HeaderValue::from_static("text/html");
            let content_type = ctx.get_req_headers().get(header::CONTENT_TYPE).unwrap_or(&default_content_type).to_str().unwrap_or("");
            match content_type {
                "text/html" => {
                    let title = self.title.clone().unwrap_or("System Maintenance".to_string());
                    let msg = self.msg.clone().map(|x| x.replace("/n", "<br>")).unwrap_or("We apologize for the inconvenience, but we are currently performing system maintenance. We will be back to normal shortly.<br> Thank you for your patience, understanding, and support.".to_string());
                    ctx.set_resp_body(
                        format!(
                            r##"<!DOCTYPE html>
                    <html>
                    <head>
                        <title>{title}</title>
                        <style>
                            body {{
                                background: radial-gradient(circle at top left, #FFD700 0%, #FF8C00 25%, #FF4500 50%, #FF6347 75%, #FF1493 100%);
                                height: 100vh;
                                display: flex;
                                justify-content: center;
                                align-items: center;
                            }}
                    
                            h1 {{
                                font-size: 40px;
                                color: #FFFFFF;
                            }}
                    
                            p {{
                                font-size: 20px;
                                color: #FFFFFF;
                                margin-bottom: 20px;
                            }}
                        </style>
                    </head>
                    <body>
                        <div>
                        <h1>{title}</h1>
                        <br>
                            <p>{msg}</p>
                        </div>
                    </body>
                    </body>
                    </html>
                    "##
                        )
                        .into_bytes(),
                    )?;
                }
                "application/json" => {
                    let msg = self.msg.clone().unwrap_or("We apologize for the inconvenience, but we are currently performing system maintenance. We will be back to normal shortly.Thank you for your patience, understanding, and support.".to_string());
                    return Err(TardisError::forbidden(&msg, ""));
                }
                _ => {
                    ctx.set_resp_body("<h1>Maintenance</h1>".to_string().into_bytes())?;
                }
            }
        }
        Ok((true, ctx))
    }

    async fn resp_filter(&self, _: &str, ctx: SgRoutePluginContext, _: Option<&SgHttpRouteMatchInst>) -> TardisResult<(bool, SgRoutePluginContext)> {
        Ok((true, ctx))
    }
}