pispas_modules/
base.rs

1use std::any::Any;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use log::{info, error, debug, warn};
5use async_trait::async_trait;
6use sharing::utils::ConfigEnv;
7use crate::service::{Service, WebSocketWrite};
8
9/// The base module version constant.
10pub const BASE_VERSION: &str = "1.0.0";
11
12/// Enum defining actions for the BaseService.
13#[derive(Deserialize, Serialize, Debug, Clone)]
14#[serde(rename_all = "UPPERCASE")]
15pub enum BaseAction {
16    /// Represents a health check action.
17    Check,
18    /// Represents a print action with a given string.
19    Print { action: String },
20    /// Represents a ping action to verify connectivity.
21    Ping,
22    /// Represents an unknown action.
23    Unknown,
24}
25
26impl From<&str> for BaseAction {
27    /// Converts a string into a `BaseAction` enum.
28    fn from(action: &str) -> Self {
29        match action {
30            "CHECK" => BaseAction::Check,
31            "PRINT" => BaseAction::Print { action: "print".to_string() },
32            "PING" => BaseAction::Ping,
33            _ => BaseAction::Unknown,
34        }
35    }
36}
37
38/// The base service implementation.
39#[derive(Clone)]
40pub struct BaseService;
41
42impl BaseService {
43    /// Creates a new instance of `BaseService`.
44    pub fn new() -> Self {
45        info!("BaseService initialized (version: {})", BASE_VERSION);
46        BaseService
47    }
48}
49
50/// Implementation of the `Service` trait for `BaseService`.
51#[async_trait]
52impl Service for BaseService {
53    /// Runs the given action with optional WebSocket support.
54    async fn run(&self, action: Value, _write: WebSocketWrite) -> (i32, String) {
55        let action_str = action.get("ACTION").and_then(|v| v.as_str()).unwrap_or("UNKNOWN");
56        // PING is a keep-alive fired every 30s per client — log at debug so
57        // the production log stays usable. CHECK / unknown stay at info.
58        if action_str == "PING" {
59            debug!("BaseService: Running action: {:?}", action);
60        } else {
61            info!("BaseService: Running action: {:?}", action);
62        }
63        match action_str {
64            "CHECK" => {
65                // Bootstrap step: the frontend, scoped to a place by its
66                // session, carries `place_id` in the CHECK message. The
67                // first authenticated CHECK pins this TPV to that place
68                // (persisted in ConfigEnv → .env). Subsequent CHECKs
69                // with a different `place_id` are rejected; the operator
70                // must call forget_identity() from the configurator to
71                // re-pair.
72                if let Some(place_id) = action.get("place_id").and_then(|v| v.as_i64()) {
73                    let mut config = ConfigEnv::load();
74                    let was = config.place_id;
75                    match config.set_place_id(place_id) {
76                        Ok(()) => {
77                            if was.is_none() {
78                                info!(
79                                    "BaseService: CHECK bound this TPV to place_id={place_id}"
80                                );
81                            } else {
82                                debug!(
83                                    "BaseService: CHECK reaffirms place_id={place_id}"
84                                );
85                            }
86                        }
87                        Err(e) => {
88                            warn!("BaseService: CHECK rejected — {e}");
89                            return (1, format!("place_id mismatch: {e}"));
90                        }
91                    }
92                }
93                info!("BaseService: Performing check action");
94                (0, "check ok".to_string())
95            }
96            "PING" => {
97                debug!("BaseService: Performing ping action");
98                (0, "pong".to_string()) // Returns a "pong" response.
99            }
100            _ => {
101                error!("BaseService: Unknown action");
102                (1, "unknown action".to_string())
103            }
104        }
105    }
106
107    /// Allows dynamic casting to `Any` for type-safe downcasting.
108    fn as_any(&self) -> &dyn Any {
109        self
110    }
111
112    /// Stops the service and performs any cleanup if necessary.
113    fn stop_service(&self) {
114        info!("BaseService: Stopping service");
115    }
116
117    /// Returns the version of the BaseService module.
118    fn get_version(&self) -> String {
119        BASE_VERSION.to_string()
120    }
121}
122
123impl Default for BaseAction {
124    /// Returns the default `BaseAction` as `Unknown`.
125    fn default() -> Self {
126        BaseAction::Unknown
127    }
128}