libmcaptcha/master/redis/
master.rs1use actix::dev::*;
19use tokio::sync::oneshot;
20
21use crate::errors::*;
22use crate::master::messages::{
23 AddSite, AddVisitor, GetInternalData, RemoveCaptcha, Rename, SetInternalData,
24};
25use crate::master::Master as MasterTrait;
26use crate::redis::mcaptcha_redis::MCaptchaRedis;
27use crate::redis::RedisConfig;
28
29#[derive(Clone)]
30pub struct Master {
31 pub redis: MCaptchaRedis,
32}
33
34impl Master {
35 pub async fn new(redis: RedisConfig) -> CaptchaResult<Self> {
36 let redis = MCaptchaRedis::new(redis).await?;
37 let master = Self { redis };
38 Ok(master)
39 }
40}
41
42impl MasterTrait for Master {}
43
44impl Actor for Master {
45 type Context = Context<Self>;
46}
47
48impl Handler<AddVisitor> for Master {
49 type Result = MessageResult<AddVisitor>;
50
51 fn handle(&mut self, m: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
52 let (tx, rx) = oneshot::channel();
53
54 let con = self.redis.get_client();
55 let fut = async move {
56 let res = con.add_visitor(m).await;
57 let _ = tx.send(res);
58 }
59 .into_actor(self);
60 ctx.wait(fut);
61 MessageResult(rx)
62 }
63}
64
65impl Handler<AddSite> for Master {
66 type Result = MessageResult<AddSite>;
67
68 fn handle(&mut self, m: AddSite, ctx: &mut Self::Context) -> Self::Result {
69 let (tx, rx) = oneshot::channel();
70 let con = self.redis.get_client();
71 let fut = async move {
72 let res = con.add_mcaptcha(m).await;
73 let _ = tx.send(res);
74 }
75 .into_actor(self);
76 ctx.wait(fut);
77 MessageResult(rx)
78 }
79}
80
81impl Handler<Rename> for Master {
82 type Result = MessageResult<Rename>;
83
84 fn handle(&mut self, m: Rename, ctx: &mut Self::Context) -> Self::Result {
85 let (tx, rx) = oneshot::channel();
86
87 let con = self.redis.get_client();
88 let fut = async move {
89 let res = con.rename_captcha(&m.name, &m.rename_to).await;
90 let _ = tx.send(res);
91 }
92 .into_actor(self);
93 ctx.wait(fut);
94 MessageResult(rx)
95 }
96}
97
98impl Handler<RemoveCaptcha> for Master {
99 type Result = MessageResult<RemoveCaptcha>;
100
101 fn handle(&mut self, m: RemoveCaptcha, ctx: &mut Self::Context) -> Self::Result {
102 let (tx, rx) = oneshot::channel();
103
104 let con = self.redis.get_client();
105 let fut = async move {
106 let res = con.delete_captcha(&m.0).await;
107 let _ = tx.send(res);
108 }
109 .into_actor(self);
110 ctx.wait(fut);
111 MessageResult(rx)
112 }
113}
114
115impl Handler<GetInternalData> for Master {
116 type Result = MessageResult<GetInternalData>;
117
118 fn handle(&mut self, m: GetInternalData, ctx: &mut Self::Context) -> Self::Result {
119 todo!()
120 }
121}
122
123impl Handler<SetInternalData> for Master {
124 type Result = MessageResult<SetInternalData>;
125
126 fn handle(&mut self, m: SetInternalData, ctx: &mut Self::Context) -> Self::Result {
127 todo!()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::master::embedded::counter::tests::get_mcaptcha;
135 use crate::master::messages::RenameBuilder;
136 use crate::master::redis::master::Master;
137 use crate::redis::RedisConfig;
138 use std::{env, sync::OnceLock};
139
140 fn redis_url() -> &'static str {
141 static REDIS_URL: OnceLock<String> = OnceLock::new();
142 REDIS_URL.get_or_init(|| {
143 env::var("LIBMCAPTCHA_TEST_REDIS_URL").unwrap_or_else(|_| "redis://127.0.1.1/".into())
144 })
145 }
146
147 #[actix_rt::test]
148 async fn redis_master_works() {
149 const CAPTCHA_NAME: &str = "REDIS_MASTER_CAPTCHA_TEST";
150 const RENAME_CAPTCHA_NAME: &str = "RENAME_REDIS_MASTER_CAPTCHA_TEST";
151
152 let master = Master::new(RedisConfig::Single(redis_url().into())).await;
153 let sec_master = Master::new(RedisConfig::Single(redis_url().into())).await;
154 let r = sec_master.unwrap().redis.get_client();
155
156 assert!(master.is_ok());
157 let master = master.unwrap();
158 {
159 let _ = master.redis.get_client().delete_captcha(CAPTCHA_NAME).await;
160 let _ = master
161 .redis
162 .get_client()
163 .delete_captcha(RENAME_CAPTCHA_NAME)
164 .await;
165 }
166
167 let addr = master.start();
168
169 let mcaptcha = get_mcaptcha();
170 let duration = mcaptcha.get_duration();
171
172 let add_mcaptcha_msg = AddSite {
173 id: CAPTCHA_NAME.into(),
174 mcaptcha,
175 };
176 addr.send(add_mcaptcha_msg).await.unwrap();
177
178 let add_visitor_msg = AddVisitor(CAPTCHA_NAME.into());
179 addr.send(add_visitor_msg).await.unwrap();
180 let visitors = r.get_visitors(CAPTCHA_NAME).await.unwrap();
181 assert_eq!(visitors, 1);
182
183 let timer_expire = std::time::Duration::new(duration, 0);
184 actix::clock::sleep(timer_expire).await;
185 let visitors = r.get_visitors(CAPTCHA_NAME).await.unwrap();
186 assert_eq!(visitors, 0);
187
188 let rename = RenameBuilder::default()
189 .name(CAPTCHA_NAME.into())
190 .rename_to(RENAME_CAPTCHA_NAME.into())
191 .build()
192 .unwrap();
193 assert!(addr.send(rename).await.is_ok());
194 assert!(addr
195 .send(RemoveCaptcha(RENAME_CAPTCHA_NAME.into()))
196 .await
197 .is_ok());
198 }
199
200 #[actix_rt::test]
201 async fn race_redis_master() {
202 const CAPTCHA_NAME: &str = "REDIS_MASTER_CAPTCHA_RACE";
203
204 let master = Master::new(RedisConfig::Single(redis_url().into())).await;
205 let sec_master = Master::new(RedisConfig::Single(redis_url().into())).await;
206 let r = sec_master.unwrap().redis.get_client();
207
208 assert!(master.is_ok());
209 let master = master.unwrap();
210 {
211 let _ = master.redis.get_client().delete_captcha(CAPTCHA_NAME).await;
212 }
213
214 let addr = master.start();
215
216 let mcaptcha = get_mcaptcha();
217 let duration = mcaptcha.get_duration();
218
219 let add_mcaptcha_msg = AddSite {
220 id: CAPTCHA_NAME.into(),
221 mcaptcha,
222 };
223 addr.send(add_mcaptcha_msg).await.unwrap();
224
225 let add_visitor_msg = AddVisitor(CAPTCHA_NAME.into());
226 for _ in 0..500 {
227 addr.send(add_visitor_msg.clone()).await.unwrap();
228 }
229 let visitors = r.get_visitors(CAPTCHA_NAME).await.unwrap();
230 assert_eq!(visitors, 500);
231
232 let timer_expire = std::time::Duration::new(duration, 0);
233 actix::clock::sleep(timer_expire).await;
234 let visitors = r.get_visitors(CAPTCHA_NAME).await.unwrap();
235 assert_eq!(visitors, 0);
236 }
237}